historical 0.2.1 → 0.2.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.
@@ -0,0 +1,19 @@
1
+ module Historical::Models
2
+ class ModelVersion
3
+ # A meta model which stores additional data to each new version (could be used for audits).
4
+ class Meta
5
+ include MongoMapper::EmbeddedDocument
6
+ extend Historical::MongoMapperEnhancements
7
+
8
+ key :created_at, Time
9
+
10
+ # Retrieve customized class definition for a record class (e.g. TopicMeta, MessageMeta)
11
+ # @return [Class]
12
+ def self.for_class(source_class)
13
+ Historical::Models::Pool.pooled(Historical::Models::Pool.pooled_name(source_class, self)) do
14
+ Class.new(self)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ module Historical::Models
2
+ # @private
3
+ # cached classes are stored here
4
+ module Pool
5
+ @@class_pool = {}
6
+ mattr_accessor :class_pool
7
+
8
+ # Gets a class from the pool or sets it if the class couldn't be found.
9
+ def self.pooled(name)
10
+ class_pool[name] ||= begin
11
+ yield.tap do |cls|
12
+ const_set(name, cls)
13
+ end
14
+ end
15
+ end
16
+
17
+ # Removes all stored classes from the pool
18
+ def self.clear!
19
+ class_pool.each do |k,v|
20
+ remove_const(k)
21
+ end
22
+
23
+ self.class_pool = {}
24
+ end
25
+
26
+ # Generate unique classnames within the pool.
27
+ def self.pooled_name(specialized_for, parent)
28
+ "#{specialized_for.name.demodulize}#{parent.name.demodulize}"
29
+ end
30
+ end
31
+ end
@@ -1,5 +1,12 @@
1
1
  module Historical
2
+ # Contains some helper methods for better MongoMapper integration.
2
3
  module MongoMapperEnhancements
4
+
5
+ # Simple `belongs_to` relation to an ActiveRecord model.
6
+ #
7
+ # @param name The name of the relation.
8
+ # @option options [String] :class_name (name.classify) The class name of the model (if it can't be guessed from the name)
9
+ # @option options [true,false] :polymorphic (false) Will create an additional {name}_type key in the model.
3
10
  def belongs_to_active_record(name, options = {})
4
11
  ar_id_field = "#{name}_id"
5
12
  ar_type_field = "#{name}_type"
@@ -8,6 +15,7 @@ module Historical
8
15
  class_name = options.delete(:class_name)
9
16
  model_class = nil
10
17
 
18
+ # classname
11
19
  unless polymorphic
12
20
  if class_name
13
21
  model_class = class_name.is_a?(Class) ? class_name.name : class_name.classify
@@ -16,9 +24,11 @@ module Historical
16
24
  end
17
25
  end
18
26
 
27
+ # define the keys
19
28
  key ar_id_field, Integer, options
20
29
  key ar_type_field, String, options if polymorphic
21
30
 
31
+ # getter
22
32
  define_method name do
23
33
  if id = send(ar_id_field)
24
34
  if polymorphic
@@ -32,6 +42,7 @@ module Historical
32
42
  end
33
43
  end
34
44
 
45
+ # setter
35
46
  define_method "#{name}=" do |val|
36
47
  id = type = nil
37
48
 
@@ -2,9 +2,11 @@ require 'historical'
2
2
  require 'rails'
3
3
 
4
4
  module Historical
5
+ # The railtie to be loaded by Rails.
5
6
  class Railtie < Rails::Railtie
6
- config.after_initialize do
7
- ActiveRecord::Base.send(:extend, Historical::ActiveRecord)
7
+ initializer "historical.attach_to_active_record" do
8
+ ::ActiveRecord::Base.send(:extend, Historical::ActiveRecord)
9
+ Historical.boot!
8
10
  end
9
11
  end
10
12
  end
@@ -10,6 +10,34 @@ describe "A historical model" do
10
10
  is_historical
11
11
  end
12
12
 
13
+ context "when loading model from database without history" do
14
+ before :each do
15
+ Historical.boot!
16
+ @msg = Message.find(1)
17
+ @msg.history.destroy
18
+ end
19
+
20
+ it "should call the creation-generator" do
21
+ o = Object.new
22
+ o.stub!(:created_at=).once.with(@msg.created_at)
23
+ o.stub!(:save!).once
24
+
25
+ @msg.should_receive(:spawn_version).once.with(:create).and_return(o)
26
+ @msg.history
27
+ end
28
+
29
+ it "should auto-generate a creation" do
30
+ @msg.history.version_index.should == 0
31
+ @msg.history.creation.should_not be_nil
32
+ end
33
+
34
+ it "should have the original timestamps" do
35
+ @msg.history.creation.tap do |e|
36
+ e.created_at.should == @msg.created_at
37
+ end
38
+ end
39
+ end
40
+
13
41
  context "when created" do
