I think I'm a little confused about this. Can you explain again what you are trying to do?
So the rule is that the sum of allocations for a book must equal a total. That sounds like a validation. My code would look like class Book validate :sum_of_allocation_equals_amount def sum_of_allocation_equals_amount error.add(:amount,"Does not match sum of allocations") unless allocations.sum_of_price == amount end end Then, you can create a book book = Book.new(:amount=>20) #and add allocations book.allocations.build(:amount=>10) book.allocations.build(:amount=>5) book.allocations.build(:amount=>5) # and then save the book, which I believe will save the allocations book.save Does that not work? Mike On Jan 23, 2009, at 3:48 PM, Greg Hauptmann wrote: > PS. Some additional clarification: > • Goal is to understand whether I can/should attempt to model a > business_rule that cuts across multiple models in Rails > • I think the given is that to carry out a user scenario (e.g. > adding more chapters to a book) would result in having to carry out > multiple steps at a per model level (e.g. adjust allocation of > amount across chapter for their authors), and that during these > model changes the Business Rule would have be violated, however by > the time you've finished the Business Rule should have been adhered > to. > • I was really hoping to have a way to do this that didn't break > the normal usage of Rails models, but at the same time if you did > jump in and try to make one isolated change on one model (e.g. add a > new chapter for a book without ensuring all the chapter costs were > adjusted to equal the book cost), the Business Rule code would kick > in and pull you up with an exception. > • The only way I can see to handle this in the KISS (keep it simple > stupid) fashion would be to be able to get some sort of > "before_commit" hook from Rails, where I ideally it would give you > the models that have changed in this hook, so you do cross-business > rule sanity check. So the check here would ultimately be database > focused (i.e. check against what is in the database) > • I've thought about doing the check just at object level at > "before_save" point, however there seem to be gotchas here. > Hope that makes sense. Perhaps this is just not-possible in Rails > currently and I should just assume I have to be very careful with > all my code, because there won't be that cross-model validation > check there to save me. One reason to have it in place too by the > way is that I could leverage a front-end frame work like > ActiveScaffold and not have to worry about the fact it would give a > user the ability to change one particular row without that cross- > model business logic check kicking in. > > > On Fri, Jan 23, 2009 at 11:41 AM, Greg Hauptmann > <[email protected] > > wrote: > Hi, (no luck on the user forum so I'm hoping I can ask here) > > I'm trying to get a simple cross-model business rule working. In > this case the rule is (see below for models overview): > * Rule = Sum(allocations amount, for a book) = Book Amount > > ISSUE: The issue is in using after_create is that either the book or > allocation is saved before the other. The only way I can see to > make this work is to have a check just prior to COMMIT, where all > records are visible within the DB and your final checks can be run > (and rolled back if there is problem). Hence my question: > > QUESTION: Is there an "before_commit" hook somewhere in Rails? (or > how else would I satisfy my requirement) > > > ---------------------------------------------------------------------------------- > Macintosh-2:after_create_test greg$ spec spec/model/ > all_in_one_test_spec.rb > ============= NEW TEST ====================== > BOOK: after_save > F============= NEW TEST ====================== > CHAPTER: after_save > .============= NEW TEST ====================== > BOOK: after_save > . > > 1) > RuntimeError in 'Book should save if allocation amount == book amount' > amounts do NOT match > ./spec/model/all_in_one_test_spec.rb:26:in `after_save_check' > ./spec/model/all_in_one_test_spec.rb:51: > > Finished in 0.061092 seconds > > 3 examples, 1 failure > ---------------------------------------------------------------------------------- > > Macintosh-2:after_create_test greg$ cat -n spec/model/ > all_in_one_test_spec.rb > 1 require File.expand_path(File.dirname(__FILE__) + '/../ > spec_helper') > 2 > 3 # ------------ ALLOCATION ------------- > 4 class Allocation < ActiveRecord::Base > 5 belongs_to :book > 6 belongs_to :chapter > 7 > 8 after_save :after_save_check > 9 def after_save_check > 10 puts "ALLOCATION: after_save" > 11 b = self.book > 12 sum = b.allocations.collect{|i| i.amount}.inject(0){| > sum, n| sum + n } > 13 raise("amounts do NOT match") if !(b.amount == sum) > 14 end > 15 end > 16 > 17 # ----------- BOOK --------------- > 18 class Book < ActiveRecord::Base > 19 has_many :allocations > 20 has_many :chapters, :through => :allocations > 21 > 22 after_save :after_save_check > 23 def after_save_check > 24 puts "BOOK: after_save" > 25 sum = self.allocations.collect{|i| i.amount}.inject(0){| > sum, n| sum + n } > 26 raise "amounts do NOT match" if !(self.amount == sum) > 27 end > 28 > 29 end > 30 # ----------- CHAPTER --------------- > 31 class Chapter < ActiveRecord::Base > 32 has_many :allocations > 33 has_many :books, :through => :allocations > 34 > 35 after_save :after_save_check > 36 def after_save_check > 37 puts "CHAPTER: after_save" > 38 end > 39 > 40 end > 41 > 42 # --------- RSPEC (BOOK) ------------ > 43 describe Book do > 44 before(:each) do > 45 puts "============= NEW TEST ======================" > 46 @b = Book.new(:amount => 100) > 47 @c = Chapter.new() > 48 end > 49 > 50 it "should save if allocation amount == book amount" do > 51 @b.save! > 52 @c.save! > 53 Allocation.create!(:book_id => @b.id, :chapter_id => > @c.id, :amount => 100) > 54 end > 55 > 56 it "should raise database exception if try to save > allocation prior to book" do > 57 lambda { > 58 @c.save! > 59 Allocation.create!(:book_id => @b.id, :chapter_id => > @c.id, :amount => 100) > 60 @b.save! > 61 }.should raise_error > 62 end > 63 > 64 it "should raise error if allocation amount != book > amount" do > 65 lambda { > 66 @b.save! > 67 @c.save! > 68 Allocation.create!(:book_id => @b.id, :chapter_id => > @c.id, :amount => 90) > 69 }.should raise_error > 70 end > 71 > 72 > 73 end > 74 > ---------------------------------------------------------------------------------- > ActiveRecord::Schema.define(:version => 20090123000614) do > > create_table "allocations", :force => true do |t| > t.integer "book_id", :null => false > t.integer "chapter_id", :null => false > t.integer "amount", :null => false > t.datetime "created_at" > t.datetime "updated_at" > end > > create_table "books", :force => true do |t| > t.integer "amount", :null => false > t.datetime "created_at" > t.datetime "updated_at" > end > > create_table "chapters", :force => true do |t| > t.datetime "created_at" > t.datetime "updated_at" > end > > end > ---------------------------------------------------------------------------------- > > > > > > > > > > > > > -- > Greg > http://blog.gregnet.org/ > > > > > > -- > Greg > http://blog.gregnet.org/ > > > > > --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group. To post to this group, send email to [email protected] To unsubscribe from this group, send email to [email protected] For more options, visit this group at http://groups.google.com/group/rubyonrails-core?hl=en -~----------~----~----~----~------~----~------~--~---
