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.
@@ -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
+