14
42
  it "should persist" do
15
43
  msg = Message.new(:title => "Hello")
@@ -41,6 +69,8 @@ describe "A historical model" do
41
69
  version_count = lambda { @msg.history.versions.count }
42
70
 
43
71
  lambda do
72
+ @msg.should_not be_new_record
73
+ @msg.changes.should be_empty
44
74
  @msg.save!
45
75
  end.should_not change(version_count, :call)
46
76
  end
@@ -78,7 +108,7 @@ describe "A historical model" do
78
108
  grouped[:body].old_value.should == nil
79
109
 
80
110
  grouped[:votes].new_value.should == 42
81
- grouped[:votes].old_value.should == nil
111
+ grouped[:votes].old_value.should == 0
82
112
 
83
113
  grouped[:read].new_value.should == true
84
114
  grouped[:read].old_value.should == false
@@ -123,13 +153,13 @@ describe "A historical model" do
123
153
 
124
154
  it "should create attribute-diffs in update-diff" do
125
155
  @msg.history.tap do |h|
126
- model_diff = h.updates.first.diff
127
- model_diff.changes.count.should == 1
156
+ diff = h.updates.first.diff
157
+ diff.changes.count.should == 1
128
158
 
129
- model_diff.changes.first.tap do |diff|
130
- diff.attribute.should == "title"
131
- diff.old_value.should == @first_title
132
- diff.new_value.should == @new_title
159
+ diff.changes.first.tap do |attr_diff|
160
+ attr_diff.attribute.should == "title"
161
+ attr_diff.old_value.should == @first_title
162
+ attr_diff.new_value.should == @new_title
133
163
  end
134
164
  end
135
165
  end
@@ -232,15 +262,15 @@ describe "A historical model" do
232
262
  it "should not break handy queries for chained restorations" do
233
263
  @message = Message.create(:title => "one")
234
264
  @message.update_attributes(:title => "two").should be_true
235
- @message.history.own_version.version_index.should == 1
265
+ @message.history.version_index.should == 1
236
266
  @message.version.should == 1
237
267
 
238
268
  previous = @message.history.restore(:previous)
239
- previous.history.own_version.version_index.should == 0
269
+ previous.history.version_index.should == 0
240
270
  previous.version.should == 0
241
271
 
242
272
  identity = previous.history.restore(:next)
243
- identity.history.own_version.version_index.should == 1
273
+ identity.history.version_index.should == 1
244
274
  identity.version.should == 1
245
275
 
246
276
  identity.should_not be_nil
@@ -254,26 +284,30 @@ describe "A historical model" do
254
284
  cattr_accessor :current_user
255
285
 
256
286
  extend Historical::ActiveRecord
287
+
257
288
  is_historical do
258
- belongs_to_active_record :author, :required => true, :class_name => "User"
289
+ meta do
290
+ belongs_to_active_record :author, :required => true, :class_name => "User"
291
+ end
259
292
 
260
- historical_callback do |diff|
261
- diff.author = AuditedMessage.current_user
293
+ callback do |version|
294
+ version.meta.author = AuditedMessage.current_user
262
295
  end
263
296
  end
264
297
  end
265
298
 
266
299
  before :each do
267
300
  AuditedMessage.current_user = nil
301
+ Historical.boot!
268
302
  end
269
303
 
270
- it "should create custom keys on ModelDiffs" do
304
+ it "should create custom keys on ModelVersion::Diffs" do
271
305
  user = User.create(:name => "Jane Doe")
272
306
  AuditedMessage.current_user = user
273
307
 
274
308
  msg = AuditedMessage.create(:title => "one")
275
309
 
276
- author = msg.history.versions.first.diff.author
310
+ author = msg.history.versions.first.meta.author
277
311
  author.should == user
278
312
  author.id.should == user.id
279
313
  end
@@ -6,8 +6,9 @@ require 'spec/autorun'
6
6
 
7
7
  require 'rubygems'
8
8
 
9
- gem "rails", "= 2.3.8"
9
+ gem "rails", "= 3.0.0"
10
10
 
11
+ require 'ruby-debug'
11
12
  require 'active_support'
12
13
  require 'active_support/test_case'
13
14
  require 'active_record'
@@ -24,7 +25,7 @@ ActiveRecord::Base.silence do
24
25
  create_table :messages do |t|
25
26
  t.string :title
26
27
  t.text :body
27
- t.integer :votes
28
+ t.integer :votes, :default => 0
28
29
  t.datetime :published_at
29
30
  t.date :stamped_on
30
31
  t.decimal :donated, :precision => 10, :scale => 2
@@ -36,6 +37,12 @@ ActiveRecord::Base.silence do
36
37
  t.string :name
37
38
  t.timestamps
38
39
  end
