Rails 4访问连接表属性

时间:2022-10-17 15:57:25

I have a has_many through join table setup for a recipe app where Ingredient and Meal connect through MealIngredient. Within MealIngredient, I have meal_id, ingredient_id, and amount. My question is: How can I access the amount column?

我有一个用于配方应用程序的has_many通过连接表设置,其中Ingredient和Meal通过MealIngredient连接。在MealIngredient中,我有meal_id,ingredient_id和amount。我的问题是:如何访问金额列?

In my recipe view, I loop through the ingredients:

在我的食谱视图中,我遍历了各种成分:

@meal.ingredients.each do |i|

I can access the properties of the ingredient but not the amount from the MealIngredient join record.

我可以访问成分的属性,但不能访问MealIngredient联接记录中的金额。

I tried using includes in the query doing @meal.ingredients.includes(:meal_ingredients), but I'm unsure of how to access the amount within the aforementioned loop. When I use i.inspect, I don't see any references to the meal_ingredients table at all.

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

Is there some way to access the variable within that loop using i.amount?

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

Thank you in advance for any help!

预先感谢您的任何帮助!

2 个解决方案

#1


22  

Ahhh the good old how do I access my extra join table attributes question. Struggled with this for MONTHS until we came up with a solution

啊,好老我如何访问我的额外连接表属性问题。我们为MONTHS苦苦挣扎,直到我们提出解决方案

--

-

ActiveRecord Association Extensions

ActiveRecord Association Extensions

The problem you have is that Rails will just use the foreign_keys in your join table to load the associative data you need. Unless you actually load the join model directly, it won't give you the ability to access the join attributes

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

Some foraging lead us to ActiveRecord Association Extensions - a way to access the intermediary data in between different ActiveRecord Associations (using a collection called proxy_association). This will allow you to access the extra attributes from the join model, appending them to your "original" model:

一些觅食引导我们进入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, through: :meal_ingredients, extend: IngredientAmount
end

#app/models/concerns/ingerdient_amount.rb
module IngredientAmount

    #Load
    def load
        amounts.each do |amount|
            proxy_association.target << amount
        end
    end

    #Private
    private

    #Amounts
    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)
    end

    #Target
    def target_collection
        #load_target
        proxy_association.target
    end

end

This should append the amount attribute to your ingredient objects now, allowing you to perform:

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

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

#2


25  

In this case, you should loop through the meal_ingredients association. You should eager load the ingredients association to reduce db queries.

在这种情况下,您应该遍历meal_ingredients协会。您应该急于加载成分关联以减少数据库查询。

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

UPDATE

UPDATE

This update came after Rich Peck's answer but I think there's a simpler way to achieve what he did.

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

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

#1


22  

Ahhh the good old how do I access my extra join table attributes question. Struggled with this for MONTHS until we came up with a solution

啊,好老我如何访问我的额外连接表属性问题。我们为MONTHS苦苦挣扎,直到我们提出解决方案

--

-

ActiveRecord Association Extensions

ActiveRecord Association Extensions

The problem you have is that Rails will just use the foreign_keys in your join table to load the associative data you need. Unless you actually load the join model directly, it won't give you the ability to access the join attributes

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

Some foraging lead us to ActiveRecord Association Extensions - a way to access the intermediary data in between different ActiveRecord Associations (using a collection called proxy_association). This will allow you to access the extra attributes from the join model, appending them to your "original" model:

一些觅食引导我们进入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, through: :meal_ingredients, extend: IngredientAmount
end

#app/models/concerns/ingerdient_amount.rb
module IngredientAmount

    #Load
    def load
        amounts.each do |amount|
            proxy_association.target << amount
        end
    end

    #Private
    private

    #Amounts
    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)
    end

    #Target
    def target_collection
        #load_target
        proxy_association.target
    end

end

This should append the amount attribute to your ingredient objects now, allowing you to perform:

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

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

#2


25  

In this case, you should loop through the meal_ingredients association. You should eager load the ingredients association to reduce db queries.

在这种情况下,您应该遍历meal_ingredients协会。您应该急于加载成分关联以减少数据库查询。

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

UPDATE

UPDATE

This update came after Rich Peck's answer but I think there's a simpler way to achieve what he did.

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

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