Qadeer
Qadeer

Reputation: 221

all records of has_many through rails

I am a beginner to RoR and have been doing some coding recently myself using the guides. I am currently stuck at a point and have done some googling around to find the reason, but with no luck. Can somebody please help me with this ?

My scenario:

class Enum < ActiveRecord::Base
  establish_connection :common
  attr_accessible :name, :description, :updated_at
  has_many :enumlists
  has_many :enumvalues, :through => :enumlists
  validates :name, presence: true, length: { maximum:20}
  validates :description, length: {maximum: 100}
end

class Enumlist < ActiveRecord::Base
  establish_connection :common
  belongs_to :enums
  belongs_to :enumvalues
  attr_accessible :updated_at
end

class Enumvalue < ActiveRecord::Base
  establish_connection :common
  attr_accessible :category, :description, :updated_at
  has_many :enumlists
  has_many :enums, :through => :enumlists
end

and the migration entry is:

class CreateEnums < ActiveRecord::Migration
  def change
    create_table :enums do |t|
        t.string :name, :limit=>20
        t.string :description, :limit=>100
        t.timestamps
    end
    add_index :enums, [:name], :unique => true

    create_table :enumvalues do |t|
      t.string :category, :limit=>50
      t.string :description, :limit=>50
      t.timestamps
    end

    create_table :enumlists do |t|
        t.integer :enum_id
        t.integer :enumvalue_id
        t.timestamps
    end
    add_index :enumlists, [:enum_id, :enumvalue_id], :unique => true
  end
end

Now, i would like to view all the Enums with their values in the controller. From the examples i have seen, i can do Enum.find(params[:id]).enumlists, however i dont find a way to get all the enums with the linked tables. I'm obviously missing something simple here, but i'm unable to figure out what it is..

Thanks..

Update: I can use @enums = Enum.includes(:enumlists) and it does return the correct entries, however i cannot get 1 level deeper.. i.e @enums = Enum.includes(:enumlists, :enumvalues) and then use @enums.enumlists.enumvalues to get the list of all enumeration values.

To help, the data is structured as below:
Enums:
-----------------------------------------------------------------------------------
id           name              description
-----------------------------------------------------------------------------------
1            App1 OS            Operating systems for Application 1
2            App2 OS            Operating systems for Application 2

Enumvalues:
-----------------------------------------------------------------------------------
id           category              description
-----------------------------------------------------------------------------------
1            Operating Systems     AIX
2            Operating Systems     Linux
3            Operating Systems     Windows

Enumlists:
-----------------------------------------------------------------------------------
id           enum_id              enumvalue_id
-----------------------------------------------------------------------------------
1            1                    1
2            1                    2
3            2                    1

