0

I have a Rails 3 ActiveRecord that belongs_to two different ActiveRecords. Example

class Animal < ActiveRecord::Base
   belongs_to: species
   belongs_to: zoo
...
end

where the animals table contains a species_id, zoo_id, name, and description and the tables species with a scientific_name and zoo has address.

In the controller, I have a query

 @animals = Animal.includes(:species, :zoo).order(:name)

and a list of columns I want displayed in the view,

 @columns = ["name", "description", "species.scientific_name", "zoo.address"]

In the view, I want the creation of a HTML table to be driven by the list of column names, e.g.

<table>
  <tbody>
    <tr>
    <% @animals.each do |animal| %>
      <% %columns.each do |col| } %>
        <td><%= animal[</td>
      <% end %>
    <% end %>
    </tr>
  </tbody>
</table>

this works great for animals's name and description, but does not work for species.scientific_name and zoo.address.

I know I could special case the loop and access the included classes directly like animal.species['scientific_name'], but I was hoping there would be a way to access the included classes by name. Something like animal['species']['scientific_name']

Steve Wilhelm
  • 6,200
  • 2
  • 32
  • 36

1 Answers1

2

Approach 1

Monkey patch the ActiveRecord class. Refer to this answer for details about monkey patching AR class.

class ActiveRecord::Base
  def read_nested(attrs)
    attrs.split(".").reduce(self, &:send)
  end
end

Sample nested attribute access:

animal.read_nested("zoos.address")
user.read_nested("contacts.first.credit_cards.first.name")
product.read_nested("industry.category.name")

For your use case:

Controller:

@columns = %w(name color zoo.address species.scientific_name)

View

<% @animals.each do |animal| %>
  <% @columns.each do |col| } %>
    <td><%= animal.read_nested(col)%></td>
  <% end %>
<% end %>

Approach 2

Add select clause to select the columns and alias them.

@animals = Animal.includes(:species, :zoo).select("
    animals.*, 
    species.scientific_name AS scientific_name,
    zoos.address AS zoo_address").
  order(:name)

Now in your view, you can access attributes like scientific_name, zoo_address like regular model attributes.

Community
  • 1
  • 1
Harish Shetty
  • 64,083
  • 21
  • 152
  • 198