Ruby on Rails has_many через объекты ассоциации перед сохранением

в проекте Ruby on Rails Я пытаюсь получить доступ к объектам ассоциации в ActiveRecord, прежде чем сохранять все в базу данных.

class Purchase < ActiveRecord::Base
 has_many :purchase_items, dependent: :destroy
 has_many :items, through: :purchase_items
 validate :item_validation
 def item_ids=(ids)
 ids.each do |item_id|
 purchase_items.build(item_id: item_id)
 end
 end
 private
 def item_validation
 items.each do |item|
 ## Lookup something with the item
 if item.check_something
 errors.add :base, "Error message"
 end
 end
 end
end

Если я построю свой объект так: purchase = Purchase.new(item_ids: [1, 2, 3]) и попытайтесь сохранить его, метод item_validation еще не заполняет коллекцию элементов, поэтому, хотя элементы были установлены, он не получает возможность вызвать метод check_something для любого из них.

Возможно ли получить доступ к коллекции элементов до того, как моя модель покупки и модели объединений будут сохранены, чтобы я мог выполнять проверки против них?

Если я изменю свой метод item_validation следующим образом:

def item_validation
 purchase_items.each do |purchase_item|
 item = purchase_item.item
 ## Lookup something with the item
 if item.something
 errors.add :base, "Error message"
 end
 end
end

похоже, работает так, как я этого хочу, однако мне трудно поверить, что нет способа прямого доступа к коллекции элементов с рельсами до того, как моя покупка и связанные записи будут сохранены в базе данных.

5 ответов

Удалите свой собственный метод item_ids= - рельсы генерируют его для вас (см. collection_singular_ids = ids). Это может решить вашу проблему.

class Purchase < ActiveRecord::Base
 has_many :purchase_items, dependent: :destroy
 has_many :items, through: :purchase_items
 validate :item_validation
 private
 def item_validation
 items.each do |item|
 ## Lookup something with the item
 if item.check_something
 errors.add :base, "Error message"
 end
 end
 end
end

Второе, что приходит мне в голову, глядя на ваш код: переместите проверку в класс Item. Итак:

class Purchase < ActiveRecord::Base
 has_many :purchase_items, dependent: :destroy
 has_many :items, through: :purchase_items
end
class Item < ActiveRecord::Base
 has_many :purchase_items
 has_many :purchases, through: :purchase_items
 validate :item_validation
 private
 def item_validation
 if check_something
 errors.add :base, "Error message"
 end
 end
end

Ваша запись Purchase также будет недействительной, если один из Item недействителен.


Попробуйте добавить аргумент inverse_of: в определениях has_many и belongs_to. Обратный аргумент - это имя отношения на другой модели, например:

class Post < ActiveRecord::Base
 has_many :comments, inverse_of: :post
end
class Comment < ActiveRecord::Base
 belongs_to :post, inverse_of: :comments
end

Не забудьте добавить его также и в другие классы, такие как PurchaseItem и Item

Надеюсь, что это поможет


Я предполагаю, что вы не можете получить к ним доступ, потому что id из Purchase недоступен до сохранения записи. Но, как вы говорите, у вас есть доступ к ассоциации первого уровня purchase_items, поэтому вы можете извлечь все идентификаторы и передать их в where для Item:

items = Item.where(purchase_item_id: purchase_items.map(&:id))


Вы можете использовать model.associations = [association_objects] и Association Callback http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Association+callbacks


Есть ли у вас документация, которая указывает, что purchase = Purchase.new(item_ids: [1, 2, 3]) выполняет то, что вы ожидаете?

Мне кажется, что вы просто устанавливаете атрибут non-database 'item_ids' в массив (т.е. не создаете связь).

В вашей модели покупки не должно быть никаких столбцов внешнего ключа, которые можно установить напрямую. Вместо этого в таблице purchase_items есть записи с purchase_id и item_id. Чтобы создать связь между вашей покупкой и тремя элементами, вам нужно создать три записи в таблице столов.

Что произойдет, если вы просто сделаете это вместо?:

purchase = Purchase.new
purchase.items = Item.find([1,2,3])

licensed under cc by-sa 3.0 with attribution.