What i need in the output is:
enums = [ [1, [Operating Sysetms, AIX, Operating Systems, Linux], 2,[Operating Sysetms, AIX] ]

Update:

The following should work.

@enums = Enum.includes({:enumlists => :enumvalues})

Note, the names enum_lists and enum_values would be more idiomatic Ruby

@AlexBlakemore - Thanks. Your input made me believe that there was an issue in the model and once i found it, your method and the method i was using earlier both appear to be working fine..

I think i found the reason for the failure. It was due to the class Enumlist which had an incorrect definition. It should have been

class Enumlist < ActiveRecord::Base
  establish_connection :common
  belongs_to :enum   <-- Renamed to enum instead of enums
  belongs_to :enumvalue <-- Renamed to enumvalue instead of enumvalues
  attr_accessible :updated_at
end

After making the above change, i tried using the following ways to link the tables and both of them work perfectly.. Method1:

@enums = Enum.includes(:enumlists, :enumvalues)
  [1m[36mEnum Load (0.4ms)[0m  [1mSELECT `enums`.* FROM `enums` [0m
  [1m[35mEnumlist Load (0.3ms)[0m  SELECT `enumlists`.* FROM `enumlists` WHERE `enumlists`.`enum_id` IN (1, 2)
  [1m[36mEnumvalue Load (0.3ms)[0m  [1mSELECT `enumvalues`.* FROM `enumvalues` WHERE `enumvalues`.`id` IN (1, 2)[0m
@enums = Enum.includes(:enumlists, :enumvalues)
[#<Enum id: 1, name: "WMB OS", description: "Operating System (WMB)", created_at: "2000-01-01 09:00:00", updated_at: "2000-01-01 09:00:00">, #<Enum id: 2, name: "4690 OS", description: "Operating System (DEC)", created_at: "2008-01-01 09:00:00", updated_at: "2008-01-01 09:00:00">]

Method2: Using the suggestion from @AlexBlakemore also i am getting a similar output.

@enums = Enum.includes({:enumvalues=>:enumlists})
  [1m[36mEnum Load (0.2ms)[0m  [1mSELECT `enums`.* FROM `enums` [0m
  [1m[35mEnumlist Load (0.3ms)[0m  SELECT `enumlists`.* FROM `enumlists` WHERE `enumlists`.`enum_id` IN (1, 2)
[#<Enum id: 1, name: "WMB OS", description: "Operating System (WMB)", created_at: "2000-01-01 09:00:00", updated_at: "2000-01-01 09:00:00">, #<Enum id: 2, name: "4690 OS", description: "Operating System (DEC)", created_at: "2008-01-01 09:00:00", updated_at: "2008-01-01 09:00:00">]
  [1m[36mEnumvalue Load (0.6ms)[0m  [1mSELECT `enumvalues`.* FROM `enumvalues` WHERE `enumvalues`.`id` IN (1, 2)[0m
  [1m[35mEnumlist Load (0.4ms)[0m  SELECT `enumlists`.* FROM `enumlists` WHERE `enumlists`.`enumvalue_id` IN (1, 2)

There are no errors so clearly the list is getting populated by both methods. But when i try to access the enumlist/enumvalues, i get the a NoMethodError. I have tried various combinations, none worked..

@enums.enumlists
@enums.enumlist
@enums.enumvalues
@enums.enumvalue

Not sure what is missing in here.. Any suggestions please ?? Once i get this to work, i can confirm which of the above two methods returns the correct result. Fingers crossed !!

Upvotes: 2

Views: 493

Answers (2)

Qadeer
Qadeer

Reputation: 221

Ok.. Here's the solution to the problem.

The first mistake was that the join table was linking the table names in singular form, due to which the join was broken. I.e The table models should be as below. It contains 3 changes to my original model posted in the query. a. Renamed the join table fields to use singular form. b. As suggested by @Alex, the original table names have been renamed to more idomatic ruby c. t.integer is changed it to t.reference to make it explicit.

class Enum < ActiveRecord::Base
  establish_connection :common
  attr_accessible :name, :description, :updated_at
  has_many :enum_lists
  has_many :enum_values, :through => :enum_lists
  validates :name, presence: true, length: { maximum:20}
  validates :description, length: {maximum: 100}
end

class Enum_list < ActiveRecord::Base
  establish_connection :common
  belongs_to :enum   <-- Renamed to enum instead of enums
  belongs_to :enum_value <-- Renamed to enum_value instead of enum_values
  attr_accessible :updated_at
end

class Enum_value < ActiveRecord::Base
  establish_connection :common
  attr_accessible :category, :description, :updated_at
  has_many :enum_lists
  has_many :enums, :through => :enum_lists
end

The second issue was that i was trying to use find(:all) in the controller to get the results. Instead it could easily be done using one of the below

@enums = Enum.includes(:enum_lists, :enum_values) or 
@enums = Enum.includes(:enum_values) or 
@enums = Enum.includes({:enum_lists => :enum_values}), 

bearing in mind the number of queries generated by each usage.

The last issue was that i couldn't print the values on ruby console by using @enums.enum_lists. This would rather need to be written within a loop to print the values. Hence, i coded the following in my view to print the enumeration name and its corresponding values as:

<% @enums.each do |enum| %>
<h4><%= enum.name %></h4>
<table width="100%">
  <tr class="<%= cycle('odd','even', :name=>"line") %>"><td>Description</td><td><%= enum.description %></td></tr>
  <table>
    <% enum.enum_values.each do |enum_value| %>
      <tr class="<%= cycle('odd','even', :name=>"line") %>"><td><%=  enum_value.category %></td><td><%=  enum_value.description %></td></tr>
    <% end %>
      <tr class="<%= cycle('odd','even', :name=>"line") %>"><td colspan="2"><span class="greyed">Last updated <%= time_ago_in_words(enum.updated_at) %> ago</span></td></tr>
  </table>
</table>

Thanks a lot to @Alex for his inputs.. !! I can continue my work on RoR further !!

Upvotes: 0

Alex Blakemore
Alex Blakemore

Reputation: 11896

The following should tell ActiveRecord to eagerly fetch enumlists and enumvalues when fetching Enums.

@enums = Enum.includes({:enumlists => :enumvalues})

Note, the names enum_lists and enum_values would be more idiomatic Ruby

or you could try just

@enums = Enum.includes(:enumvalues)

if you really just mean to have a has_and_belongs_to_many association. (HABTM)

Upvotes: 1

Related Questions