【问题标题】:Rails 4 Accessing Join Table AttributesRails 4 访问连接表属性
【发布时间】:2014-08-11 02:14:58
【问题描述】:

我有一个 has_many through 用于配方应用程序的连接表设置,其中 IngredientMeal 通过 MealIngredient 连接。在MealIngredient 中,我有meal_idingredient_idamount。我的问题是:如何访问amount 列?

在我的食谱视图中,我循环浏览配料:

@meal.ingredients.each do |i|

我可以从MealIngredient 加入记录中访问成分的属性,但不能访问数量。

我尝试在查询中使用includes 执行@meal.ingredients.includes(:meal_ingredients),但我不确定如何在上述循环中访问amount。当我使用i.inspect 时,我根本看不到对meal_ingredients 表的任何引用。

有没有办法使用i.amount访问该循环中的变量?

提前感谢您的帮助!

【问题讨论】:

    标签: sql ruby-on-rails ruby-on-rails-4


    【解决方案1】:

    于 2020 年 8 月 1 日更新 - RPECK

    为此苦苦挣扎了几个月,直到我们找到了合适的解决方案:

    --

    ActiveRecord Association Extensions

    您遇到的问题是 Rails 只会使用连接表中的 foreign_keys 来加载您需要的关联数据。除非您实际直接加载连接模型,否则它不会让您能够访问连接属性

    一些搜索将我们引向ActiveRecord Association Extensions - 一种访问不同 ActiveRecord 关联之间的中间数据的方法(使用称为 proxy_association 的集合)。这将允许您从连接模型访问额外的属性,将它们附加到您的“原始”模型:

    #app/models/ingredient.rb
    class Ingredient < ActiveRecord::Base
       attr_accessor :amount #-> need a setter/getter
    end
    
    #app/models/meal.rb
    class Meal < ActiveRecord::Base
       has_many :meal_ingredients
       has_many :ingredients, { -> extending: IngredientAmount }, through: :meal_ingredients
    end
    
    #app/models/concerns/ingerdient_amount.rb
    module IngredientAmount
    
        # => Load
        # => Triggered whenever module is invoked (allows us to populate response data)
        # => #<Method: ActiveRecord::Relation#load(&block) c:/Dev/Apps/pwinty-integration/.bundle/ruby/2.7.0/gems/activerecord-6.0.2.1/lib/active_record/relation.rb:614>
        def load(&block)
    
           # => This is called from the following reference - if you want to dig deeper, you can use method(:exec_queries).source_location
           # => c:/Dev/Apps/pwinty-integration/.bundle/ruby/2.7.0/gems/activerecord-6.0.2.1/lib/active_record/association_relation.rb:42
          unless loaded?
            exec_queries do |record|
              record.assign_attributes({amount: items[record.id]}) if items[record.id].present?
            end
          end
    
        end
    
        # Load
        # Deprecated with AR 6.0
        #def load
        #   amounts.each do |amount|
        #       proxy_association.target << amount
        #   end
        #end
    
        #Private
        private
    
        #Amounts
        # Not needed after AR 6.0
        #def amounts
        #   return_array = []
        #   through_collection.each_with_index do |through,i|
        #       associate = through.send(reflection_name)
        #       associate.assign_attributes({amount: items[i]}) if items[i].present?
        #       return_array.concat Array.new(1).fill( associate )
        #   end
        #   return_array
        #end
    
        #######################
        #      Variables      #
        #######################
    
        #Association
        def reflection_name
            proxy_association.source_reflection.name
        end
    
        #Foreign Key
        def through_source_key
            proxy_association.reflection.source_reflection.foreign_key
        end
    
        #Primary Key
        def through_primary_key
             proxy_association.reflection.through_reflection.active_record_primary_key
        end
    
        #Through Name
        def through_name
            proxy_association.reflection.through_reflection.name
        end
    
        #Through
        def through_collection
            proxy_association.owner.send through_name
        end
    
        #Captions
        def items
            #through_collection.map(&:amount)
            through_collection.pluck(through_source_key, :amount).map{ |id, amount| { id => amount } }.inject(:merge) #-> { id: record, id: record }
        end
    
        #Target
        # This no longer works with AR 6.0+
        #def target_collection
        #   #load_target
        #   proxy_association.target
        #end
    
    end
    

    这应该现在将amount 属性附加到您的ingredient 对象,允许您执行:

    @meal = Meal.find 1
    @meal.ingredients.each do |ingredient|
       ingredient.amount
    end
    

    【讨论】:

    • 太棒了,感谢您的详细回答和解释!我更喜欢这种方法,因为最终结果在视图中最有意义,并且逻辑在关注点中不受阻碍。
    • 这确实是一件了不起的工作——我会在适当的时候把它变成一颗宝石 :D 让我知道它是如何为你工作的——可能有一些问题或任何可能
    • 如何更新本例中的“金额”?这工作正常,但我还需要更新值。
    • Richard...两年后,我问您有兴趣回答的更新值问题。这里是stackoverflow.com/questions/39010893/…
    • 看起来正是我想要实施的解决方案。非常干净 - 不幸的是,无论如何我都会得到 nil 值。使用 byebug - 它甚至不加载属性(调用加载)。我想念什么吗?注意我在 Rails 5 @RichardPeck
    【解决方案2】:

    在这种情况下,您应该循环访问meal_ingredients 关联。您应该立即加载 ingredients 关联以减少数据库查询。

    @meal.meal_ingredients.includes(:ingredient).each do |meal_ingredient|
      puts meal_ingredient.amount
      puts meal_ingredient.ingredient.name
    end
    

    更新

    此更新是在 Rich Peck 的回答之后发布的,但我认为有一种更简单的方法可以实现他所做的。

    @meal.ingredients.select('ingredients.*, meal_ingredients.amount').each do |ingredient|
      puts ingredient.amount
      puts ingredient.name
    end
    

    【讨论】:

    • 是的,我只是在尝试您更新的解决方案,发现它没有进行类型转换(例如,布尔列返回为 0 或 1),因此这可能对某些人来说是个问题
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多