通过 has_many :through 实现 has_and_belongs_to_many 关联

6
我正在一个Ruby on Rails项目中尝试做以下事情:
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

但是调用实例方法some_food_item.places会给我以下错误:

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))

这是很有道理的 - 因为在FoodItem和FoodCategory上有HABTM,所以我有一个名为food_categories_food_items的映射表。

要让some_food_item.places通过映射表正确查找地点,而不是在food_categories表中查找food_item_id,我需要做什么?

4个回答

7

我第一版的答案是错误的,但这个版本完美地解决了问题。我第一次打字时犯了几个错别字(没有实际创建应用程序进行测试的危险),但这次我进行了验证。需要一个插件,但这很容易。首先,安装插件:

script/plugin install git://github.com/ianwhite/nested_has_many_through.git

这将安装Ian White的解决方法,使用起来非常方便。现在,模型可以直接从我设置的测试应用程序中复制,并且可以正常工作:

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

现在,“远程”关联同样有效。 place_instance.food_itemsfood_item.places 都能够无缝运作,而且涉及到的更简单的关联也同样如此。 为了参考,这里是我的架构图,显示所有外键所在的位置:
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

希望这可以帮到你!

更新:最近这个问题出现了几次。我写了一篇文章,关于在Ruby on Rails中嵌套has_many:through关系,进行详细解释。它甚至有一个伴随的GitHub示例应用程序可供下载和试玩。


这确实有帮助!感谢你们提供的解决方案,也感谢你们对HABTM的提示。我相信这不会是我应用此解决方案的唯一场所! - Rasmus Bang Grouleff

2
几个月前我写过一篇关于这个的文章。简而言之,Rails 不允许通过 has_and_belongs_to_many 关联来实现 has_many through。不过,你可以通过类似以下的方式部分模拟这种关系:
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

这将给你所需的some_food_item.places方法。

2

我正在使用Rails 3.2.13和Rasmus,你的原始设置现在在HABTM上似乎可以正常工作。

我建议用户在尝试解决方法之前先尝试一下。


-1

这是正确的,因为你无法在连接表上执行“has many through”。 本质上,您正在尝试将关系扩展一个程度比您实际可以扩展的更远。 HABTM(has_and_belongs_to_many)对大多数问题都不是很强大的解决方案。

在您的情况下,我建议添加一个名为FoodCategoryItem的模型,并重新命名加入表以匹配。 您还需要添加回主键字段。 然后按如下设置您的模型关联:

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

请注意,我还在“Place -> has_many:food_items”中修正了一个拼写错误。这应该可以满足您的需求,并为您将来添加FoodCategoryItems“join”模型的功能提供额外的奖励。

我无法让它工作-当调用some_food_item.places时,我得到相同的错误。显然这是ActiveRecord的限制(请参见http://www.ruby-forum.com/topic/115029)。 - Rasmus Bang Grouleff
你是对的,抱歉。我添加了一个修复所有问题的答案。 - Jaime Bellmyer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接