Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
243 views
in Technique[技术] by (71.8m points)

sql - Rails 4 Accessing Join Table Attributes

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?

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.

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.

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

Thank you in advance for any help!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Updated on 08/01/2020 - RPECK

Struggled with this for months until we found an appropriate solution:

--

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

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:

#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

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

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...