yep - the magazine should have the total_cost field (slip when I typed in the example). Also you're last suggestion is good, but I was trying to construct an easy example where it highlighted the multiple-model-spanning validation brick wall I'm at. So with this in mind, and assuming I use the "validate" approach (c.f. after_create hook), I still have the same question really? So an example of the issue is:
i) assuming there is a validation routine in both Magazine and Article to check business rules, that is: (a) for Magazine: Has to have an associated Article before successful validation & (b) for Article: Has to have an associated Magazine before succesful validation ii) issue is that if I create a Magazine, the validation is then hit and fails because I haven't yet created the Article (i.e. so they don't tie together) iii) if I create Article first to cover this then it fails because there isn't a Magazine yet iv) I could put the creation in a method like "create_magazine_article_pair" and remove these above-mentioned validation checks, however this wouldn't then protect against use of base methods (e.g. create/update/delete would still be available as part of ActiveRecord) Is there anyway out of this? i.e. to end up with a very solid data-access layer that doesn't allow for the business rules to be broken? thanks On Tue, Jan 13, 2009 at 1:11 PM, Andrew Timberlake < [email protected]> wrote: > On Tue, Jan 13, 2009 at 1:28 AM, Greg Hauptmann < > [email protected]> wrote: > >> Hi, >> >> QUESTION: How can establishing validation that spans multiple models be >> achieved in Rails? That is in such a fashion that it is not possible for a >> developer to break the validation via using any of the public model methods >> (e.g. update_attribute, save, create etc). > > > A developer can always break validation with something like > save_with_validation(false) > > >> >> >> EXAMPLE: >> * Concept: MAGAZINE can contain multiple ARTICLES, and an ARTICLE can be >> associated with multiple MAGAZINE (i.e. many to many). Cost of Magazine = >> Sum(Cost of Articles) >> * Tables: >> (a) magazines (has "cost" field) >> (b) articles_magazines (to map the many-to-many) >> (c) articles (has "total_cost" field) > > > Shouldn't the magazine have the total_cost field, and the article have a > cost field? > > >> >> >> BUSINESS RULE to be implemented: Not possible for a database update that >> would end up with a Magazine's "total_cost" not being equal to the >> Sum(associated articles "cost"s) >> >> ISSUES / QUESTIONS: >> (1) Assume would not try to implement rules at database constraint >> level??? >> (2) Use of Model "before_create" - but I'm assuming here if the Article is >> generated (Article.new), and then validation occurs, the code has NOT yet >> got to the bit where it updates the Magazine? >> (3) Use of "after_create" - Add a check for both Magazine and Article >> perhaps here, noting the database record has been created by transaction NOT >> finalised yet. So would the following be the best way: >> >> -----example------- >> class Magazine < ActiveRecord::Base >> after_save :business_rule_validation >> def business_rule_validation >> sum_of_articles = << INSERT code that calculates SUM of Articles costs >> for all articles that are associated with the Magazine >> >> errors.add_to_base("business rules fail") if self.total_cost != >> sum_of_articles >> end >> end > > > The after_save callback is not for validation - see > http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html > If you must validate use the validate method or and do your calculations > and validation in there. > > >> >> class Article < ActiveRecord::Base >> << Add Same Concept as per Magazine >> >> end >> -----example------- >> >> BUT wouldn't this fail, as it assumes the Article create/update/delete and >> the Magazine create/update/delete is in the SAME transaction no??? Does >> this mean you really have to create an overarching facade that handles >> creates/updates/deletes for Article/Magazines and somehow hide the normal >> per model save/update/delete??? >> >> -- >> Greg >> http://blog.gregnet.org/ >> >> >> >> >> > Instead of storing the total cost and doing all this validation, I'd > calculate the magazine total_cost as needed > class Magazine < ActiveRecord::Base > def total_cost > articles.to_a.sum(&:cost) > end > end > > -- > Andrew Timberlake > http://ramblingsonrails.com > http://www.linkedin.com/in/andrewtimberlake > > "I have never let my schooling interfere with my education" - Mark Twain > > > > -- Greg http://blog.gregnet.org/ --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" 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-talk?hl=en -~----------~----~----~----~------~----~------~--~---

