deja_vue 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Manifest ADDED
@@ -0,0 +1,9 @@
1
+ Manifest
2
+ README.rdoc
3
+ Rakefile
4
+ deja_vue.gemspec
5
+ lib/deja_vue.rb
6
+ lib/deja_vue/has_deja_vue.rb
7
+ lib/deja_vue/history.rb
8
+ lib/deja_vue/version.rb
9
+ rails/init.rb
data/README.rdoc ADDED
@@ -0,0 +1,75 @@
1
+ = DejaVue
2
+
3
+ Yet another gem to store version of your models. But it uses mongodb as
4
+ backend. Based on PaperTrail (http://github.com/airblade/paper_trail/).
5
+
6
+ == Why choose DejaVue
7
+
8
+ The main goal of DejaVue is to keep track of associated models. It's
9
+ useful for a model whose relations change among time and to see how
10
+ it really looked like at a certain point.
11
+
12
+ There are 3 kinds of versioning: create, update, destroy
13
+ Differently from PaperTrail, each version will record the current model info.
14
+ So PapelTrail _create_ versions have reify == nil
15
+ DejaVue have _create_ version == model_version_when_creating
16
+
17
+ == Installing
18
+
19
+ $ bundle install
20
+ $ gem install gemcutter
21
+ $ gem build deja_vue.gemspec
22
+ $ gem install ./deja_vue-*.gem
23
+
24
+ == Beggining with DejaVue: Add to your model
25
+
26
+ Add a single line and your model will start beeing versionated:
27
+
28
+ def SomeModel < ActiveRecord::Base
29
+ has_deja_vue
30
+ end
31
+
32
+ Or, for example, use the ignore option:
33
+
34
+ def SomeModel < ActiveRecord::Base
35
+ has_deja_vue :ignore => :location
36
+ end
37
+
38
+
39
+ == More Options
40
+
41
+ You might pass some options, including:
42
+ :ignore | An Array of fields that will be ignored. If
43
+ | only those fields have changed, then no version
44
+ | will be created.
45
+ | has_deja_vue :ignore => :last_exported_on
46
+ :associations | Store associated models to be record with the model
47
+ | so it can be fully restored. Right now it works only
48
+ | with has_one / belongs_to relationships.
49
+ | Ex.:
50
+ | class Account < ActiveRecord::Base
51
+ | has_one :account_preference
52
+ |
53
+ | has_deja_vue :associations => [:account_preference]
54
+ | end
55
+ |
56
+ :extra_info_fields | Almost the same as the above option. But it handles
57
+ | more simple info (like strings, integers, floats).
58
+ | Useful to store a tag_list field or a counter cache
59
+ | Ex:
60
+ |
61
+ | class BlogPost < ActiveRecord::Base
62
+ | acts_as_taggable_on :tags
63
+ |
64
+ | has_deja_vue :extra_info_fields => [:tag_list]
65
+ | end
66
+ |
67
+ :who_did_it | a default value for record who_did_it when you
68
+ | are not in a request-response cycle (ex. in a job).
69
+ | Ex.:
70
+ | has_deja_vue :who_did_it => 'admin'
71
+ | Alternatively you are able to set the user right
72
+ | before save the model that will be versionated, using:
73
+ | DejaVue.who_did_it = 'otavio'
74
+ | SomeModel.save
75
+
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ begin
2
+ # Rspec 1.3.0
3
+ require 'spec/rake/spectask'
4
+ desc 'Default: run specs'
5
+ task :default => :spec
6
+ Spec::Rake::SpecTask.new do |t|
7
+ t.spec_files = FileList["spec/**/*_spec.rb"]
8
+ end
9
+
10
+ Spec::Rake::SpecTask.new('rcov') do |t|
11
+ t.spec_files = FileList["spec/**/*_spec.rb"]
12
+ t.rcov = true
13
+ t.rcov_opts = ['--exclude', 'spec']
14
+ end
15
+
16
+ rescue LoadError
17
+ puts "Rspec not available. Install it with: gem install rspec"
18
+ end
19
+
data/deja_vue.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib/', __FILE__)
3
+ $:.unshift lib unless $:.include?(lib)
4
+
5
+ require 'deja_vue/version'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = %q{deja_vue}
9
+ s.version = DejaVue::VERSION
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
12
+ s.authors = ["Riopro Informatica Ltda"]
13
+ s.date = %q{2010-08-11}
14
+ s.description = %q{DejaVue -> Ruby versioning Gem using MongoDB as backend}
15
+ s.email = %q{riopro@riopro.com.br}
16
+ s.extra_rdoc_files = ["README.rdoc", "lib/deja_vue.rb"]
17
+ s.files = ["Manifest", "README.rdoc", "Rakefile", "deja_vue.gemspec", "lib/deja_vue.rb", "lib/deja_vue/has_deja_vue.rb", "lib/deja_vue/history.rb", "rails/init.rb"]
18
+ s.homepage = %q{http://github.com/riopro/deja_vue}
19
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "deja_vue", "--main", "README.rdoc"]
20
+ s.require_paths = ["lib"]
21
+ s.rubyforge_project = %q{deja_vue}
22
+ s.rubygems_version = %q{1.3.7}
23
+ s.summary = %q{Keep track of your models changing history, using MongoDB as backend}
24
+
25
+ if s.respond_to? :specification_version then
26
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
27
+ s.specification_version = 3
28
+ end
29
+ end
data/lib/deja_vue.rb ADDED
@@ -0,0 +1,98 @@
1
+ require 'deja_vue/has_deja_vue'
2
+ require 'deja_vue/history'
3
+
4
+ # Based on PaperTrail (http://github.com/airblade/paper_trail/).
5
+ # Using:
6
+ # Add an initializer at config/initializers and require 'deja_vue'
7
+ # Add to your model:
8
+ # has_deja_vue
9
+ # You might pass some options, including:
10
+ # :ignore | An Array of fields that will be ignored. If
11
+ # | only those fields have changed, then no version
12
+ # | will be created.
13
+ # | has_deja_vue :ignore => :last_exported_on
14
+ # :associations | Store associated models to be record with the model
15
+ # | so it can be fully restored. Right now it works only
16
+ # | with has_one / belongs_to relationships.
17
+ # | Ex.:
18
+ # | class Account < ActiveRecord::Base
19
+ # | has_one :account_preference
20
+ # |
21
+ # | has_deja_vue :associations => [:account_preference]
22
+ # | end
23
+ # |
24
+ # :extra_info_fields | Almost the same as the above option. But it handles
25
+ # | more simple info (like strings, integers, floats).
26
+ # | Useful to store a tag_list field or a counter cache
27
+ # | Ex:
28
+ # |
29
+ # | class BlogPost < ActiveRecord::Base
30
+ # | acts_as_taggable_on :tags
31
+ # |
32
+ # | has_deja_vue :extra_info_fields => [:tag_list]
33
+ # | end
34
+ # |
35
+ # :who_did_it | a default value for record who_did_it when you
36
+ # | are not in a request-response cycle (ex. in a job).
37
+ # | Ex.:
38
+ # | has_deja_vue :who_did_it => 'admin'
39
+ # | Alternatively you are able to set the user right
40
+ # | before save the model that will be versionated, using:
41
+ # | DejaVue.who_did_it = 'otavio'
42
+ # | SomeModel.save
43
+ #
44
+ # There are 3 kinds of versioning: create, update, destroy
45
+ # Differently from PaperTrail, each version will record the current model info.
46
+ # So PapelTrail _create_ versions have reify == nil
47
+ # DejaVue have _create_ version == model_version_when_creating
48
+ module DejaVue
49
+
50
+ def self.included(base)
51
+ base.before_filter :set_deja_vue_user
52
+ end
53
+
54
+ def self.who_did_it
55
+ Thread.current[:who_did_it]
56
+ end
57
+
58
+ def self.who_did_it=(value)
59
+ Thread.current[:who_did_it] = value
60
+ end
61
+
62
+ # Used to set an user to be versionated, execute the block and
63
+ # then rollback to the thread's regular user.
64
+ # Example:
65
+ #
66
+ # @blog_post = BlogPost.find 10
67
+ # @blog_post.title = "new title"
68
+ # DejaVue.setting_user_as('someone') do
69
+ # @blog_post.save
70
+ # end
71
+ def self.setting_user_as(value, &block)
72
+ actual_user = Thread.current[:who_did_it]
73
+ Thread.current[:who_did_it] = value
74
+ yield if block_given?
75
+ Thread.current[:who_did_it] = actual_user
76
+ end
77
+
78
+ protected
79
+
80
+ # Returns the user who is responsible for any changes that occur.
81
+ # By default this calls `current_user` and returns the result.
82
+ #
83
+ # Override this method in your controller to call a different
84
+ # method, e.g. `current_person`, or anything you like.
85
+ def user_for_deja_vue
86
+ return current_user if current_user && current_user.is_a?(String)
87
+ current_user[:id] if current_user && current_user[:id]
88
+ end
89
+
90
+ private
91
+
92
+ # Verifies if current controller responds to current_user method
93
+ def set_deja_vue_user
94
+ Thread.current[:who_did_it] = user_for_deja_vue
95
+ end
96
+ end
97
+
98
+ ActionController::Base.send :include, DejaVue
@@ -0,0 +1,85 @@
1
+ module DejaVue
2
+
3
+ def self.included(base)
4
+ base.send :extend, ClassMethods
5
+ end
6
+
7
+
8
+ module ClassMethods
9
+ # Options:
10
+ # :ignore an array of attributes for which a new +Version+ will not be created if only they change.
11
+ def has_deja_vue(options = {})
12
+ send :include, InstanceMethods
13
+
14
+ cattr_accessor :deja_vue_options
15
+ options[:ignore] = options[:ignore].map &:to_s if options[:ignore]
16
+ self.deja_vue_options = options
17
+
18
+ cattr_accessor :deja_vue_active
19
+ self.deja_vue_active = true
20
+
21
+ before_save :check_for_version_changes
22
+ after_update :record_update_version
23
+ after_create :record_create_version
24
+ after_destroy :record_destroy_version
25
+
26
+ end
27
+
28
+ # FIXME: not implemented yet
29
+ def deja_vue_off
30
+ self.deja_vue_active = false
31
+ end
32
+
33
+ # FIXME: not implemented yet
34
+ def deja_vue_on
35
+ self.deja_vue_active = true
36
+ end
37
+ end
38
+
39
+ module InstanceMethods
40
+
41
+ # return array with changed fields after object has been saved
42
+ # (created or updated).
43
+ # Whe need this to validate if the changes must be recorded after save
44
+ def version_changes
45
+ @version_changes
46
+ end
47
+
48
+ # Search for all change history from the current object
49
+ def histories(extra_options={})
50
+ default_options = {:versionable_type => self.class.to_s, :versionable_id => self.id.to_s}
51
+ History.where(extra_options.merge(default_options)).sort(:created_at.desc).all
52
+ end
53
+
54
+ # Search for a change history from the current object by id
55
+ def history(param_id)
56
+ History.where(:versionable_type => self.class.to_s, :versionable_id => self.id.to_s, :id => param_id).first
57
+ end
58
+
59
+ private
60
+
61
+ def check_for_version_changes
62
+ @version_changes = self.changed
63
+ end
64
+
65
+ def versionate_as(kind_of_version)
66
+ History.versionate(self, kind_of_version, deja_vue_options.merge(:who_did_it => DejaVue.who_did_it)) if DejaVue.who_did_it
67
+ History.versionate(self, kind_of_version, deja_vue_options) unless DejaVue.who_did_it
68
+ end
69
+
70
+ def record_update_version
71
+ versionate_as("update")
72
+ end
73
+
74
+ def record_create_version
75
+ versionate_as("create")
76
+ end
77
+
78
+ def record_destroy_version
79
+ versionate_as("destroy")
80
+ end
81
+ end
82
+
83
+ end
84
+
85
+ ActiveRecord::Base.send :include, DejaVue
@@ -0,0 +1,184 @@
1
+ class History
2
+ include MongoMapper::Document
3
+
4
+ key :versionable_type, String, :required => true, :index => true
5
+ key :versionable_id, String, :required => true, :index => true
6
+ key :kind_of_version, String, :required => true
7
+ key :version_attributes, Hash, :required => true
8
+ key :version_associations,Hash, :index => true
9
+ key :extra_info, Hash # para armazenar informações relacionadas, como tag_list
10
+ key :changed_fields, Array
11
+ key :who_did_it, String
12
+ timestamps!
13
+ ensure_index :created_at
14
+
15
+
16
+ #
17
+ # Validations
18
+ #
19
+ validates_true_for :version_attributes, :logic => lambda { !version_attributes.empty? }
20
+
21
+ #
22
+ # Constants
23
+ #
24
+
25
+ DEFAULT_IGNORED_FIELDS = ["updated_at", "created_at"]
26
+
27
+ KIND_OF_VERSIONS = %w( create update destroy )
28
+
29
+ #
30
+ # Class Methods
31
+ #
32
+
33
+ def self.versionate(object, kind_of_version, options={})
34
+ return false if object.blank?
35
+ History.new.create_version(object, kind_of_version, options)
36
+ end
37
+
38
+ # marreta para teste rodar, por causa do i18n do remarkable, FIXME
39
+ def self.human_name(foo)
40
+ "History"
41
+ end
42
+
43
+ #
44
+ # Instance Methods
45
+ #
46
+
47
+ def create_version(object, kind_of_version, options={})
48
+ self.ignored_fields= options[:ignore] if options[:ignore]
49
+ self.who_did_it = check_and_retrieve_user(options[:who_did_it]) if options[:who_did_it]
50
+ self.kind_of_version = kind_of_version
51
+ self.versionable_type = object.class.name
52
+ self.versionable_id = object.id.to_s
53
+
54
+ unless self.kind_of_version == "destroy"
55
+ return false unless self.has_changed?(object)
56
+ end
57
+ self.version_attributes = attributes_filter(object.attributes)
58
+ store_version_associations object, options
59
+ store_extra_info(object, options)
60
+ self.save
61
+ end
62
+
63
+ # Rebuild the versionated object.
64
+ # Do not rebuild if it's not saved
65
+ def version
66
+ return nil if self.new_record?
67
+ @version ||= recreate_version_object
68
+ end
69
+
70
+ # fields that whose change won't cause a history trace
71
+ def ignored_fields
72
+ @ignored_fields || DEFAULT_IGNORED_FIELDS
73
+ end
74
+
75
+ def ignored_fields=(fields_array)
76
+ fields = nil
77
+ fields = fields_array if fields_array.is_a?(Array)
78
+ fields = [fields_array.to_s] if ( fields_array.is_a?(String) || fields_array.is_a?(Symbol) )
79
+ return false unless fields.is_a?(Array)
80
+ @ignored_fields = ( DEFAULT_IGNORED_FIELDS + fields ).uniq
81
+ end
82
+
83
+ # return true id object has changed
84
+ def has_changed?(object)
85
+ return false unless object.try(:version_changes)
86
+ !store_changed_fields(object).empty?
87
+ end
88
+
89
+ def next_version
90
+ History.where(:created_at.gte => self.created_at, # could have the same creation date, as long it's not the same object
91
+ :id.ne => self.id, # ne => not equal to
92
+ :versionable_type => self.versionable_type,
93
+ :versionable_id => self.versionable_id).sort(:created_at.asc).first
94
+ end
95
+
96
+ def previous_version
97
+ History.where(:created_at.lte => self.created_at, # could have the same creation date, as long it's not the same object
98
+ :id.ne => self.id, # ne => not equal to
99
+ :versionable_type => self.versionable_type,
100
+ :versionable_id => self.versionable_id).sort(:created_at.desc).first
101
+ end
102
+
103
+ protected
104
+
105
+ # Retrieve the versionated object
106
+ def recreate_version_object
107
+ model = recreate_model(self.versionable_type, self.version_attributes)
108
+ model.id = self.versionable_id
109
+
110
+ self.version_associations.each do |associated_object, associated_object_attributes|
111
+ model.try "#{associated_object.to_s}=", recreate_model(associated_object, associated_object_attributes)
112
+ end
113
+ update_fields model, self.extra_info
114
+ model
115
+ end
116
+
117
+ # recreates object from a class name and its attributes
118
+ def recreate_model(class_name, attributes_hash={})
119
+ associated_klass = class_name.to_s.camelize.constantize.new
120
+ update_fields associated_klass, attributes_hash
121
+ associated_klass
122
+ end
123
+
124
+ # updates values for an instantiated class
125
+ def update_fields(class_object, attributes_hash={})
126
+ attributes_hash.each do |key, value|
127
+ class_object.send "#{key.to_s}=", value
128
+ end
129
+ end
130
+
131
+ # If user is a Class, try to retrieve user
132
+ # from an Authlogic User Session.
133
+ # If it is an Authlogic object, return the user id.
134
+ #
135
+ # Else it returns the current object.
136
+ def check_and_retrieve_user(user)
137
+ if user.is_a? Class
138
+ begin
139
+ user.find.user.id
140
+ rescue
141
+ nil
142
+ end
143
+ else
144
+ user
145
+ end
146
+ end
147
+
148
+ def store_version_associations(object, options)
149
+ self.version_associations = {}
150
+ options[:associations].each do |association|
151
+ self.version_associations.store(association, attributes_filter( object.try(association).attributes) ) if object.try(association)
152
+ end if options[:associations]
153
+ true
154
+ end
155
+
156
+ def store_changed_fields(object)
157
+ self.changed_fields = object.version_changes - self.ignored_fields if self.ignored_fields.is_a?(Array)
158
+ self.changed_fields
159
+ end
160
+
161
+ def store_extra_info(object, options)
162
+ self.extra_info = {}
163
+ options[:extra_info_fields].each do |extra_info|
164
+ self.extra_info.store( extra_info, object.try(extra_info) ) if object.try(extra_info)
165
+ end if options[:extra_info_fields]
166
+ self.extra_info = attributes_filter(self.extra_info)
167
+ true
168
+ end
169
+
170
+ def attributes_filter(key_value={})
171
+ key_value.each do |k,v|
172
+ key_value[k]=v.to_s if key_value[k].is_a?(Date)
173
+ end
174
+ key_value.each do |k,v|
175
+ key_value[k]=v.utc.to_s if key_value[k].is_a?(Time)
176
+ end
177
+ key_value.each do |k,v|
178
+ key_value[k]=v.to_f if key_value[k].is_a?(BigDecimal)
179
+ end
180
+
181
+ key_value
182
+ end
183
+
184
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ config.after_initialize do
2
+ require 'deja_vue'
3
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deja_vue
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 2
10
+ version: 0.1.2
11
+ platform: ruby
12
+ authors:
13
+ - Riopro Informatica Ltda
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-08-11 00:00:00 -03:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: DejaVue -> Ruby versioning Gem using MongoDB as backend
23
+ email: riopro@riopro.com.br
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - README.rdoc
30
+ - lib/deja_vue.rb
31
+ files:
32
+ - Manifest
33
+ - README.rdoc
34
+ - Rakefile
35
+ - deja_vue.gemspec
36
+ - lib/deja_vue.rb
37
+ - lib/deja_vue/has_deja_vue.rb
38
+ - lib/deja_vue/history.rb
39
+ - rails/init.rb
40
+ has_rdoc: true
41
+ homepage: http://github.com/riopro/deja_vue
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --line-numbers
47
+ - --inline-source
48
+ - --title
49
+ - deja_vue
50
+ - --main
51
+ - README.rdoc
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ hash: 11
69
+ segments:
70
+ - 1
71
+ - 2
72
+ version: "1.2"
73
+ requirements: []
74
+
75
+ rubyforge_project: deja_vue
76
+ rubygems_version: 1.3.7
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: Keep track of your models changing history, using MongoDB as backend
80
+ test_files: []
81
+