model_history 0.1.0
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/.gitignore +19 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +57 -0
- data/Rakefile +1 -0
- data/lib/generators/model_history/migration/migration_generator.rb +31 -0
- data/lib/generators/model_history/migration/templates/active_record/migration.rb +33 -0
- data/lib/model_history.rb +3 -0
- data/lib/model_history/model_history_mixin.rb +142 -0
- data/lib/model_history/model_history_record.rb +71 -0
- data/lib/model_history/version.rb +3 -0
- data/model_history.gemspec +28 -0
- data/spec/fake_app.rb +40 -0
- data/spec/models/user_spec.rb +35 -0
- data/spec/spec_helper.rb +18 -0
- metadata +185 -0
data/.gitignore
ADDED
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2013 patorash
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# ModelHistory
|
|
2
|
+
|
|
3
|
+
Model History is a simple gem that allows you to keep track of changes to specific fields in your Rails models using the ActiveRecord::Dirty module.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
gem 'model_history'
|
|
10
|
+
|
|
11
|
+
Install it using Bundler:
|
|
12
|
+
|
|
13
|
+
$ bundle install
|
|
14
|
+
|
|
15
|
+
Generate the Model History migration and migrate your database
|
|
16
|
+
|
|
17
|
+
$ rails generate model_history:migration
|
|
18
|
+
$ rake db:migrate
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```Ruby
|
|
23
|
+
class Widget < ActiveRecord::Base
|
|
24
|
+
has_model_history :name, :price, :creator => proc{ User.current_user }
|
|
25
|
+
attr_accessible :name, :price
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
widget = Widget.last
|
|
29
|
+
widget.name # => "Box"
|
|
30
|
+
widget.name = "Heart Shaped Box"
|
|
31
|
+
widget.save
|
|
32
|
+
widget.model_history_records # => returns all changes to the widget
|
|
33
|
+
|
|
34
|
+
model_history = widget.model_history_records.last
|
|
35
|
+
model_history.old_value # => "Box"
|
|
36
|
+
model_history.new_value # => "Heart Shaped Box"
|
|
37
|
+
|
|
38
|
+
user = User.find(123)
|
|
39
|
+
widget.model_history_records.created_by(user)
|
|
40
|
+
# => returns all changes to the widget performed by the specified user
|
|
41
|
+
|
|
42
|
+
class User < ActiveRecord::Base
|
|
43
|
+
creates_model_history
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
user = User.find(123)
|
|
47
|
+
user.model_history_records
|
|
48
|
+
# => returns changes made by the specified user
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Contributing
|
|
52
|
+
|
|
53
|
+
1. Fork it
|
|
54
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
55
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
56
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
57
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'rails/generators/migration'
|
|
2
|
+
|
|
3
|
+
module ModelHistory
|
|
4
|
+
class MigrationGenerator < Rails::Generators::Base
|
|
5
|
+
include Rails::Generators::Migration
|
|
6
|
+
|
|
7
|
+
desc "Generates migration for ModelHistoryRecord model"
|
|
8
|
+
|
|
9
|
+
def self.orm
|
|
10
|
+
Rails::Generators.options[:rails][:orm]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.source_root
|
|
14
|
+
File.join(File.dirname(__FILE__), 'templates', (orm.to_s unless orm.class.eql?(String)) )
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.orm_has_migration?
|
|
18
|
+
[:active_record].include? orm
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.next_migration_number(path)
|
|
22
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def create_migration_file
|
|
26
|
+
if self.class.orm_has_migration?
|
|
27
|
+
migration_template 'migration.rb', 'db/migrate/create_model_history_records'
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class CreateModelHistoryRecords < ActiveRecord::Migration
|
|
2
|
+
def self.up
|
|
3
|
+
create_table :model_history_records do |t|
|
|
4
|
+
t.integer :creator_id
|
|
5
|
+
t.string :creator_type, :limit => 64
|
|
6
|
+
t.integer :model_id
|
|
7
|
+
t.string :model_type, :limit => 64
|
|
8
|
+
t.string :column_name, :limit => 64
|
|
9
|
+
t.string :column_type, :limit => 16 # :string, :text, :integer, :decimal, :float, :datetime, :boolean
|
|
10
|
+
t.string :old_value, :limit => 128
|
|
11
|
+
t.string :new_value, :limit => 128
|
|
12
|
+
|
|
13
|
+
t.datetime :created_at
|
|
14
|
+
t.datetime :revised_created_at
|
|
15
|
+
t.datetime :updated_at
|
|
16
|
+
t.datetime :deleted_at
|
|
17
|
+
end
|
|
18
|
+
add_index :model_history_records, [:creator_id, :creator_type]
|
|
19
|
+
add_index :model_history_records, [:model_id, :model_type]
|
|
20
|
+
add_index :model_history_records, [:created_at, :revised_created_at], :name => "index_created_at"
|
|
21
|
+
add_index :model_history_records, [:deleted_at, :created_at, :revised_created_at], :name => "index_deleted_at_created_at"
|
|
22
|
+
add_index :model_history_records, [:model_type, :column_name, :deleted_at], :name => "index_obj_type_column_deleted_at"
|
|
23
|
+
add_index :model_history_records, [:model_type, :column_name, :deleted_at, :created_at, :revised_created_at], :name => "index_obj_type_column_deleted_at_created_at"
|
|
24
|
+
|
|
25
|
+
add_index :model_history_records, :old_value, :name => "index_old_value"
|
|
26
|
+
add_index :model_history_records, :new_value, :name => "index_new_value"
|
|
27
|
+
add_index :model_history_records, [:old_value, :new_value], :name => "index_old_value_new_value"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.down
|
|
31
|
+
drop_table :model_history_records
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
module ModelHistory
|
|
2
|
+
|
|
3
|
+
module Mixin
|
|
4
|
+
|
|
5
|
+
class CreatorProcError < StandardError ; end
|
|
6
|
+
|
|
7
|
+
def self.included base
|
|
8
|
+
base.class_eval do
|
|
9
|
+
extend ClassMethods
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module ClassMethods
|
|
14
|
+
|
|
15
|
+
# call the model_history class method on models with fields that you want to track changes on.
|
|
16
|
+
# example usage:
|
|
17
|
+
# class User < ActiveRecord::Base
|
|
18
|
+
# has_model_history :email, :first_name, :last_name
|
|
19
|
+
# end
|
|
20
|
+
|
|
21
|
+
# pass an optional proc to assign a creator to the model_history object
|
|
22
|
+
# example usage:
|
|
23
|
+
# class User < ActiveRecord::Base
|
|
24
|
+
# has_model_history :email, :first_name, :last_name, :creator => proc { User.current_user }
|
|
25
|
+
# end
|
|
26
|
+
|
|
27
|
+
def has_model_history *args
|
|
28
|
+
# Mix in the module, but ensure to do so just once.
|
|
29
|
+
metaclass = (class << self; self; end)
|
|
30
|
+
return if metaclass.included_modules.include?(ModelHistory::Mixin::ObjectInstanceMethods)
|
|
31
|
+
|
|
32
|
+
has_many :model_history_records, :as => :model, :dependent => :destroy
|
|
33
|
+
attr_accessor :model_history_changes, :initialize_model_history
|
|
34
|
+
cattr_accessor :model_history_columns
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
self.model_history_columns ||= []
|
|
38
|
+
|
|
39
|
+
if args.present?
|
|
40
|
+
|
|
41
|
+
before_save :set_model_history_changes
|
|
42
|
+
after_save :save_model_history
|
|
43
|
+
|
|
44
|
+
args.each do |arg|
|
|
45
|
+
if [String,Symbol].include?(arg.class)
|
|
46
|
+
arg = arg.to_sym
|
|
47
|
+
self.model_history_columns << arg unless self.model_history_columns.include?(arg)
|
|
48
|
+
define_method "creator_for_model_history" do end
|
|
49
|
+
elsif arg.is_a?(Hash)
|
|
50
|
+
creator_proc = arg.delete(:creator)
|
|
51
|
+
send :define_method, "creator_for_model_history" do
|
|
52
|
+
begin
|
|
53
|
+
creator_proc.is_a?(Proc) ? creator_proc.call : nil
|
|
54
|
+
rescue
|
|
55
|
+
raise ModelHistory::Mixin::CreatorProcError
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
include ModelHistory::Mixin::ObjectInstanceMethods
|
|
61
|
+
end
|
|
62
|
+
end # has_model_history
|
|
63
|
+
|
|
64
|
+
def creates_model_history
|
|
65
|
+
# Mix in the module, but ensure to do so just once.
|
|
66
|
+
metaclass = (class << self; self; end)
|
|
67
|
+
return if metaclass.included_modules.include?(ModelHistory::Mixin::CreatorInstanceMethods)
|
|
68
|
+
|
|
69
|
+
has_many :model_history_records, :as => :creator
|
|
70
|
+
|
|
71
|
+
include ModelHistory::Mixin::CreatorInstanceMethods
|
|
72
|
+
end # creates_model_history
|
|
73
|
+
end # ClassMethods
|
|
74
|
+
|
|
75
|
+
module ObjectInstanceMethods
|
|
76
|
+
|
|
77
|
+
def set_model_history_changes
|
|
78
|
+
return true unless self.new_record? || self.changed?
|
|
79
|
+
|
|
80
|
+
self.model_history_changes = self.class.model_history_columns.inject({}) do |changes_hash, column_name|
|
|
81
|
+
changes_hash[column_name] = self.send("#{column_name}_change") if self.send("#{column_name}_changed?")
|
|
82
|
+
changes_hash[column_name] ||= [nil, self.send(column_name)] if self.new_record? && self.send(column_name).present?
|
|
83
|
+
changes_hash
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
self.initialize_model_history = self.new_record?
|
|
87
|
+
true
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def save_model_history
|
|
91
|
+
return true unless self.model_history_changes.present?
|
|
92
|
+
self.model_history_changes.each do |column_name,vals|
|
|
93
|
+
add_model_history_record column_name, vals[0], vals[1], :creator => self.creator_for_model_history
|
|
94
|
+
end
|
|
95
|
+
self.model_history_changes = nil
|
|
96
|
+
true
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def add_model_history_record column_name, old_value, new_value, options={}
|
|
100
|
+
creator = options[:creator] || self.creator_for_model_history
|
|
101
|
+
|
|
102
|
+
dhr_attributes = {
|
|
103
|
+
:model => self,
|
|
104
|
+
:column_name => column_name,
|
|
105
|
+
:column_type => self.class.columns_hash[column_name.to_s].type,
|
|
106
|
+
:old_value => old_value,
|
|
107
|
+
:new_value => new_value,
|
|
108
|
+
:creator => creator
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
dhr = ModelHistoryRecord.new(dhr_attributes)
|
|
112
|
+
|
|
113
|
+
# attributes for manual updates
|
|
114
|
+
[:revised_created_at, :performing_manual_update].each do |attribute|
|
|
115
|
+
dhr.send("#{attribute}=", options[attribute]) if options[attribute]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
self.model_history_records << dhr
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def history_for_column column, options={}
|
|
122
|
+
options[:sort] = true if options[:sort].blank?
|
|
123
|
+
|
|
124
|
+
records = model_history_records.for_column(column)
|
|
125
|
+
records = records.send(*options[:scope]) if options[:scope]
|
|
126
|
+
records = records.order_asc if options[:sort]
|
|
127
|
+
|
|
128
|
+
options[:return_objects] ? records : records.map { |s| s.new_value }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
end # ObjectInstanceMethods
|
|
132
|
+
|
|
133
|
+
module CreatorInstanceMethods
|
|
134
|
+
|
|
135
|
+
end # CreatorInstanceMethods
|
|
136
|
+
|
|
137
|
+
end # Mixin
|
|
138
|
+
|
|
139
|
+
end # ModelHistory
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
ActiveRecord::Base.send :include, ModelHistory::Mixin
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require "active_record"
|
|
2
|
+
|
|
3
|
+
class ModelHistoryRecord < ActiveRecord::Base
|
|
4
|
+
belongs_to :creator, :polymorphic => true
|
|
5
|
+
belongs_to :model, :polymorphic => true
|
|
6
|
+
|
|
7
|
+
validates_presence_of :model_type, :model_id, :column_name, :column_type, :new_value
|
|
8
|
+
|
|
9
|
+
scope :created_by, lambda { |creator| where(["#{table_name}.creator_id = ? AND #{table_name}.creator_type = ?", creator.id, creator.class.name]) }
|
|
10
|
+
scope :not_created_by, lambda { |non_creator| where(["#{table_name}.creator_id <> ? OR #{table_name}.creator_type <> ?", non_creator.id, non_creator.class.name]) }
|
|
11
|
+
scope :for_model_type, lambda { |model_type| where(model_type: model_type.to_s.classify) }
|
|
12
|
+
scope :for_column, lambda { |column| where(column_name: column.to_s) }
|
|
13
|
+
scope :created_in_range, lambda { |range| created_at_gte(range.first).created_at_lte(range.last) }
|
|
14
|
+
scope :created_at_gte, lambda { |date| created_at_lte_or_gte(date,"gte") }
|
|
15
|
+
scope :created_at_lte, lambda { |date| created_at_lte_or_gte(date,"lte") }
|
|
16
|
+
scope :created_at_lte_or_gte, lambda { |date, lte_or_gte|
|
|
17
|
+
lte_or_gte = lte_or_gte.to_s == "lte" ? "<=" : ">="
|
|
18
|
+
where("((#{table_name}.revised_created_at is NULL OR #{table_name}.revised_created_at = '') AND #{table_name}.created_at #{lte_or_gte} ?) " +
|
|
19
|
+
" OR #{table_name}.revised_created_at #{lte_or_gte} ?", date, date)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
scope :order_asc, lambda { order_by_action_timestamp("ASC") }
|
|
23
|
+
scope :order_desc, lambda { order_by_action_timestamp("DESC") }
|
|
24
|
+
scope :order_by_action_timestamp, lambda { |asc_or_desc|
|
|
25
|
+
order("CASE WHEN (#{table_name}.revised_created_at IS NULL OR #{table_name}.revised_created_at = '') then #{table_name}.created_at else #{table_name}.revised_created_at END #{asc_or_desc}")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
attr_accessible :model, :model_id, :model_type,
|
|
30
|
+
:column_name, :column_type, :old_value, :new_value,
|
|
31
|
+
:creator, :creator_id, :creator_type, :revised_created_at
|
|
32
|
+
|
|
33
|
+
attr_accessor :performing_manual_update
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
[:new_value, :old_value].each do |attribute|
|
|
37
|
+
define_method "#{attribute}" do
|
|
38
|
+
val_to_col_type(attribute)
|
|
39
|
+
end
|
|
40
|
+
define_method "#{attribute}=" do |val|
|
|
41
|
+
self[attribute] = val.nil? ? nil : val.to_s
|
|
42
|
+
instance_variable_set "@#{attribute}", val
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def action_timestamp
|
|
47
|
+
# use revised_created_at field to update the timestamp for
|
|
48
|
+
# the dirty history action while retaining data integrity
|
|
49
|
+
self[:revised_created_at] || self[:created_at]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def val_to_col_type attribute
|
|
55
|
+
val_as_string = self[attribute]
|
|
56
|
+
return nil if val_as_string.nil?
|
|
57
|
+
case self[:column_type].to_sym
|
|
58
|
+
when :integer, :boolean
|
|
59
|
+
val_as_string.to_i
|
|
60
|
+
when :decimal, :float
|
|
61
|
+
val_as_string.to_f
|
|
62
|
+
when :datetime
|
|
63
|
+
Time.parse val_as_string
|
|
64
|
+
when :date
|
|
65
|
+
Date.parse val_as_string
|
|
66
|
+
else # :string, :text
|
|
67
|
+
val_as_string
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'model_history/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = 'model_history'
|
|
8
|
+
spec.version = ModelHistory::VERSION
|
|
9
|
+
spec.authors = ['patorash']
|
|
10
|
+
spec.email = ['chariderpato@gmail.com']
|
|
11
|
+
spec.description = %q{Model History is a simple gem that allows you to keep track of changes to specific fields in your Rails models using the ActiveRecord::Dirty module.}
|
|
12
|
+
spec.summary = %q{Model History is a simple gem that allows you to keep track of changes to specific fields in your Rails models using the ActiveRecord::Dirty module.}
|
|
13
|
+
spec.homepage = 'https://github.com/patorash/model_history'
|
|
14
|
+
spec.license = 'MIT'
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files`.split($/)
|
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
|
+
spec.require_paths = ['lib']
|
|
20
|
+
|
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
|
22
|
+
spec.add_development_dependency 'rake'
|
|
23
|
+
spec.add_development_dependency 'rspec'
|
|
24
|
+
spec.add_development_dependency 'rspec-rails'
|
|
25
|
+
spec.add_development_dependency 'activerecord'
|
|
26
|
+
spec.add_development_dependency 'sqlite3'
|
|
27
|
+
spec.add_dependency 'rails', '>= 3.2.11'
|
|
28
|
+
end
|
data/spec/fake_app.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
require 'active_record'
|
|
3
|
+
require 'action_controller/railtie'
|
|
4
|
+
require 'action_view/helpers'
|
|
5
|
+
require 'model_history'
|
|
6
|
+
|
|
7
|
+
# database
|
|
8
|
+
ActiveRecord::Base.configurations = {'test' => {:adapter => 'sqlite3', :database => ':memory:'}}
|
|
9
|
+
ActiveRecord::Base.establish_connection('test')
|
|
10
|
+
|
|
11
|
+
# config
|
|
12
|
+
app = Class.new(Rails::Application)
|
|
13
|
+
app.config.secret_token = '3b7cd727ee24e8444053437c36cc66c4'
|
|
14
|
+
app.config.session_store :cookie_store, :key => '_myapp_session'
|
|
15
|
+
app.config.active_support.deprecation = :log
|
|
16
|
+
app.initialize!
|
|
17
|
+
|
|
18
|
+
# routes
|
|
19
|
+
app.routes.draw do
|
|
20
|
+
resources :users
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# models
|
|
24
|
+
class User < ActiveRecord::Base
|
|
25
|
+
has_model_history :name, :age
|
|
26
|
+
validates :name, :presence => true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# controllers
|
|
30
|
+
class ApplicationController < ActionController::Base; end
|
|
31
|
+
|
|
32
|
+
# helpers
|
|
33
|
+
Object.const_set(:ApplicationHelper, Module.new)
|
|
34
|
+
|
|
35
|
+
#migrations
|
|
36
|
+
class CreateAllTables < ActiveRecord::Migration
|
|
37
|
+
def self.up
|
|
38
|
+
create_table(:users) {|t| t.string :name; t.integer :age}
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe User do
|
|
5
|
+
describe "Check history" do
|
|
6
|
+
context "name change" do
|
|
7
|
+
before do
|
|
8
|
+
@user = User.create!(:name => 'Makunouchi', :age => 22)
|
|
9
|
+
@user.update_attributes!(:name => 'Sendo')
|
|
10
|
+
end
|
|
11
|
+
it "Get user history" do
|
|
12
|
+
history = @user.model_history_records.last
|
|
13
|
+
history.model_type.should == 'User'
|
|
14
|
+
history.column_name.should == 'name'
|
|
15
|
+
history.column_type.should == 'string'
|
|
16
|
+
history.old_value.should == 'Makunouchi'
|
|
17
|
+
history.new_value.should == 'Sendo'
|
|
18
|
+
end
|
|
19
|
+
it "#history_for_column" do
|
|
20
|
+
histories = @user.history_for_column :name
|
|
21
|
+
histories.should have(2).items
|
|
22
|
+
histories.should == ['Makunouchi', 'Sendo']
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context "callback methods" do
|
|
27
|
+
it "call add_model_history_record" do
|
|
28
|
+
user = User.new(:name => "Makunouchi", :age => 22)
|
|
29
|
+
user.should_receive(:set_model_history_changes)
|
|
30
|
+
user.should_receive(:save_model_history)
|
|
31
|
+
user.save!
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
3
|
+
require 'rspec'
|
|
4
|
+
|
|
5
|
+
# Requires supporting files with custom matchers and macros, etc,
|
|
6
|
+
# in ./support/ and its subdirectories.
|
|
7
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
|
8
|
+
require File.join(File.dirname(__FILE__), 'fake_app')
|
|
9
|
+
|
|
10
|
+
require 'rspec/rails'
|
|
11
|
+
require 'generators/model_history/migration/templates/active_record/migration'
|
|
12
|
+
|
|
13
|
+
RSpec.configure do |config|
|
|
14
|
+
config.before :all do
|
|
15
|
+
CreateAllTables.up unless ActiveRecord::Base.connection.table_exists? 'users'
|
|
16
|
+
CreateModelHistoryRecords.up unless ActiveRecord::Base.connection.table_exists? 'model_history_records'
|
|
17
|
+
end
|
|
18
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: model_history
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- patorash
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2013-06-17 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: bundler
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ~>
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '1.3'
|
|
22
|
+
type: :development
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
none: false
|
|
26
|
+
requirements:
|
|
27
|
+
- - ~>
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '1.3'
|
|
30
|
+
- !ruby/object:Gem::Dependency
|
|
31
|
+
name: rake
|
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
|
33
|
+
none: false
|
|
34
|
+
requirements:
|
|
35
|
+
- - ! '>='
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: '0'
|
|
38
|
+
type: :development
|
|
39
|
+
prerelease: false
|
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
41
|
+
none: false
|
|
42
|
+
requirements:
|
|
43
|
+
- - ! '>='
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '0'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: rspec
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
none: false
|
|
50
|
+
requirements:
|
|
51
|
+
- - ! '>='
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
type: :development
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
none: false
|
|
58
|
+
requirements:
|
|
59
|
+
- - ! '>='
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
- !ruby/object:Gem::Dependency
|
|
63
|
+
name: rspec-rails
|
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
|
65
|
+
none: false
|
|
66
|
+
requirements:
|
|
67
|
+
- - ! '>='
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0'
|
|
70
|
+
type: :development
|
|
71
|
+
prerelease: false
|
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
73
|
+
none: false
|
|
74
|
+
requirements:
|
|
75
|
+
- - ! '>='
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0'
|
|
78
|
+
- !ruby/object:Gem::Dependency
|
|
79
|
+
name: activerecord
|
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
|
81
|
+
none: false
|
|
82
|
+
requirements:
|
|
83
|
+
- - ! '>='
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: '0'
|
|
86
|
+
type: :development
|
|
87
|
+
prerelease: false
|
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
89
|
+
none: false
|
|
90
|
+
requirements:
|
|
91
|
+
- - ! '>='
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '0'
|
|
94
|
+
- !ruby/object:Gem::Dependency
|
|
95
|
+
name: sqlite3
|
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
|
97
|
+
none: false
|
|
98
|
+
requirements:
|
|
99
|
+
- - ! '>='
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '0'
|
|
102
|
+
type: :development
|
|
103
|
+
prerelease: false
|
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
105
|
+
none: false
|
|
106
|
+
requirements:
|
|
107
|
+
- - ! '>='
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: rails
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
none: false
|
|
114
|
+
requirements:
|
|
115
|
+
- - ! '>='
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: 3.2.11
|
|
118
|
+
type: :runtime
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
none: false
|
|
122
|
+
requirements:
|
|
123
|
+
- - ! '>='
|
|
124
|
+
- !ruby/object:Gem::Version
|
|
125
|
+
version: 3.2.11
|
|
126
|
+
description: Model History is a simple gem that allows you to keep track of changes
|
|
127
|
+
to specific fields in your Rails models using the ActiveRecord::Dirty module.
|
|
128
|
+
email:
|
|
129
|
+
- chariderpato@gmail.com
|
|
130
|
+
executables: []
|
|
131
|
+
extensions: []
|
|
132
|
+
extra_rdoc_files: []
|
|
133
|
+
files:
|
|
134
|
+
- .gitignore
|
|
135
|
+
- .rspec
|
|
136
|
+
- Gemfile
|
|
137
|
+
- LICENSE.txt
|
|
138
|
+
- README.md
|
|
139
|
+
- Rakefile
|
|
140
|
+
- lib/generators/model_history/migration/migration_generator.rb
|
|
141
|
+
- lib/generators/model_history/migration/templates/active_record/migration.rb
|
|
142
|
+
- lib/model_history.rb
|
|
143
|
+
- lib/model_history/model_history_mixin.rb
|
|
144
|
+
- lib/model_history/model_history_record.rb
|
|
145
|
+
- lib/model_history/version.rb
|
|
146
|
+
- model_history.gemspec
|
|
147
|
+
- spec/fake_app.rb
|
|
148
|
+
- spec/models/user_spec.rb
|
|
149
|
+
- spec/spec_helper.rb
|
|
150
|
+
homepage: https://github.com/patorash/model_history
|
|
151
|
+
licenses:
|
|
152
|
+
- MIT
|
|
153
|
+
post_install_message:
|
|
154
|
+
rdoc_options: []
|
|
155
|
+
require_paths:
|
|
156
|
+
- lib
|
|
157
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
158
|
+
none: false
|
|
159
|
+
requirements:
|
|
160
|
+
- - ! '>='
|
|
161
|
+
- !ruby/object:Gem::Version
|
|
162
|
+
version: '0'
|
|
163
|
+
segments:
|
|
164
|
+
- 0
|
|
165
|
+
hash: 1898891071957235163
|
|
166
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
167
|
+
none: false
|
|
168
|
+
requirements:
|
|
169
|
+
- - ! '>='
|
|
170
|
+
- !ruby/object:Gem::Version
|
|
171
|
+
version: '0'
|
|
172
|
+
segments:
|
|
173
|
+
- 0
|
|
174
|
+
hash: 1898891071957235163
|
|
175
|
+
requirements: []
|
|
176
|
+
rubyforge_project:
|
|
177
|
+
rubygems_version: 1.8.25
|
|
178
|
+
signing_key:
|
|
179
|
+
specification_version: 3
|
|
180
|
+
summary: Model History is a simple gem that allows you to keep track of changes to
|
|
181
|
+
specific fields in your Rails models using the ActiveRecord::Dirty module.
|
|
182
|
+
test_files:
|
|
183
|
+
- spec/fake_app.rb
|
|
184
|
+
- spec/models/user_spec.rb
|
|
185
|
+
- spec/spec_helper.rb
|