Подтвердить что ты не робот

Rails 4 Доступ к атрибутам таблицы соединений

У меня есть таблица настройки таблицы has_many through для приложения рецепта, где Ingredient и Meal соединяются через MealIngredient. В пределах MealIngredient у меня есть meal_id, ingredient_id и amount. Мой вопрос: как я могу получить доступ к столбцу amount?

В моем рецепте я прохожу через ингредиенты:

@meal.ingredients.each do |i|

Я могу получить доступ к свойствам ингредиента, но не количеству из записи объединения MealIngredient.

Я попытался использовать includes в запросе, выполняющем @meal.ingredients.includes(:meal_ingredients), но я не уверен, как получить доступ к amount в вышеупомянутом цикле. Когда я использую i.inspect, я вообще не вижу ссылок на таблицу meal_ingredients.

Есть ли способ получить доступ к переменной внутри этого цикла, используя i.amount?

Заранее благодарю за помощь!

4b9b3361

Ответ 1

Ahhh старый добрый вопрос how do I access my extra join table attributes. Борясь с этим за МЕСЯЦ, пока мы не придумали решение

-

Расширения ассоциации ActiveRecord

Проблема заключается в том, что 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, 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

Теперь это должно добавить атрибут amount к вашим объектам ingredient, что позволит вам выполнить:

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

Ответ 2

В этом случае вы должны пройти через ассоциацию meal_ingredients. Вы должны с нетерпением загрузить ассоциацию ingredients, чтобы уменьшить запросы db.

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

UPDATE

Это обновление появилось после ответа Rich Peck, но я думаю, что есть более простой способ добиться того, что он сделал.

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