0

Good afternoon. I am new to ruby and trying to build my first application. I am using sqlite database and rails 5.0. I have a model called Person that has the first name, last name and date of birth as attributes. On the page where I list people I want to add the age of the people and obtain an average of the ages of the people

My controller looks like this:

  before_action :set_persona, only: %i[ show edit update destroy ]

  # GET /personas or /personas.json
  def index
    @persona = Persona.order("cast(strftime('%m', fecha_nacimiento) as integer)")
  end 

And my view like this


<table>
  <thead>
    <tr>
      <th>Nombre</th>
      <th>Apellido</th>
      <th>Fecha nacimiento</th>
      <th>Dni</th>
      <th>Edad</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @persona.each do |persona| %>
      <tr>
        <td><%= persona.nombre %></td>
        <td><%= persona.apellido %></td>
        <td><%= persona.fecha_nacimiento %></td>
        <td><%= persona.dni %></td>
        <td><%= Time.now.year - persona.fecha_nacimiento.year %></td>
        <td><%= link_to 'Detail', persona %></td>
        <td><%= link_to 'Edit', edit_persona_path(persona) %></td>
      </tr>
    <% end %>
  </tbody>
</table>
  <p>El promedio de edad de las personas es: </p>

Since I don't have a field in the database called "age" I can't understand how I can achieve the result. The objective would be to iterate through each of the people and divide it by the length of it, or is there an easier way?

Please excuse my ignorance, thank you very much in advance.

Admardusa
  • 13
  • 1

2 Answers2

1

What you want to do is select your calculated column and give it an alias:

  def index
    @persona = Persona.select(
         Persona.arel_table[Arel.star], # personas.*
         "cast(strftime('%m', fecha_nacimiento) as integer) as age"
       )
       .order(age: :desc)
  end 

Any columns you select will be available in the resulting model instances as attributes:

<table>
  <thead>
    <tr>
      <th>Nombre</th>
      <th>Apellido</th>
      <th>Fecha nacimiento</th>
      <th>Dni</th>
      <th>Edad</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @persona.each do |persona| %>
      <tr>
        <td><%= persona.nombre %></td>
        <td><%= persona.apellido %></td>
        <td><%= persona.fecha_nacimiento %></td>
        <td><%= persona.dni %></td>
        <td><%= persona.age %></td>
        <td><%= link_to 'Detail', persona %></td>
        <td><%= link_to 'Edit', edit_persona_path(persona) %></td>
      </tr>
    <% end %>
  </tbody>
</table>
max
  • 96,212
  • 14
  • 104
  • 165
  • Instead of doing all that hacky shit in the model just go with `"cast(strftime('%m', fecha_nacimiento) as integer) as age"` until you switch to a real database like Postgres that actually has date and time columns and functions to do date differences. On PG you just use something like `AGE(NOW(), 'birth_date')`. – max Jun 11 '21 at 13:20
-1

The easiest way to implement what you're asking is to do the operation within the view. This kind of breaks MVC but it's the fastest.

<% edades = 0 %>
<% @persona.each do |persona| %>
<% edades += Time.now.year - persona.fecha_nacimiento.year %>
<!-- ... the rest of the view code -->
<% end %>
<% average = edades.to_d / @persona.length # need to typecast to decimal so you can have fractionals %>

To do this in an MVC manner, you will have to do the computation in the controller.

  def index
    @persona = Persona.order("cast(strftime('%m', fecha_nacimiento) as integer)")
    @average = @persona.collect { |p| Time.now.year - p.fecha_nacimiento.year }.sum / @persona.length.to_d
  end 

It would be easier to implement an age method in Person model so you can just call the method instead.

class Person
  def edad
    Time.now.year - fecha_nacimiento.year
  end
end

and lastly, the computation for the age is more complex than just current year less birthday year. We use this function (taken from https://stackoverflow.com/a/2357790/365218) to calculate for the age.

def age(dob)
  now = Time.now.utc.to_date
  now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
end
Rystraum
  • 1,985
  • 1
  • 20
  • 32