有很多直通 - 即使不存在关联也获取相关对象
has many through - get related object even if no associations exist
我已经建立了 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)
,但这似乎是错误的。
我试过不同的连接,但它们只加载已经与菜肴相关的过敏原。 rails 方法是什么?
has_many :through
协会为此提供了一个工具 - 它是 allergen_ids
方法。从理论上讲,它只是一个包含所有 id 的数组,但它与 setter allergen_ids=
一起提供,它采用一个数组并在您的模型和具有给定 id 的模型之间创建关联。所以你需要做的就是在你的表单中添加以下内容:
<%= f.collection_check_boxes :allergen_ids, Allergen.all, :id, :name %>
Rails 将呈现所有过敏原的复选框,并使用提到的方法来决定要选择哪一个。之后它将以数组的形式发送所有这些,并将传递给提到的 setter.
现在,问题。与大多数属性编写器不同,将数组传递给 setter 实际上会调用数据库更改 - 在调用保存之前不会缓存它,并且无论验证是否失败都会执行。自然是不能接受的。我通常必须制定 "small" 解决方法(不要使用术语 "bloody hack")。技巧是覆盖 ids setter 和 getter 并在保存挂钩后创建。由于这是一个经常出现的问题,我通常将其放在一个单独的模块中:
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
而且都应该是可缓存的。
我已经建立了 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)
,但这似乎是错误的。
我试过不同的连接,但它们只加载已经与菜肴相关的过敏原。 rails 方法是什么?
has_many :through
协会为此提供了一个工具 - 它是 allergen_ids
方法。从理论上讲,它只是一个包含所有 id 的数组,但它与 setter allergen_ids=
一起提供,它采用一个数组并在您的模型和具有给定 id 的模型之间创建关联。所以你需要做的就是在你的表单中添加以下内容:
<%= f.collection_check_boxes :allergen_ids, Allergen.all, :id, :name %>
Rails 将呈现所有过敏原的复选框,并使用提到的方法来决定要选择哪一个。之后它将以数组的形式发送所有这些,并将传递给提到的 setter.
现在,问题。与大多数属性编写器不同,将数组传递给 setter 实际上会调用数据库更改 - 在调用保存之前不会缓存它,并且无论验证是否失败都会执行。自然是不能接受的。我通常必须制定 "small" 解决方法(不要使用术语 "bloody hack")。技巧是覆盖 ids setter 和 getter 并在保存挂钩后创建。由于这是一个经常出现的问题,我通常将其放在一个单独的模块中:
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
而且都应该是可缓存的。