hierarchy_snapshot 0.0.1
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/MIT-LICENSE +20 -0
- data/README.md +80 -0
- data/Rakefile +27 -0
- data/lib/generators/hierarchy_snapshot_migration/hierarchy_snapshot_migration_generator.rb +16 -0
- data/lib/generators/hierarchy_snapshot_migration/templates/migration.rb +15 -0
- data/lib/hierarchy_snapshot.rb +12 -0
- data/lib/hierarchy_snapshot/active_record.rb +66 -0
- data/lib/hierarchy_snapshot/configuration.rb +28 -0
- data/lib/hierarchy_snapshot/railtie.rb +10 -0
- data/lib/hierarchy_snapshot/version.rb +3 -0
- data/lib/tasks/hierarchy_snapshot_tasks.rake +4 -0
- metadata +139 -0
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2012 Shaun Mangelsdorf
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# HierarchySnapshot
|
|
2
|
+
|
|
3
|
+
This project adds the ability for ActiveRecord-based projects to automatically
|
|
4
|
+
maintain a snapshot of an object hierarchy when it is updated.
|
|
5
|
+
|
|
6
|
+
# Usage
|
|
7
|
+
|
|
8
|
+
This example was hastily taken from the test cases:
|
|
9
|
+
|
|
10
|
+
class Parent < ActiveRecord::Base
|
|
11
|
+
has_many :children # Standard association
|
|
12
|
+
|
|
13
|
+
keep_hierarchy_snapshot do # Invoke the plugin
|
|
14
|
+
attrs :name # Attributes to retrieve from 'self'
|
|
15
|
+
many :children do # Associations to invoke on 'self'
|
|
16
|
+
attrs :name # Attributes to retrieve from each child
|
|
17
|
+
many :grandchildren do # Associations to invoke on each child
|
|
18
|
+
attrs :name # Attributes to retrieve from each grandchild
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
When a parent object is created, like:
|
|
25
|
+
|
|
26
|
+
p = Parent.create :name => 'test',
|
|
27
|
+
:children_attributes => [ {:name => 'test2',
|
|
28
|
+
:grandchildren_attributes => [ {:name => 'test3'} ],
|
|
29
|
+
} ]
|
|
30
|
+
|
|
31
|
+
This will create a snapshot containing a JSON object that looks like:
|
|
32
|
+
|
|
33
|
+
{"name":"test",
|
|
34
|
+
"children":[{"name":"test2",
|
|
35
|
+
"grandchildren":[{"name":"test3"}]
|
|
36
|
+
}]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
Creating a snapshot when the child records are updated directly requires:
|
|
40
|
+
|
|
41
|
+
p = Parent.find params[:id]
|
|
42
|
+
p.with_snapshot do
|
|
43
|
+
p.children.first.update_attributes! :name => 'test3'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
Creating an object without triggering a snapshot:
|
|
47
|
+
|
|
48
|
+
p = Parent.find params[:id]
|
|
49
|
+
p.without_snapshot do
|
|
50
|
+
p.update_attributes! :name => 'test2'
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
Requiring a user to be recorded with a snapshot:
|
|
54
|
+
|
|
55
|
+
class Parent < ActiveRecord::Base
|
|
56
|
+
has_many :children
|
|
57
|
+
|
|
58
|
+
keep_hierarchy_snapshot do
|
|
59
|
+
require_user
|
|
60
|
+
attrs :name
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
p = Parent.find params[:id]
|
|
65
|
+
p.name = 'test2'
|
|
66
|
+
p.save # Throws an exception
|
|
67
|
+
p.snapshot_user = current_user # snapshot_user is automatically added to your snapshotted models
|
|
68
|
+
p.save # Works correctly
|
|
69
|
+
|
|
70
|
+
or
|
|
71
|
+
|
|
72
|
+
p = Parent.find params[:id]
|
|
73
|
+
|
|
74
|
+
p.with_snapshot do
|
|
75
|
+
p.children.create :name => 'test2'
|
|
76
|
+
end # Throws an exception
|
|
77
|
+
|
|
78
|
+
p.with_snapshot current_user do
|
|
79
|
+
p.children.create :name => 'test2'
|
|
80
|
+
end # Works correctly
|
data/Rakefile
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env rake
|
|
2
|
+
begin
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
rescue LoadError
|
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
6
|
+
end
|
|
7
|
+
begin
|
|
8
|
+
require 'rdoc/task'
|
|
9
|
+
rescue LoadError
|
|
10
|
+
require 'rdoc/rdoc'
|
|
11
|
+
require 'rake/rdoctask'
|
|
12
|
+
RDoc::Task = Rake::RDocTask
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
17
|
+
rdoc.title = 'HierarchySnapshot'
|
|
18
|
+
rdoc.options << '--line-numbers'
|
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
Bundler::GemHelper.install_tasks
|
|
27
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Released under the MIT license. See the LICENSE file for details
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
require 'rails/generators/migration'
|
|
5
|
+
require 'rails/generators/active_record/migration'
|
|
6
|
+
|
|
7
|
+
class HierarchySnapshotMigrationGenerator < Rails::Generators::NamedBase
|
|
8
|
+
include Rails::Generators::Migration
|
|
9
|
+
extend ActiveRecord::Generators::Migration
|
|
10
|
+
|
|
11
|
+
source_root File.join(File.dirname(__FILE__), 'templates')
|
|
12
|
+
|
|
13
|
+
def manifest
|
|
14
|
+
migration_template 'migration.rb', "db/migrate/#{file_name}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class <%= class_name %> < ActiveRecord::Migration
|
|
2
|
+
def self.up
|
|
3
|
+
create_table :hierarchy_snapshots, :force => true do |t|
|
|
4
|
+
t.references :source, :polymorphic => {}
|
|
5
|
+
t.references :user, :polymorphic => {}
|
|
6
|
+
t.text :data
|
|
7
|
+
t.timestamp :created_at
|
|
8
|
+
end
|
|
9
|
+
add_index :hierarchy_snapshots, [:source_id, :source_type], :name => 'source_index'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.down
|
|
13
|
+
drop_table :hierarchy_snapshots
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module HierarchySnapshot
|
|
2
|
+
autoload :ActiveRecord, 'hierarchy_snapshot/active_record'
|
|
3
|
+
autoload :Configuration, 'hierarchy_snapshot/configuration'
|
|
4
|
+
|
|
5
|
+
class Snapshot < ::ActiveRecord::Base
|
|
6
|
+
self.table_name = 'hierarchy_snapshots'
|
|
7
|
+
belongs_to :source, :polymorphic => true
|
|
8
|
+
belongs_to :user, :polymorphic => true
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require 'hierarchy_snapshot/railtie' if defined? Rails
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module HierarchySnapshot
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
module ClassMethods
|
|
4
|
+
def keep_hierarchy_snapshot(&bl)
|
|
5
|
+
unless respond_to? :hierarchy_snapshot_save
|
|
6
|
+
send :include, InstanceMethods
|
|
7
|
+
end
|
|
8
|
+
after_create :hierarchy_snapshot_save
|
|
9
|
+
after_update :hierarchy_snapshot_save
|
|
10
|
+
|
|
11
|
+
class_attribute :hierarchy_snapshot_config
|
|
12
|
+
attr_accessor :snapshot_user
|
|
13
|
+
|
|
14
|
+
self.hierarchy_snapshot_config = Configuration.new(true).tap{|c| c.instance_eval(&bl)}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def without_snapshot
|
|
18
|
+
old = Thread.current[:skip_hierarchy_snapshot]
|
|
19
|
+
begin
|
|
20
|
+
Thread.current[:skip_hierarchy_snapshot] = true
|
|
21
|
+
return yield
|
|
22
|
+
ensure
|
|
23
|
+
Thread.current[:skip_hierarchy_snapshot] = old
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module InstanceMethods
|
|
29
|
+
def with_snapshot(user=nil)
|
|
30
|
+
self.snapshot_user = user
|
|
31
|
+
yield
|
|
32
|
+
hierarchy_snapshot_save
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def without_snapshot(&bl)
|
|
36
|
+
self.class.without_snapshot(&bl)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def hierarchy_snapshot_save
|
|
40
|
+
return if Thread.current[:skip_hierarchy_snapshot]
|
|
41
|
+
|
|
42
|
+
if hierarchy_snapshot_config.require_user? and snapshot_user.nil?
|
|
43
|
+
raise 'No snapshot user was specified, but one is required'
|
|
44
|
+
end
|
|
45
|
+
Snapshot.create :source => self, :data => hierarchy_snapshot_data, :user => snapshot_user
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def hierarchy_snapshot_data
|
|
49
|
+
hierarchy_snapshot_data_for(self, hierarchy_snapshot_config).to_json
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def hierarchy_snapshot_data_for(obj, config)
|
|
53
|
+
{}.tap do |map|
|
|
54
|
+
config.attrs.each do |sym|
|
|
55
|
+
map[sym] = obj.send(sym)
|
|
56
|
+
end
|
|
57
|
+
config.many(nil).each do |sym, config|
|
|
58
|
+
map[sym] = obj.send(sym).to_a.collect do |child|
|
|
59
|
+
hierarchy_snapshot_data_for(child, config)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module HierarchySnapshot
|
|
2
|
+
class Configuration
|
|
3
|
+
def initialize(top=false)
|
|
4
|
+
@top = top
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def require_user
|
|
8
|
+
raise 'Only valid at top level' unless @top
|
|
9
|
+
@require_user = true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def require_user?
|
|
13
|
+
!!@require_user
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def attrs(*args)
|
|
17
|
+
@attrs ||= []
|
|
18
|
+
@attrs += args.flatten
|
|
19
|
+
@attrs
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def many(assoc, &bl)
|
|
23
|
+
@many ||= {}
|
|
24
|
+
@many[assoc] = Configuration.new.tap{|c| c.instance_eval(&bl)} unless assoc.nil?
|
|
25
|
+
@many
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
require 'hierarchy_snapshot'
|
|
2
|
+
require 'rails'
|
|
3
|
+
|
|
4
|
+
module HierarchySnapshot
|
|
5
|
+
class Railtie < Rails::Railtie
|
|
6
|
+
initializer 'hierarchy_snapshot.active_record_hooks' do
|
|
7
|
+
::ActiveRecord::Base.send :extend, HierarchySnapshot::ActiveRecord::ClassMethods
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: hierarchy_snapshot
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 29
|
|
5
|
+
prerelease:
|
|
6
|
+
segments:
|
|
7
|
+
- 0
|
|
8
|
+
- 0
|
|
9
|
+
- 1
|
|
10
|
+
version: 0.0.1
|
|
11
|
+
platform: ruby
|
|
12
|
+
authors:
|
|
13
|
+
- Shaun Mangelsdorf
|
|
14
|
+
autorequire:
|
|
15
|
+
bindir: bin
|
|
16
|
+
cert_chain: []
|
|
17
|
+
|
|
18
|
+
date: 2012-11-20 00:00:00 Z
|
|
19
|
+
dependencies:
|
|
20
|
+
- !ruby/object:Gem::Dependency
|
|
21
|
+
prerelease: false
|
|
22
|
+
name: rails
|
|
23
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
|
24
|
+
none: false
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
hash: 7
|
|
29
|
+
segments:
|
|
30
|
+
- 3
|
|
31
|
+
- 0
|
|
32
|
+
version: "3.0"
|
|
33
|
+
- - <
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
hash: 1
|
|
36
|
+
segments:
|
|
37
|
+
- 3
|
|
38
|
+
- 3
|
|
39
|
+
version: "3.3"
|
|
40
|
+
requirement: *id001
|
|
41
|
+
type: :development
|
|
42
|
+
- !ruby/object:Gem::Dependency
|
|
43
|
+
prerelease: false
|
|
44
|
+
name: rspec
|
|
45
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
|
46
|
+
none: false
|
|
47
|
+
requirements:
|
|
48
|
+
- - ">="
|
|
49
|
+
- !ruby/object:Gem::Version
|
|
50
|
+
hash: 3
|
|
51
|
+
segments:
|
|
52
|
+
- 0
|
|
53
|
+
version: "0"
|
|
54
|
+
requirement: *id002
|
|
55
|
+
type: :development
|
|
56
|
+
- !ruby/object:Gem::Dependency
|
|
57
|
+
prerelease: false
|
|
58
|
+
name: sqlite3
|
|
59
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
|
60
|
+
none: false
|
|
61
|
+
requirements:
|
|
62
|
+
- - ">="
|
|
63
|
+
- !ruby/object:Gem::Version
|
|
64
|
+
hash: 3
|
|
65
|
+
segments:
|
|
66
|
+
- 0
|
|
67
|
+
version: "0"
|
|
68
|
+
requirement: *id003
|
|
69
|
+
type: :development
|
|
70
|
+
- !ruby/object:Gem::Dependency
|
|
71
|
+
prerelease: false
|
|
72
|
+
name: ruby-debug
|
|
73
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
|
74
|
+
none: false
|
|
75
|
+
requirements:
|
|
76
|
+
- - ">="
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
hash: 3
|
|
79
|
+
segments:
|
|
80
|
+
- 0
|
|
81
|
+
version: "0"
|
|
82
|
+
requirement: *id004
|
|
83
|
+
type: :development
|
|
84
|
+
description: This project adds the ability for ActiveRecord-based projects to automatically maintain a snapshot of an object hierarchy when it is updated.
|
|
85
|
+
email:
|
|
86
|
+
- s.mangelsdorf@gmail.com
|
|
87
|
+
executables: []
|
|
88
|
+
|
|
89
|
+
extensions: []
|
|
90
|
+
|
|
91
|
+
extra_rdoc_files: []
|
|
92
|
+
|
|
93
|
+
files:
|
|
94
|
+
- lib/hierarchy_snapshot/configuration.rb
|
|
95
|
+
- lib/hierarchy_snapshot/version.rb
|
|
96
|
+
- lib/hierarchy_snapshot/railtie.rb
|
|
97
|
+
- lib/hierarchy_snapshot/active_record.rb
|
|
98
|
+
- lib/tasks/hierarchy_snapshot_tasks.rake
|
|
99
|
+
- lib/hierarchy_snapshot.rb
|
|
100
|
+
- lib/generators/hierarchy_snapshot_migration/hierarchy_snapshot_migration_generator.rb
|
|
101
|
+
- lib/generators/hierarchy_snapshot_migration/templates/migration.rb
|
|
102
|
+
- MIT-LICENSE
|
|
103
|
+
- Rakefile
|
|
104
|
+
- README.md
|
|
105
|
+
homepage: https://github.com/smangelsdorf/hierarchy_snapshot
|
|
106
|
+
licenses: []
|
|
107
|
+
|
|
108
|
+
post_install_message:
|
|
109
|
+
rdoc_options: []
|
|
110
|
+
|
|
111
|
+
require_paths:
|
|
112
|
+
- lib
|
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
114
|
+
none: false
|
|
115
|
+
requirements:
|
|
116
|
+
- - ">="
|
|
117
|
+
- !ruby/object:Gem::Version
|
|
118
|
+
hash: 3
|
|
119
|
+
segments:
|
|
120
|
+
- 0
|
|
121
|
+
version: "0"
|
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
|
+
none: false
|
|
124
|
+
requirements:
|
|
125
|
+
- - ">="
|
|
126
|
+
- !ruby/object:Gem::Version
|
|
127
|
+
hash: 3
|
|
128
|
+
segments:
|
|
129
|
+
- 0
|
|
130
|
+
version: "0"
|
|
131
|
+
requirements: []
|
|
132
|
+
|
|
133
|
+
rubyforge_project:
|
|
134
|
+
rubygems_version: 1.8.24
|
|
135
|
+
signing_key:
|
|
136
|
+
specification_version: 3
|
|
137
|
+
summary: Add object hierarchy snapshot capabilities to ActiveRecord
|
|
138
|
+
test_files: []
|
|
139
|
+
|