Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby on Rails has_many through association objects before save

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.

like image 491
Nick Avatar asked Mar 15 '12 21:03

Nick


2 Answers

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

like image 125
matiasdh Avatar answered Nov 15 '22 19:11

matiasdh


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 Items is invalid.

like image 43
Markus Avatar answered Nov 15 '22 21:11

Markus