Rails 4 Dostęp Do Atrybutów Join Table

Mam has_many through join table setup for a recipe app where Ingredient and Meal connect through MealIngredient. W obrębie MealIngredient, mam meal_id, ingredient_id, i amount. Moje pytanie brzmi: jak Mogę uzyskać dostęp do kolumny amount?

W moim widoku przepisu, przeglądam składniki:

@meal.ingredients.each do |i|

Mogę uzyskać dostęp do Właściwości składnika, ale nie do ilości z MealIngredient rekordu przyłączenia.

Próbowałem użyć includes W zapytaniu robi @meal.ingredients.includes(:meal_ingredients), ale nie jestem pewien, jak uzyskać dostęp do amount w ramach wspomnianej pętli. Kiedy używam i.inspect, nie widzę żadnych odniesień do tabeli meal_ingredients.

Czy Jest jakiś sposób, aby uzyskać dostęp do zmiennej w tej pętli za pomocą i.amount?

Z góry dziękuję za wszelką pomoc!

Author: Justin, 2014-08-11

2 answers

Zaktualizowano dnia 08/01/2020-RPECK

Zmagaliśmy się z tym miesiącami, dopóki nie znaleźliśmy odpowiedniego rozwiązania:

--

Rozszerzenia Stowarzyszenia ActiveRecord

Problem polega na tym, że Rails po prostu użyje foreign_keys w tabeli Dołącz, aby załadować potrzebne dane asocjacyjne. Jeśli model join nie zostanie załadowany bezpośrednio, nie będzie on miał dostępu do atrybutów join

Niektóre żerowania prowadzą nas do ActiveRecord Association Extensions - sposób dostępu do danych pośredniczących pomiędzy różnymi asocjacjami ActiveRecord (za pomocą zbioru o nazwie proxy_association). Pozwoli to na dostęp do dodatkowych atrybutów z modelu join, dołączając je do "oryginalnego" modelu:

#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

To powinno teraz dołączyć atrybut amount do obiektów ingredient, pozwalając na wykonanie:

@meal = Meal.find 1
@meal.ingredients.each do |ingredient|
   ingredient.amount
end
 29
Author: Richard Peck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-08 11:35:24

W tym przypadku należy wykonać pętlę przez skojarzenie meal_ingredients. Powinieneś wczytać asocjację ingredients, aby zmniejszyć zapytania db.

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

UPDATE

Ta aktualizacja pojawiła się po odpowiedzi Rich ' a Pecka, ale myślę, że jest prostszy sposób, aby osiągnąć to, co zrobił.

@meal.ingredients.select('ingredients.*, meal_ingredients.amount').each do |ingredient|
  puts ingredient.amount
  puts ingredient.name
end
 27
Author: jvnill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-11 16:36:15