问题
on a Ruby on Rails project I'm trying to access association objects on an ActiveRecord prior to saving everything to the database.
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
If I build out my object like so:
purchase = Purchase.new(item_ids: [1, 2, 3])
and try to save it the item_validation
method doesn't have the items collection populated yet, so even though items have been set set it doesn't get a chance to call the check_something
method on any of them.
Is it possible to access the items collection before my purchase model and association models are persisted so that I can run validations against them?
If I change my item_validation
method to be:
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
it seems to work the way I want it to, however I find it hard to believe that there is no way to directly access the items collection with rails prior to my purchase and associated records being saved to the database.
回答1:
Try to adding the argument inverse_of: in the has_many and belongs_to definitions. The inverse_of argument it's the name of the relation on the other model, For example:
class Post < ActiveRecord::Base
has_many :comments, inverse_of: :post
end
class Comment < ActiveRecord::Base
belongs_to :post, inverse_of: :comments
end
Don't forget to add it also on the other classes, such as PurchaseItem and Item
Hope it helps
回答2:
Remove your own item_ids=
method - rails generates one for you (see collection_singular_ids=ids). This might already solve your problem.
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
The second thing that comes in my mind looking at your code: Move the validation to the Item
class. So:
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
Your Purchase
record will also be invalid if one of the Item
s is invalid.
回答3:
Do you have documentation that indicates purchase = Purchase.new(item_ids: [1, 2, 3])
does what you're expecting?
To me that looks like you are just setting the non-database attribute 'item_ids' to an array (i.e. not creating an association).
Your Purchase model should not even have any foreign key columns to set directly. Instead there are entries in the purchase_items
table that have a purchase_id
and item_id
. To create a link between your purchase and the three items you need to create three entries in the joiner table.
What happens if you just do this instead?:
purchase = Purchase.new
purchase.items = Item.find([1,2,3])
回答4:
You can use model.associations = [association_objects]
and an Association Callback
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Association+callbacks
回答5:
I assume you can't access them because id
of Purchase
isn't available before record saved. But as you mention you have access to first level association purchase_items
, so you can extract all ids and pass them in where
for Item
:
items = Item.where(purchase_item_id: purchase_items.map(&:id))
来源:https://stackoverflow.com/questions/9728484/ruby-on-rails-has-many-through-association-objects-before-save