Hi. I''m evaluating Rails for a project. One of the requirements that is placed, is that we must be able to version data (for change history). Normally I''d do PK(id, version_id) and define version_id = 0 to be the current record, and then bump the version_id when the record gets updated. Using ActiveRecord, this approach is no good. So I''m considering implementing it via single table inheritance. In this I could have an Item and a VersionedItem, where VersionedItem uses 2 extra attributes, namely version and item_id which refers to the parent id. Is it okay to have eg.: class Item < ActiveRecord::Base has_many :versioned_items end class VersionedItem < Item belongs_to :item end I would have to adjust a few finder methods to ensure I get the proper version, but other than that, any reason the above would not work? Suggestions for other approaches much appreciated. Br, Morten
On Tue, 22 Feb 2005 23:27:22 +0100, Morten <lists-Mr43XxmkdKzQT0dZR+AlfA@public.gmane.org> wrote:> > Hi. I''m evaluating Rails for a project. One of the requirements that is > placed, is that we must be able to version data (for change history). > > Normally I''d do PK(id, version_id) and define version_id = 0 to be the > current record, and then bump the version_id when the record gets updated. > > Using ActiveRecord, this approach is no good. So I''m considering > implementing it via single table inheritance. In this I could have an > Item and a VersionedItem, where VersionedItem uses 2 extra attributes, > namely version and item_id which refers to the parent id. Is it okay to > have eg.: > > class Item < ActiveRecord::Base > has_many :versioned_items > end > > class VersionedItem < Item > belongs_to :item > end > > I would have to adjust a few finder methods to ensure I get the proper > version, but other than that, any reason the above would not work? > Suggestions for other approaches much appreciated. > > Br, > > MortenLooks good to me... Dunno how else you''d do it.
On Tue, 22 Feb 2005 23:27:22 +0100, Morten <lists-Mr43XxmkdKzQT0dZR+AlfA@public.gmane.org> wrote:> > Hi. I''m evaluating Rails for a project. One of the requirements that is > placed, is that we must be able to version data (for change history). > > Normally I''d do PK(id, version_id) and define version_id = 0 to be the > current record, and then bump the version_id when the record gets updated. > > Using ActiveRecord, this approach is no good. So I''m considering > implementing it via single table inheritance. In this I could have an > Item and a VersionedItem, where VersionedItem uses 2 extra attributes, > namely version and item_id which refers to the parent id. Is it okay to > have eg.: > > class Item < ActiveRecord::Base > has_many :versioned_items > end > > class VersionedItem < Item > belongs_to :item > end > > I would have to adjust a few finder methods to ensure I get the proper > version, but other than that, any reason the above would not work? > Suggestions for other approaches much appreciated.If the concept of an item inherently *has* a version in your domain, I''d model it accordingly rather than try to hide it in the database. If your application doesn''t need to do *anything* with these older version (even just display) then perhaps you can get away with using a trigger and an audit table (very common ''pattern'' in banking). Item and VersionedItem is certainly *not* a place where you''d want to use inheritance. Taking a look at ''Coad''s Rules'' (http://netobjectivesgroups.com/eve/ubb.x/a/tpc/f/9556003502/m/8456007902) it fails the first test, as VersionedItem isn''t a special kind of item. I find that overuse of inheritance tends to lead to brittle tightly-coupled applications, so dodge it where you can.> Br, > > Morten > > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails >-- Cheers Koz
> Hi. I''m evaluating Rails for a project. One of the requirements that > is placed, is that we must be able to version data (for change > history).Change history is best implemented as an audit responsibility driven by observers. Pseudo code: class Account < ActiveRecord::Base end class Trace < ActiveRecord::Base end class Auditor < ActiveRecord::Observer observes :account def before_save(account) Trace.create("balance" => account.balance, "traced_at" => Time.now) end end So you have an Auditor that create Trace objects to log what''s going on with Accounts. -- David Heinemeier Hansson, http://www.basecamphq.com/ -- Web-based Project Management http://www.rubyonrails.org/ -- Web-application framework for Ruby http://www.loudthinking.com/ -- Broadcasting Brain
Hi Morten and others, I am going to have to do versioning myself in a couple of days. Here is what I have thought so far. I need versions for my Pages in a CMS. I would be glad to get your input on this. * Everytime the user hits the save page button a new version is created. * To minimize the impact I have on read queries and the overall size of the `pages` table, I would like to move every versioned Page into a seperate `version` table. That way I can reuse the `id` of the page. Otherwise I would have to come up with some other numbering scheme - class Version < ARB; has_many :pages; end * When the user requests an older version (to look at), it will just flip the table names internally. The current page always stays in the pages table. * When the user recovers a version, a new version is simply created and the current page is updated with the requested version. * I would maybe have to validate that there are changes between versions, so that the user cannot hit the save button a thousand times with 1000 new copies in the database. This is what I have come up with when thinking very shortly about the problem so it might lack a certain deepness of thought. It should be the simplest thing that could ... I have also by accident (I think it was on #rubyonrails) come across a comment that suggested to look into Tobias Luetke''s Hieraki (www.hieraki.org). They say that he has a nice scheme for versioning. I haven''t looked at it myself, will definately do so tommorow. Probably here: http://dev.hieraki.org/trac.cgi/file/trunk/app/models/page.rb and here: http://dev.hieraki.org/trac.cgi/file/trunk/app/models/page_revision.rb Greetings Sascha Ebach
On Wed, 23 Feb 2005 00:59:30 +0100, David Heinemeier Hansson <david-OiTZALl8rpK0mm7Ywyx6yg@public.gmane.org> wrote:> > Hi. I''m evaluating Rails for a project. One of the requirements that > > is placed, is that we must be able to version data (for change > > history). > > Change history is best implemented as an audit responsibility driven by > observers. Pseudo code:Damn, observers are cool! If you''re just tracking the history of these things for audit purposes, david''s solution kicks the crap out of the ''triggers'' option I mentioned before. However, if you''re actually *doing* things with this history (i.e. revert to version 2) I''d still ensure your object model includes this concept.> class Account < ActiveRecord::Base > end > > class Trace < ActiveRecord::Base > end > > class Auditor < ActiveRecord::Observer > observes :account > > def before_save(account) > Trace.create("balance" => account.balance, "traced_at" => Time.now) > end > end > > So you have an Auditor that create Trace objects to log what''s going on > with Accounts. > -- > David Heinemeier Hansson, > http://www.basecamphq.com/ -- Web-based Project Management > http://www.rubyonrails.org/ -- Web-application framework for Ruby > http://www.loudthinking.com/ -- Broadcasting Brain > > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails >-- Cheers Koz
On 23.2.2005, at 02:05, Sascha Ebach wrote:> > - class Version < ARB; has_many :pages; endI''d say class Version < ActiveRecord::Base belongs_to :page end But you probably meant that ;-) //jarkko> --Jarkko Laine http://jlaine.net http://odesign.fi _______________________________________________ Rails mailing list Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails
> > > Hi. I''m evaluating Rails for a project. One of the requirements that > > > is placed, is that we must be able to version data (for change > > > history). > > > > Change history is best implemented as an audit responsibility driven by > > observers. Pseudo code: > > Damn, observers are cool! > > If you''re just tracking the history of these things for audit > purposes, david''s solution kicks the crap out of the ''triggers'' > option I mentioned before.There''ll be no need to revert to previous versions or update those, once saved, data is immutable. There is a need for people to be able to scroll through revisions to see what changed when. Thanks for the good input to all. Br, Morten
Jarkko Laine schrieb:> On 23.2.2005, at 02:05, Sascha Ebach wrote: > >> >> - class Version < ARB; has_many :pages; end > > > I''d say > > class Version < ActiveRecord::Base > belongs_to :page > end > > But you probably meant that ;-) >ah, yeah, still mixing those up sometimes ... thx Sascha
On 23-Feb-2005, at 05:11, Morten wrote:> >>>> Hi. I''m evaluating Rails for a project. One of the requirements that >>>> is placed, is that we must be able to version data (for change >>>> history). >>> >>> Change history is best implemented as an audit responsibility driven >>> by >>> observers. Pseudo code: >> >> Damn, observers are cool! >> >> If you''re just tracking the history of these things for audit >> purposes, david''s solution kicks the crap out of the ''triggers'' >> option I mentioned before. > > There''ll be no need to revert to previous versions or update > those, once saved, data is immutable. There is a need for people to be > able to scroll through revisions to see what changed when. > >This discussion raises a question I''m working on. Let''s take some tables from my project for example: Customers <--->> Programs <--->> Parts Where <- is belongs_to, and ->> is has_many. When you need to save a version of, say, Parts, you can''t just copy the object/row somewhere. If you do, then when the Program for part.program_id changes, the archived part is no longer accurate. It seems that I must save the object/row, and copies of all other objects/rows that are related to it at the time. That could lead to a monster database! Consider a has_and_belongs_to_many relation: customer#1 has program#2, program#5, program#8 program#2 has part#5, part#2, part#6 program#5 has part#5, part#3, part#7 program#8 has part#1, part#9, part#4 If I snapshot part#6, I also have to save part#2, part#5, program#2, customer#1, program#5 (shares part#5), part#3 and part#7. It''s enough to make you tell them to print it out and put it in a notebook! Hm, I could generate a PDF and store that... Thanks, JJ
On Wed, 23 Feb 2005, John Johnson wrote:> This discussion raises a question I''m working on. Let''s take some tables from > my project for example: > > Customers <--->> Programs <--->> Parts > > Where <- is belongs_to, and ->> is has_many. > > When you need to save a version of, say, Parts, you can''t just copy the > object/row somewhere. If you do, then when the Program for part.program_id > changes, the archived part is no longer accurate. It seems that I must save > the object/row, and copies of all other objects/rows that are related to it > at the time. That could lead to a monster database! Consider a > has_and_belongs_to_many relation: > > customer#1 has program#2, program#5, program#8 > program#2 has part#5, part#2, part#6 > program#5 has part#5, part#3, part#7 > program#8 has part#1, part#9, part#4 > > If I snapshot part#6, I also have to save part#2, part#5, program#2, > customer#1, program#5 (shares part#5), part#3 and part#7. > > It''s enough to make you tell them to print it out and put it in a notebook! > > Hm, I could generate a PDF and store that... > > Thanks, > JJnot that you will use this api, but you might find this information useful http://www.codeforpeople.com/lib/ruby/btpgsql/ specifically http://www.codeforpeople.com/lib/ruby/btpgsql/btpgsql-0.2.4/doc/ it address these, and more, issues. HTH. -a -- ==============================================================================| EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov | PHONE :: 303.497.6469 | When you do something, you should burn yourself completely, like a good | bonfire, leaving no trace of yourself. --Shunryu Suzuki ===============================================================================
The "ActiveRecord::Mixins::Touch" would partially handle this situation (I know that is not specified this way anymore). I wonder if there is a way to make ActiveRecord take into account whether or not a given find_ should include the "current timestamp" as part of the SQL when retrieving from the DB? On Wed, 23 Feb 2005 10:17:12 -0700 (MST), Ara.T.Howard <Ara.T.Howard-32lpuo7BZBA@public.gmane.org> wrote:> On Wed, 23 Feb 2005, John Johnson wrote: > > > This discussion raises a question I''m working on. Let''s take some tables from > > my project for example: > > > > Customers <--->> Programs <--->> Parts > > > > Where <- is belongs_to, and ->> is has_many. > > > > When you need to save a version of, say, Parts, you can''t just copy the > > object/row somewhere. If you do, then when the Program for part.program_id > > changes, the archived part is no longer accurate. It seems that I must save > > the object/row, and copies of all other objects/rows that are related to it > > at the time. That could lead to a monster database! Consider a > > has_and_belongs_to_many relation: > > > > customer#1 has program#2, program#5, program#8 > > program#2 has part#5, part#2, part#6 > > program#5 has part#5, part#3, part#7 > > program#8 has part#1, part#9, part#4 > > > > If I snapshot part#6, I also have to save part#2, part#5, program#2, > > customer#1, program#5 (shares part#5), part#3 and part#7. > > > > It''s enough to make you tell them to print it out and put it in a notebook! > > > > Hm, I could generate a PDF and store that... > > > > Thanks, > > JJ > > not that you will use this api, but you might find this information useful > > http://www.codeforpeople.com/lib/ruby/btpgsql/ > > specifically > > http://www.codeforpeople.com/lib/ruby/btpgsql/btpgsql-0.2.4/doc/ > > it address these, and more, issues. > > HTH. > > -a > -- > ==============================================================================> | EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov > | PHONE :: 303.497.6469 > | When you do something, you should burn yourself completely, like a good > | bonfire, leaving no trace of yourself. --Shunryu Suzuki > ==============================================================================> _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails >
Ara.T.Howard wrote:> not that you will use this api, but you might find this information useful > http://www.codeforpeople.com/lib/ruby/btpgsql/ > it address these, and more, issues.Ara, very nice lib. Check out Fowler''s _Analysis Patterns_ for a fascinating (and perhaps over-abstract) investigation of temporal patterns: http://www.martinfowler.com/ap2/ See http://www.martinfowler.com/ap2/timeNarrative.html in particular. jeremy
On Wed, 23 Feb 2005, Jeremy Kemper wrote:> Ara.T.Howard wrote: >> not that you will use this api, but you might find this information useful >> http://www.codeforpeople.com/lib/ruby/btpgsql/ >> it address these, and more, issues. > > Ara, very nice lib. Check out Fowler''s _Analysis Patterns_ for a > fascinating (and perhaps over-abstract) investigation of temporal > patterns: http://www.martinfowler.com/ap2/ > > See http://www.martinfowler.com/ap2/timeNarrative.html in particular.ah - i remeber reading this when i was writing that library (my first ruby project) and it is very useful. this problem domain is so slippery and tricky : it would be great to have a rails helper for it. if anyone is interested feel free to contact me - i won''t have time to code anything for months, but do have a lot of experience in this area and contribute that now. kind regards. -a -- ==============================================================================| EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov | PHONE :: 303.497.6469 | When you do something, you should burn yourself completely, like a good | bonfire, leaving no trace of yourself. --Shunryu Suzuki ===============================================================================
Hi, I''m following up from an old thread about using Observers to do audit trails. On Wed, 2005-02-23 at 00:59 +0100, David Heinemeier Hansson wrote:> Change history is best implemented as an audit responsibility driven by > observers. Pseudo code: > > class Account < ActiveRecord::Base > end > > class Trace < ActiveRecord::Base > end > > class Auditor < ActiveRecord::Observer > observes :account > > def before_save(account) > Trace.create("balance" => account.balance, "traced_at" => Time.now) > end > end > > So you have an Auditor that create Trace objects to log what''s going on > with Accounts.I tried doing as per above by adding the following 2 classes to the app/models directory. class Trace < ActiveRecord::Base end class Auditor < ActiveRecord::Observer observes :jobgrade def before_save(jobgrade) Trace.new("user_id" => @session[''user''].id, ''item_id'' => jobgrade.id) end end However, nothing happens when I update a jobgrade object. Is app/models the correct place to put the auditor.rb file? Is there something else I need to do to make the above observer pattern work? I also changed the "observes :jobgrade" to "observe Jobgrade" in the above Auditor class in accordance with the documentation for ActiveRecord::Observer (http://rails.rubyonrails.com/classes/ActiveRecord/Observer.html) but that didn''t work too. I''m using rails 0.9.5 and ruby1.8. Any help much appreciated. cheers, mengkuan
Additional info: The table "traces" is created as follows: create table traces ( id int not null AUTO_INCREMENT, user_id int, item_type varchar(255), action varchar(255), item_id int, created_at datetime, constraint pk_traces primary key (id) ) ; Database is mysql version 4.1.9. On Wed, 2005-03-09 at 19:20 +0800, Meng Kuan wrote:> Hi, > > I''m following up from an old thread about using Observers to do audit > trails. > > On Wed, 2005-02-23 at 00:59 +0100, David Heinemeier Hansson wrote: > > Change history is best implemented as an audit responsibility driven by > > observers. Pseudo code: > > > > class Account < ActiveRecord::Base > > end > > > > class Trace < ActiveRecord::Base > > end > > > > class Auditor < ActiveRecord::Observer > > observes :account > > > > def before_save(account) > > Trace.create("balance" => account.balance, "traced_at" => Time.now) > > end > > end > > > > So you have an Auditor that create Trace objects to log what''s going on > > with Accounts. > > I tried doing as per above by adding the following 2 classes to the > app/models directory. > > > class Trace < ActiveRecord::Base > end > > class Auditor < ActiveRecord::Observer > observes :jobgrade > > def before_save(jobgrade) > Trace.new("user_id" => @session[''user''].id, ''item_id'' => > jobgrade.id) > end > > end > > However, nothing happens when I update a jobgrade object. Is app/models > the correct place to put the auditor.rb file? Is there something else I > need to do to make the above observer pattern work? > > I also changed the "observes :jobgrade" to "observe Jobgrade" in the > above Auditor class in accordance with the documentation for > ActiveRecord::Observer > (http://rails.rubyonrails.com/classes/ActiveRecord/Observer.html) but > that didn''t work too. > > I''m using rails 0.9.5 and ruby1.8. > > Any help much appreciated. > > cheers, > mengkuan > > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails >-- --------------------------------------------------------------- Privileged, confidential and/or copyright information may be contained in this e-mail. This e-mail is for the use only of the intended addressee. If you are not the intended addressee, or the person responsible for delivering it to the intended addressee, you may not copy, forward, disclose or otherwise use it or any part of it in any way whatsoever. To do so is prohibited and may be unlawful. If you receive this e-mail by mistake please advise the sender immediately by using the reply facility in your e-mail software. This message is subject to and does not create or vary any contractual relationship between Little Green Apples Pte Ltd (the Company) or any of its subsidiaries (comprising of LGA Technologies Pte Ltd; LGA International Pte Ltd; & LGA Telecom Pte Ltd) and you. By opening any attachment to this message, you also agree to accept the risk that it may contain a virus or damaging code, and you agree that the sender and/or the Company and its subsidiaries will not be liable for any loss or damage thereby caused. ---------------------------------------------------------------
> class Auditor < ActiveRecord::Observer > observes :jobgrade > > def before_save(jobgrade) > Trace.new("user_id" => @session[''user''].id, ''item_id'' => > jobgrade.id) > endTry this: t = Trace.new("user_id" => @session[''user''].id, ''item_id'' => jobgrade.id) t.save -- rick http://techno-weenie.net
> I''m following up from an old thread about using Observers to do audit > trails.Since hieraki is mentioned in this discussion: I actually changed hieraki recently to use an observer for versioning. page : http://dev.hieraki.org/trac.cgi/file/trunk/app/models/page.rb page_revision : http://dev.hieraki.org/trac.cgi/file/trunk/app/models/page_revision.rb and the observer : http://dev.hieraki.org/trac.cgi/file/trunk/app/models/page_observer.rb Its almost too easy -- Tobi http://www.snowdevil.ca - Snowboards that don''t suck http://www.hieraki.org - Open source book authoring http://blog.leetsoft.com - Technical weblog
Tobias Luetke wrote:> > > I''m following up from an old thread about using Observers to do audit > > trails. > > > Since hieraki is mentioned in this discussion: > > I actually changed hieraki recently to use an observer for versioning. > > page : http://dev.hieraki.org/trac.cgi/file/trunk/app/models/page.rb > page_revision : > http://dev.hieraki.org/trac.cgi/file/trunk/app/models/page_revision.rb > > and the observer : > http://dev.hieraki.org/trac.cgi/file/trunk/app/models/page_observer.rb > > Its almost too easyOne off-topic questions... In page.rb that you list above, the Page class contains these two lines: has_many :revisions, :class_name => "PageRevision", ... has_one :revision, :class_name => "PageRevision", ... Why do you have both? Curt
The has_one relation uses :order => "id DESC" so it conveniently gets me the latest revision. On Wed, 9 Mar 2005 08:16:25 -0600, Curt Hibbs <curt-fk6st7iWb8MAvxtiuMwx3w@public.gmane.org> wrote:> Tobias Luetke wrote: > > > > > I''m following up from an old thread about using Observers to do audit > > > trails. > > > > > > Since hieraki is mentioned in this discussion: > > > > I actually changed hieraki recently to use an observer for versioning. > > > > page : http://dev.hieraki.org/trac.cgi/file/trunk/app/models/page.rb > > page_revision : > > http://dev.hieraki.org/trac.cgi/file/trunk/app/models/page_revision.rb > > > > and the observer : > > http://dev.hieraki.org/trac.cgi/file/trunk/app/models/page_observer.rb > > > > Its almost too easy > > One off-topic questions... > > In page.rb that you list above, the Page class contains these two lines: > > has_many :revisions, :class_name => "PageRevision", ... > has_one :revision, :class_name => "PageRevision", ... > > Why do you have both? > > Curt > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails >-- Tobi http://www.snowdevil.ca - Snowboards that don''t suck http://www.hieraki.org - Open source book authoring http://blog.leetsoft.com - Technical weblog
On Wed, 2005-03-09 at 07:32 -0600, Rick Olson wrote:> > class Auditor < ActiveRecord::Observer > > observes :jobgrade > > > > def before_save(jobgrade) > > Trace.new("user_id" => @session[''user''].id, ''item_id'' => > > jobgrade.id) > > end > > Try this: > t = Trace.new("user_id" => @session[''user''].id, ''item_id'' => jobgrade.id) > t.saveThanks but that didn''t work for me. Do I have to upgrade rails to 0.10.1 from 0.9.5 which I''m currently using in order for the observer pattern to work?
On Thu, 2005-03-10 at 09:33 +0800, Meng Kuan wrote:> On Wed, 2005-03-09 at 07:32 -0600, Rick Olson wrote: > > > class Auditor < ActiveRecord::Observer > > > observes :jobgrade > > > > > > def before_save(jobgrade) > > > Trace.new("user_id" => @session[''user''].id, ''item_id'' => > > > jobgrade.id) > > > end > > > > Try this: > > t = Trace.new("user_id" => @session[''user''].id, ''item_id'' => jobgrade.id) > > t.save > > Thanks but that didn''t work for me. Do I have to upgrade rails to 0.10.1 > from 0.9.5 which I''m currently using in order for the observer pattern > to work?Finally got it to work. Found a clue in the following documentation: http://rails.rubyonrails.com/classes/ActionController/Dependencies/ClassMethods.html Basically I just have to add the following to my ApplicationController class (controllers/application.rb) definition: observer :auditor Now when I try to update a jobgrade entry the before_save code in auditor class gets executed. However, I now run into another problem. How do I get access to session variables (eg. the @session[''user''] object) within the Auditor Observer class? I get errors like the following: NoMethodError in Jobgrades#update undefined method `[]'' for nil:NilClass /app/models/auditor.rb:17:in `trace'' /app/models/auditor.rb:5:in `after_update'' /app/controllers/jobgrades_controller.rb:38:in `update'' cheers, mengkuan