即使没有关联,也有很多通过相关的对象

时间:2015-02-27 11:16:27

标签: ruby-on-rails has-many-through

我建立了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),但这似乎不对。

我玩过不同的连接,但他们只加载已经与菜相关的过敏原。什么是轨道方法?

1 个答案:

答案 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

所有人都应该可以放心。