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

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