That's why I was so confused.  There's no reason to expect @b.save! to hold
off validations.  That's the whole point calling save.  If you want, you can
build out the objects a la @b.allocations.build(options) and then the save
will also save the new allocations.  So you may be able to do something
like:

@b = Book.new(book_options)
@c = Chapter.new(chap_options)
@c.save!
@a = @b.allocations.build(alloc_options)

@b.save  # which will save the allocations too

You could also simplify things by creating a workflow where the user could
not change both models in one action.  If you're trying to be railsful and
restful, changing the book value in one action, and then subsequently
changing the chapter values in successive actions may be the way to go.


On Sun, Jan 25, 2009 at 4:36 PM, Greg Hauptmann <
[email protected]> wrote:

> 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
-~----------~----~----~----~------~----~------~--~---

Reply via email to