BTW - I've had an indication after posting on a mysql forum site that MySql
does not provide a "deferred constraints" feature, whilst apparently Oracle
and Postgres do. From what I gather this would have been useful as I could
have put the business logic check in the database I think, to be only run
after all statements had occurred, but before the final commit.
Implication is that MySql won't provide the solution so it would have to be
in Rails if at all.
At this stage I'd be happy to assume I can not get a fully robust solution
to cross-model validation checks in Rails. What at least would be good is
to be able to get to the concept that:
1. Data Access Layer - That is model classes and base ActiveRecord
methods for models: These do not provide any protection themselves. If a
developer uses the normal ActiveRecord calls (e.g. save, update etc) then
it's up to them to get it right, however they would be encouraged not to use
this layer directly but use the "Service Layer"
2. Service Layer - Provides methods to use the models/tables that have
cross-model business rules. Basically these are the "trusted" methods that
will respect the business rules (noting it's not possible it seems to have
Rails provide the robust solution). For example:
- update_chapters - would update all chapters first (no model
validation calls would be firing), then at the end perform the business
logic check
3. Controller Layer - calls the service layer
Implication here is that one may not be able to use the normal tools like
ActiveScaffold which automatically gives you maintenance pages for all the
models, as it would be hooking into the Data Access Layer directly and
therefore wouldn't adhere to Business Logic check..
How does this sound? Probably the best I can do?
thanks
On Mon, Jan 26, 2009 at 7:36 AM, 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/
>
>
>
--
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
-~----------~----~----~----~------~----~------~--~---