我建立了has_many :through
关系:
class Dish
has_many :dish_allergenes
has_many :allergenes, :through => :dish_allergenes
end
class DishAllergene
belongs_to :dish
belongs_to :allergene
end
class Allergene
has_many :dish_allergenes
has_many :dishes, :through => :dish_allergenes
end
有一套约10种过敏原。添加或编辑菜肴时,应该能够直接调整过敏原。所以我的目标是将所有过敏原包括在一个菜中,即使没有任何关联。
我知道我可以在表单中调用Allergene.all
,然后将它们循环播放检查if dish.allergenes.include?(allergene.id)
,但这似乎不对。
我玩过不同的连接,但他们只加载已经与菜相关的过敏原。什么是轨道方法?
答案 0 :(得分:1)
has_many :through
关联为您提供了一个工具 - 它是allergen_ids
方法。从理论上讲,它只是一个包含所有id的数组,但是它与一个setter allergen_ids=
一起发送,它使用一个数组并在给定id的模型和模型之间创建一个关联。所以您需要做的就是在表单中添加以下内容:
<%= f.collection_check_boxes :allergen_ids, Allergen.all, :id, :name %>
Rails将呈现所有过敏原的复选框,并使用提到的方法来决定选择哪一个。之后,它将以数组的形式发送所有这些,这将被传递给提到的setter。
现在,问题。与大多数属性编写器不同,将数组传递给setter实际上会调用数据库更改 - 在调用save之前它不会被缓存,并且无论验证是否失败都将执行。当然这是不可接受的。我通常要做一个&#34;小&#34;解决方法(不使用术语&#34;血腥黑客&#34;)。诀窍是覆盖ids setter和getter以及在save hook之后创建。由于这是一个经常出现的问题,我通常将其保存在一个单独的模块中:
module ActiveRecord::DirtyIds
extend ActiveSupport::Concern
module ClassMethods
def has_dirty_ids_for(*associations)
associations.each do |association|
define_dirty_ids(association)
end
end
private
def define_dirty_ids(association)
name = "#{association.to_s.singularize}_ids"
# setter will store values in an instance variable
define_method("#{name}_with_cache=") do |value|
value = value.select(&:present?).map(&:to_i)
attribute_will_change!(name) if send(name).sort != value.sort
instance_variable_set(:"@#{name}", value)
end
# getter will read instance variable, if it is falsy fallback for default implementation
define_method("#{name}_with_cache") do
instance_variable_get(:"@#{name}") || send("#{name}_without_cache")
end
# override default association method so it reflects cached values
define_method("#{association}_with_cache") do
if instance_variable_get(:"@#{name}")
association(association).klass.where(id: send("#{name}_with_cache"))
else
send("#{association}_without_cache")
end
end
# after save hook calls the original method
define_method("save_#{name}") do
return if send(name) == send("#{name}_without_cache")
send("#{name}_without_cache=", send(name))
end
private "save_#{name}"
after_save "save_#{name}"
alias_method_chain :"#{name}=", :cache
alias_method_chain :"#{name}", :cache
alias_method_chain :"#{association}", :cache
end
end
end
class ActiveRecord::Base
include ActiveRecord::DirtyIds
end
将此代码放在初始值设定项的新文件中。然后在你的模型中简单地调用:
has_dirty_ids_for :allergenes
所有人都应该可以放心。