40
+
41
+ execute "
42
+ INSERT INTO
43
+ messages (id, title, body, created_at, updated_at)
44
+ VALUES
45
+ (1, 'existed already', 'hi', '2010-01-01 00:00:00', '2010-01-01 00:00:00')"
39
46
  end
40
47
  end
41
48
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: historical
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 1
10
- version: 0.2.1
9
+ - 2
10
+ version: 0.2.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Marcel Jackwerth
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-26 00:00:00 +02:00
18
+ date: 2010-10-08 00:00:00 +02:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -42,20 +42,23 @@ extensions: []
42
42
 
43
43
  extra_rdoc_files:
44
44
  - LICENSE
45
- - README.rdoc
45
+ - README.markdown
46
46
  files:
47
47
  - .gitignore
48
48
  - LICENSE
49
- - README.rdoc
49
+ - README.markdown
50
50
  - Rakefile
51
51
  - VERSION
52
52
  - historical.gemspec
53
53
  - lib/historical.rb
54
54
  - lib/historical/active_record.rb
55
+ - lib/historical/class_builder.rb
55
56
  - lib/historical/model_history.rb
56
57
  - lib/historical/models/attribute_diff.rb
57
- - lib/historical/models/model_diff.rb
58
58
  - lib/historical/models/model_version.rb
59
+ - lib/historical/models/model_version/diff.rb
60
+ - lib/historical/models/model_version/meta.rb
61
+ - lib/historical/models/pool.rb
59
62
  - lib/historical/mongo_mapper_enhancements.rb
60
63
  - lib/historical/railtie.rb
61
64
  - rails/init.rb
@@ -1,17 +0,0 @@
1
- = historical2
2
-
3
- Description goes here.
4
-
5
- == Note on Patches/Pull Requests
6
-
7
- * Fork the project.
8
- * Make your feature addition or bug fix.
9
- * Add tests for it. This is important so I don't break it in a
10
- future version unintentionally.
11
- * Commit, do not mess with rakefile, version, or history.
12
- (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
- * Send me a pull request. Bonus points for topic branches.
14
-
15
- == Copyright
16
-
17
- Copyright (c) 2010 Marcel Jackwerth. See LICENSE for details.
@@ -1,90 +0,0 @@
1
- module Historical::Models
2
- class ModelDiff
3
- include MongoMapper::EmbeddedDocument
4
- extend Historical::MongoMapperEnhancements
5
- class_inheritable_accessor :historical_callbacks
6
-
7
- validates_associated :changes
8
-
9
- key :_type, String
10
- key :diff_type, String, :required => true
11
- many :changes, :class_name => "Historical::Models::AttributeDiff"
12
-
13
- delegate :creation?, :update?, :to => :diff_type_inquirer
14
-
15
- def record
16
- new_version.record
17
- end
18
-
19
- def new_version
20
- @parent || _parent_document
21
- end
22
-
23
- def old_version
24
- new_version.previous
25
- end
26
-
27
- def self.from_versions(from, to)
28
- return from_creation(to) if from.nil?
29
-
30
- generate_from_version(from, 'update').tap do |d|
31
- from.record.attribute_names.each do |attr_name|
32
- attr = attr_name.to_sym
33
- next if Historical::IGNORED_ATTRIBUTES.include? attr
34
-
35
- old_value, new_value = from[attr], to[attr]
36
-
37
- Historical::Models::AttributeDiff.specialized_for(d, attr).new.tap do |ad|
38
- ad.attribute_type = Historical::Models::AttributeDiff.detect_attribute_type(d, attr)
39
- ad.parent = d
40
- ad.old_value = old_value
41
- ad.new_value = new_value
42
- ad.attribute = attr.to_s
43
- d.changes << ad
44
- end if old_value != new_value
45
- end
46
- end
47
- end
48
-
49
- def self.from_creation(to)
50
- generate_from_version(to)
51
- end
52
-
53
- protected
54
-
55
- def diff_type_inquirer
56
- ActiveSupport::StringInquirer.new(diff_type)
57
- end
58
-
59
- def self.historical_callback(&block)
60
- raise "no block given" unless block_given?
61
-
62
- self.historical_callbacks ||= []
63
- self.historical_callbacks << block
64
- end
65
-
66
-
67
- def self.generate_from_version(version, type = 'creation')
68
- for_class(version.record.class).new.tap do |d|
69
- d.diff_type = type
70
- d.instance_variable_set :@parent, version
71
-
72
- if cbs = d.class.historical_callbacks
73
- cbs.each do |c|
74
- c.call(d)
75
- end
76
- end
77
- end
78
- end
79
-
80
- def self.for_class(source_class)
81
- Historical::Models::Pool.pooled(Historical::Models::Pool.pooled_name(source_class, self)) do
82
- Class.new(self).tap do |cls|
83
- source_class.historical_customizations.each do |customization|
84
- cls.instance_eval(&customization)
85
- end
86
- end
87
- end
88
- end
89
- end
90
- end