thanks stephen - I think (haven't tested it) the problem here would still be called a line 1 (see below) of my use case. So whilst it would trigger validation for chapter in a non-looping sense, the problem is that until Chapter & the Allocation are saved to DB the validation will fail. Make sense? Hence why I was interested in an a "before_final_commit" type hook. --------------------------- 1 @b.save! # SEEMS TO TRIGGER BOOK AFTER_SAVE HERE RATHER THAN HOLDING OFF 2 @c.save! 3 @a1 = Allocation.create!(:book_id => @b.id, :chapter_id => @c.id, :amount => 100) ---------------------------
On Mon, Jan 26, 2009 at 7:14 AM, stephen paul suarez <[email protected]>wrote: > hmmm, i have an idea for you but i think it's a bit hacky.. i've put the > check on validation since i think it mostly relates to validating your > models..# ----------- Allocation --------------- > class Allocation < ActiveRecord::Base > belongs_to :book > belongs_to :chapter > > validate :check > > def check > #this will always be true.. this just ensures that > return @check if @check > @check = true > self.book.valid? > end > end > > # ----------- BOOK --------------- > class Book < ActiveRecord::Base > has_many :allocations > has_many :chapters, :through => :allocations > > validates_associated :allocations > validate :check > > def check > puts "BOOK: after_save" > #reload associated allocations always > sum = self.allocations(true).map(&:amount).sum > errors.add("amount","do NOT match") unless self.amount == sum > end > end > > > this is a bit tricky, since Allocation#save will try to call on > Book#valid?, the validates_associated will in turn try to call > Allocation#valid? once again, but the second time the code executes on > Allocation#check, the validation will just return true.. hth > > --stephen > > > On Sun, Jan 25, 2009 at 1:35 PM, Greg Hauptmann < > [email protected]> wrote: > >> Hi Matt/all, >> Actually from what I can see Rails does not hold off on trigger it's >> "after_save" callbacks until just before commit in the case they are wrapped >> in a specific transaction (unfortunately). So there's still no >> "before_commit" equivalent yet anyone has identified. Let me know if I'm >> wrong however here's the test I've run. >> >> >> ------------------- test output >> ------------------------------------------------- >> Macintosh-2:after_create_test greg$ spec >> spec/model/with_transaction_block.rb >> BOOK: before_save >> F >> >> 1) >> RuntimeError in 'Book should allow creation of book-allocation-chapter if >> costs match' >> amounts do NOT match >> ./spec/model/with_transaction_block.rb:27:in `after_save_check' >> ./spec/model/with_transaction_block.rb:57: >> ./spec/model/with_transaction_block.rb:56: >> >> Finished in 0.048817 seconds >> >> 1 example, 1 failure >> Macintosh-2:after_create_test greg$ >> >> --------------- spec >> ------------------------------------------------------------------ >> require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') >> >> # ------------ ALLOCATION ------------- >> class Allocation < ActiveRecord::Base >> belongs_to :book >> belongs_to :chapter >> >> before_save :after_save_check >> def after_save_check >> puts "ALLOCATION: before_save" >> b = self.book >> sum = b.allocations.sum(:amount) >> raise("amounts do NOT match") if !(b.amount == sum) >> end >> end >> >> # ----------- BOOK --------------- >> class Book < ActiveRecord::Base >> has_many :allocations >> has_many :chapters, :through => :allocations >> >> before_save :after_save_check >> def after_save_check >> puts "BOOK: before_save" >> sum = self.allocations.sum(:amount) >> raise "amounts do NOT match" if !(self.amount == sum) >> end >> >> end >> # ----------- CHAPTER --------------- >> class Chapter < ActiveRecord::Base >> has_many :allocations >> has_many :books, :through => :allocations >> >> before_save :after_save_check >> def after_save_check >> puts "CHAPTER: before_save" >> end >> >> end >> >> # --------- RSPEC (BOOK) ------------ >> describe Book do >> before(:each) do >> @b = Book.new(:amount => 100) >> @c = Chapter.new() >> end >> >> it "should allow creation of book-allocation-chapter if costs match" do >> Book.transaction do >> @b.save! # SEEMS TO TRIGGER BOOK AFTER_SAVE HERE RATHER THAN >> HOLDING OFF >> @c.save! >> @a1 = Allocation.create!(:book_id => @b.id, :chapter_id => @c.id, >> :amount => 100) >> end >> end >> >> end >> >> -------mysql log >> ----------------------------------------------------------------- >> 090125 15:31:46 1534 Connect r...@localhost on >> after_create_test_test >> 1534 Query SET SQL_AUTO_IS_NULL=0 >> 1534 Statistics >> 1534 Query SHOW FIELDS FROM `books` >> 1534 Query SHOW FIELDS FROM `chapters` >> 1534 Query BEGIN >> 1534 Query SHOW FIELDS FROM `allocations` >> 1534 Query SELECT sum(`allocations`.amount) AS >> sum_amount FROM `allocations` WHERE (`allocations`.book_id = NULL) >> 1534 Query ROLLBACK >> 1534 Quit >> >> >> >> Is my analysis correct? >> >> Cheers >> Greg >> >> On Sun, Jan 25, 2009 at 10:08 AM, Matt Jones <[email protected]> wrote: >> >>> >>> There's no explicit hook, but you can pretty much do what you've >>> described using transactions. >>> If you're updating the chapters in a single controller action, you can >>> use a transaction block >>> (see >>> http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html >>> ) >>> to wrap all >>> the changes. Then, either use an after_save on Book, or just call a >>> method directly to validate the >>> combination. >>> >>> You'll want to use save! and friends within the block, and catch >>> exceptions (ActiveRecord::RecordInvalid and >>> ActiveRecord::RecordNotSaved) to display errors. >>> >>> --Matt Jones >>> >>> >>> On Jan 24, 2009, at 6:44 PM, Greg Hauptmann wrote: >>> >>> > Hi Mike, all >>> > >>> > Understood. To help align my fictitious example to the cross-model >>> > validation question I've asked consider that: >>> > (a) the book value is fixed [e.g. perhaps think of this as a bank >>> > account transaction amount, being allocated out to different tax >>> > categories & then the user wants to adjust the tax categories] >>> > (b) the user manually adjusts the chapter value (i.e. there is no >>> > programmatic approach to calculating the distribution) >>> > >>> > So this brings it back to my scenario I'm not sure how to solve in >>> > Rails whereby the sequence of events here would be: >>> > - change chapter 1 value >>> > - change chapter 2 value >>> > - change chapter 3 value >>> > - <only at this point should the cross model business rule be >>> > checked, i.e. Book.amount.should == Sum(chapter values)> >>> > >>> > My assumption here (correct me if I'm wrong) is that any Rails >>> > validation/after_save/observer kicks in at such of the sequence >>> > points, whereas what is actually required here is a cross_model >>> > business logic check at the end. >>> > >>> > Does this make sense? Is there a ways in Rails to get access to a >>> > "before_commit" type hook that would align with the point I want the >>> > business logic check to kick in? >>> > >>> > Thanks >>> > >>> >>> >>> >>> >> >> >> -- >> 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 -~----------~----~----~----~------~----~------~--~---
