hierarchy_snapshot 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module HierarchySnapshot
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :hierarchy_snapshot do
3
+ # # Task goes here
4
+ # 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
+