public
Description: plugin which adds version control system to models AND associations, and also allows to pack many changes into one changelist
Homepage: http://has_versioning.lighthouseapp.com/
Clone URL: git://github.com/kkurach/has_versioning.git
name age message
file DESIGN_NOTES Wed Sep 02 15:29:03 -0700 2009 link to has_many :through scenario [kkurach]
file LICENSE Wed Aug 26 18:18:03 -0700 2009 first import [kkurach]
file README Wed Sep 02 13:54:47 -0700 2009 Fix readme [Dan Van Derveer]
file Rakefile Wed Aug 26 18:18:03 -0700 2009 first import [kkurach]
file init.rb Wed Aug 26 18:18:03 -0700 2009 first import [kkurach]
file install.rb Wed Aug 26 18:18:03 -0700 2009 first import [kkurach]
directory lib/ Tue Sep 01 09:54:43 -0700 2009 now using variable real_id instad of versioned_... [kkurach]
directory tasks/ Wed Aug 26 18:18:03 -0700 2009 first import [kkurach]
directory test/ Tue Sep 01 09:54:43 -0700 2009 now using variable real_id instad of versioned_... [kkurach]
file uninstall.rb Wed Aug 26 18:18:03 -0700 2009 first import [kkurach]
README
# 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:

- keep track of all changes to selected models
- collect many changes in a one changelist
- keep track of object associations


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.

4. 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

4. 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)
}