kkurach / has_versioning

plugin which adds version control system to models AND associations, and also allows to pack many changes into one changelist
http://has_versioning.lighthouseapp.com/
Other
28 stars 0 forks source link

Copyright 2009 Google Inc.

Original author: Karol Kurach <kkurach (at) gmail (dot) com>

Licensed under the Apache License, Version 2.0 (the "License");

you may not use this file except in compliance with the License.

You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software

distributed under the License is distributed on an "AS IS" BASIS,

WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

See the License for the specific language governing permissions and

limitations under the License.

HasVersioning

This plugin adds version control to your's app models. It allows to:

Install

1.copy this plugin to yourapp/vendor/plugins.

2.add new migration, with those 2 lines:

Changelist.create_versioning_table Change.create_versioning_table

3.for each model, which you want to version, add 'has_versioning' to class definition, and and call to 'add_versioning_columns' in migration. so, if you have:

class User end

you need to change it to class User has_versioning end

and add "User.add_versioning_columns" into your migrations.

  1. add argument :versioned => true, for each association you want to version. both sides of association MUST have versioning.

Requirements

requires RAILS >= 2.3.3

to install rails 2.3.3, you may run following command in your app directory: rake rails:freeze:edge RELEASE=2.3.3

Example

let say we have following objects in our app:


class User has_many :cars has_many :dogs end

class Car has_one :engine belong_to :user end

class Engine belongs_to :car end

To make those models use versioning, we need to add 'has_versioning' in class definition. this will allow to query about attributes of this object in the past.

if we also need to remember history of has_many :cars assocition, we need to pass :versioned => true argument


class User has_versioning has_many :cars, :versioned => true has_many :dogs end

class Car belong_to :user, :versioned => true has_one :engine, :versioned => true end

class Engine has_versioning belong_to :car, :versioned => true end

now, following magic works:

user = User.new new_cl = Changelist.record!('added new user') do user.name = 'kkurach' user.save! end

user.version # => 1 user.name = 'nickesk' user.save! # changelist autocreated user.version # => 2

user.at_changelist(new_cl.id).name # => 'kkurach' user.at_version(1).name # => 'kkurach'

c1 = Car.create(:color => 'red')
c2 = Car.create(:color => 'blue')

cl_add1 = Changelist.record! { user.cars << c1 } cl_add2 = Changelist.record! { user.cars << c2 }

user.cars # => [c1, c2] user.cars.count # => 2 user.at_changelist(cl_add1.id).cars # => [c1] user.at_changelist(cl_add1.id).cars.count # => 1

user.at_changelist(cl_add2.id).cars.find(:all, :conditions => { :color => 'blue} ) # => [c2]

cl_engine = Changelist.record!('engine added') do c1.engine = Engine.create(:power => 123) end

c1.engine.power = 444

user.cars.first.engine.power # => 444

user.at_changelist(cl_engine.id+1).cars. first.engine.power # => 444

user.at_changelist(cl_engine.id+1).cars. first.at_changelist(cl_engine.id).engine.power # => 123

################ END OF EXAMPLE ##########################

Limitations

  1. plugin wasn't tested with non-standard naming ( different than rails default table names, foreign keys, etc...) so most probably it won't work in this case.

  2. it doesn't work with has_many :through and has_one :through (yet )

  3. queries which includes id field in conditions are forbidden after at_changelist(cl). so, for example this query will give wrong output:

User.find_by_name('karol').at_changelist(5).cars.find(:all, :conditions => { :id => 4..10 } )

but this is ok:

User.find_by_name('karol').at_changelist(5).cars.find(:all, :conditions => { :color => 'red' })

only conditions with primary key after at_changelist are BAD

  1. it will never work with has_and_belongs_to_many (or it will be hard and dirty), because it's impossible to set a callback on table in database. we need a model...

Special thanks to:

Nick Eskelinen - for an idea of writing this plugin, help with starting it, solving tons of my problems & much more

Joel Votaw - for being my mentor while Nick was away, and several REALLY great design advises, without which I wouldn't be able to finish writing the plugin.

Shane Liebling & Daniel Van Derveer - for everyday's help in Ruby/Rails/Git problems ;)

also, [Nick,Joel,Dan,Shane].each { |x| Karol.thanks_for_code_reviews_and_your_patience_when_I_was_asking_stupid_questions(x) }