I am trying to do the following in a Ruby on Rails project:
class FoodItem < ActiveRecord::Base
has_and_belongs_to_many :food_categories
has_many :places, :through => :food_categories
end
class FoodCategory < ActiveRecord::Base
has_and_belongs_to_many :food_items
belongs_to :place
end
class Place < ActiveRecord::Base
has_many :food_categories
has_many :food_items, :through => :food_category
end
But calling the instance method some_food_item.places
gives me the following error:
ActiveRecord::StatementInvalid: PGError: ERROR: column
food_categories.food_item_id does not exist
LINE 1: ...laces".id = "food_categories".place_id WHERE (("food_cate...
: SELECT "places".* FROM "places" INNER JOIN "food_categories" ON "places".id = "food_categories".place_id WHERE (("food_categories".food_item_id = 1))
Which makes perfect sense - because of the HABTMs on FoodItem and FoodCategory I have the mapping table named food_categories_food_items
.
What do I have to do to get some_food_item.places
to look places up correctly through the mapping table instead of looking for a food_item_id
in the food_categories
table?
My first version of the answer was incorrect, but this one works perfectly. I made a couple of typos the first time (the hazard of not actually creating an app to test) but this time I verified. And a plugin is needed, but this is easy. first, install the plugin:
script/plugin install git://github.com/ianwhite/nested_has_many_through.git
This installs Ian White's workaround, and it works seamlessly. Now the models, copied directly from the test app I setup to get this working:
class FoodItem < ActiveRecord::Base
has_many :food_category_items
has_many :food_categories, :through => :food_category_items
has_many :places, :through => :food_categories
end
class FoodCategory < ActiveRecord::Base
has_many :food_category_items
has_many :food_items, :through => :food_category_items
belongs_to :place
end
class FoodCategoryItem < ActiveRecord::Base
belongs_to :food_item
belongs_to :food_category
end
class Place < ActiveRecord::Base
has_many :food_categories
has_many :food_category_items, :through => :food_categories
has_many :food_items, :through => :food_category_items
end
Now "far" associations work just as well. place_instance.food_items
and food_item.places
both work flawlessly, as well as the simpler associations involved. Just for reference, here's my schema to show where all the foreign keys go:
create_table "food_categories", :force => true do |t|
t.string "name"
t.integer "place_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "food_category_items", :force => true do |t|
t.string "name"
t.integer "food_item_id"
t.integer "food_category_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "food_items", :force => true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "places", :force => true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
Hope this helps!
UPDATE: This question has come up a few times recently. I wrote an article, nesting your has_many :through relationships , to explain in detail. It even has an accompanying example application on GitHub to download and play around with.
I'm using Rails 3.2.13 and Rasmus, your original setup now seems to work fine on a HABTM.
I'd suggest users try first before attempting a workaround.
A few months ago I wrote an article about this . In short, has_many
through a has_and_belongs_to_many
association is not allowed by Rails. However, you can partly simulate the relationship by doing something like this:
class FoodItem < ActiveRecord::Base
has_and_belongs_to_many :food_categories
named_scope :in_place, lambda{ |place|
{
:joins => :food_categories,
:conditions => {:food_categories => {:id => place.food_category_ids}},
:select => "DISTINCT `food_items`.*" # kill duplicates
}
}
end
class FoodCategory < ActiveRecord::Base
has_and_belongs_to_many :food_items
belongs_to :place
end
class Place
has_many :food_categories
def food_items
FoodItem.in_place(self)
end
end
This will give you the some_food_item.places
method you seek.
This is correct, because you can't peform "has many through" on a join table. In essence, you're trying to extend the relationship one degree further than you really can. HABTM (has_and_belongs_to_many) is not a very robust solution to most problems.
In your case, I'd recommend adding a model called FoodCategoryItem, and renaming your join table to match. You'll also need to add back the primary key field. Then setup your model associations like this:
class FoodItem < ActiveRecord::Base
has_many :food_categories, :through => :food_category_items
has_many :places, :through => :food_categories
end
class FoodCategory < ActiveRecord::Base
has_many :food_items, :through => :food_category_items
belongs_to :place
end
class FoodCategoryItems < ActiveRecord::Base
belongs_to :food_item
belongs_to :food_category
end
class Place < ActiveRecord::Base
has_many :food_categories
has_many :food_items, :through => :food_categories
end
Note, I also fixed a typo in "Place -> has_many :food_items". This should take care of what you need, and give you the added bonus of being able to add functionality to your FoodCategoryItems "join" model in the future.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.