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

Reply via email to