mongoid-historicals 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,5 @@
1
+ Mongoid Historicals
2
+ ===================
3
+
4
+ Record a snapshot of current values to reference later as historical values. Useful for showing changes in values over time.
5
+
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => :test
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs.push "lib"
7
+ t.pattern = "test/*_test.rb"
8
+ end
@@ -0,0 +1,10 @@
1
+ module Mongoid
2
+ module Historicals
3
+ class Record
4
+ include Mongoid::Document
5
+ include Mongoid::Timestamps::Created
6
+
7
+ field :'_label', type: String
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ module Mongoid
2
+ module Historicals
3
+
4
+ VERSION = "0.1.0"
5
+
6
+ end
7
+ end
@@ -0,0 +1,103 @@
1
+ require 'mongoid'
2
+ require_relative 'historicals/version'
3
+ require_relative 'historicals/record'
4
+
5
+ module Mongoid
6
+ module Historicals
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ embeds_many :historicals,
11
+ class_name: "Mongoid::Historicals::Record"
12
+
13
+ class_attribute :historical_attributes
14
+ class_attribute :historical_options
15
+ end
16
+
17
+ # Save the current values for historicals by label
18
+ #
19
+ def record!(label = nil)
20
+ label ||= Time.now
21
+ record = historical(label) || self.historicals.build(:'_label' => labelize(label))
22
+
23
+ self.class.historical_attributes.each do |attr|
24
+ record[attr] = self.send(attr)
25
+ end
26
+
27
+ record.save!
28
+ destroy_old_historicals!
29
+ record
30
+ end
31
+
32
+ def historical(label)
33
+ self.historicals.where(:'_label' => labelize(label)).first
34
+ end
35
+
36
+ def historical_difference(attr, label, options = {})
37
+ opts = {
38
+ default: 0
39
+ }.merge(options)
40
+
41
+ record = historical(labelize(label))
42
+
43
+ begin
44
+ self[attr] - record[attr]
45
+ rescue # Pokemon exception handling, but actually seems appropriate here
46
+ opts[:default]
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def labelize(label)
53
+ if label.is_a?(String) || label.is_a?(Symbol)
54
+ label.to_s
55
+ elsif label.respond_to?(:to_datetime)
56
+ dt = label.to_datetime
57
+
58
+ case self.class.historical_options[:frequency]
59
+ when :monthly
60
+ dt.strftime("%Y/%m")
61
+ when :weekly
62
+ dt.strftime("%G-%V")
63
+ else # default to `:daily`
64
+ dt.strftime("%Y/%m/%d")
65
+ end
66
+ else
67
+ raise("`label` must be a String, Symbol or respond to :to_datetime")
68
+ end
69
+ end
70
+
71
+ def destroy_old_historicals!
72
+ if self.class.historical_options[:max]
73
+ records = self.historicals.asc(:created_at)
74
+
75
+ if (num_to_delete = records.size - self.class.historical_options[:max]) > 0
76
+ records[0, num_to_delete].each do |r|
77
+ self.historicals.delete(r)
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ module ClassMethods
84
+
85
+ # This model should record historical values for the specified
86
+ # attributes.
87
+ #
88
+ # Options:
89
+ #
90
+ # <tt>max</tt>: The maximum number of entries to store (default: none)
91
+ # <tt>frequency</tt>: :monthly, :weekly, or :daily (default: :daily)
92
+ #
93
+ def historicals(*attrs)
94
+ options = attrs.extract_options!
95
+ options[:max] ||= nil
96
+ options[:frequency] ||= :daily
97
+
98
+ self.historical_options = options
99
+ self.historical_attributes = attrs
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,6 @@
1
+ test:
2
+ sessions:
3
+ default:
4
+ database: mongoid_historicals_test
5
+ hosts:
6
+ - localhost:27017
@@ -0,0 +1,9 @@
1
+ class Player
2
+ include Mongoid::Document
3
+ include Mongoid::Historicals
4
+
5
+ field :name, type: String
6
+ field :score, type: Float
7
+
8
+ historicals :score
9
+ end
@@ -0,0 +1,173 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe Mongoid::Historicals do
4
+ describe ".historical_attributes" do
5
+ it "should include specified attributes" do
6
+ Player.historical_attributes.must_include :score
7
+ end
8
+
9
+ it "should not include other attributes" do
10
+ Player.historical_attributes.wont_include :name
11
+ end
12
+ end
13
+
14
+ describe ".historical_options" do
15
+ it "should have default `max` of nil" do
16
+ Player.historical_options[:max].must_equal nil
17
+ end
18
+
19
+ it "should have a default `frequency` of :daily" do
20
+ Player.historical_options[:frequency].must_equal :daily
21
+ end
22
+ end
23
+
24
+ describe "#record!" do
25
+ before do
26
+ @player = Player.create!(name: "Lytol", score: 95.0)
27
+ @record = @player.record!('test')
28
+ end
29
+
30
+ it "should add a record to historicals" do
31
+ @player.historicals.wont_equal []
32
+ end
33
+
34
+ it "should label with specified label" do
35
+ @record._label.must_equal 'test'
36
+ end
37
+
38
+ it "should timestamp the record" do
39
+ @record.created_at.wont_equal nil
40
+ end
41
+
42
+ it "should have existing values for specified attrbutes in record" do
43
+ @record.score.must_equal 95.0
44
+ end
45
+
46
+ it "should not have values for unspecified attributes in record" do
47
+ @record.wont_respond_to :name
48
+ end
49
+
50
+ describe "when labelled record already exists" do
51
+ it "should overwrite existing record" do
52
+ @player.update_attribute(:score, 90.0)
53
+ record = @player.record!('test')
54
+ @player.historicals.size.must_equal 1
55
+ @player.historicals.must_include record
56
+ end
57
+ end
58
+
59
+ describe "when `max` records is exceeded" do
60
+ before do
61
+ @player.class.historical_options[:max] = 5
62
+ 5.times do |i|
63
+ @player.record!(i.to_s)
64
+ end
65
+ end
66
+
67
+ after do
68
+ @player.class.historical_options[:max] = nil
69
+ end
70
+
71
+ it "should delete the oldest records" do
72
+ oldest_record = @player.historicals.desc(:created_at).last
73
+ @player.record!('test')
74
+ @player.historicals.wont_include oldest_record
75
+ end
76
+ end
77
+
78
+ describe "when labelled with DateTime" do
79
+ before do
80
+ @player.update_attribute(:score, 54.0)
81
+ @player.record!(5.days.ago)
82
+ @player.update_attribute(:score, 65.0)
83
+ end
84
+
85
+ it "should be retrievable with DateTime" do
86
+ record = @player.historical(5.days.ago)
87
+ record.score.must_equal 54.0
88
+ end
89
+ end
90
+ end
91
+
92
+ describe "#historical" do
93
+ before do
94
+ @player = Player.create!(name: "Lytol", score: 95.0)
95
+ @player.record!('test')
96
+ end
97
+
98
+ describe "when record exists" do
99
+ it "should return record" do
100
+ @record = @player.historical('test')
101
+ @record.must_be_instance_of(Mongoid::Historicals::Record)
102
+ @record._label.must_equal 'test'
103
+ end
104
+ end
105
+
106
+ describe "when record does not exist" do
107
+ it "should return nil" do
108
+ @record = @player.historical('unknown-label')
109
+ @record.must_equal nil
110
+ end
111
+ end
112
+ end
113
+
114
+ describe "#historicals" do
115
+ before do
116
+ @player = Player.create!(name: "Lytol", score: 95.0)
117
+ end
118
+
119
+ describe "before any recording" do
120
+ it "should be empty" do
121
+ @player.historicals.must_equal []
122
+ end
123
+ end
124
+ end
125
+
126
+ describe "#historical_difference" do
127
+ before do
128
+ @player = Player.create!(name: "Lytol", score: 90.5)
129
+ @player.record!('test')
130
+ @player.update_attribute(:score, 92.0)
131
+ end
132
+
133
+ it "should return difference from specified label" do
134
+ @player.historical_difference(:score, 'test').must_equal 1.5
135
+ end
136
+
137
+ describe "when there is no historical value" do
138
+ before do
139
+ @player = Player.create!(name: "Lytol", score: nil)
140
+ @player.record!('test')
141
+ @player.update_attribute(:score, 92.0)
142
+ end
143
+
144
+ it "should return 0" do
145
+ @player.historical_difference(:score, 'test').must_equal 0
146
+ end
147
+
148
+ describe "and a default is provided" do
149
+ it "should return specified `default`" do
150
+ @player.historical_difference(:score, 'test', default: 'none').must_equal 'none'
151
+ end
152
+ end
153
+ end
154
+
155
+ describe "when there is no labeled record" do
156
+ before do
157
+ @player = Player.create!(name: "Lytol", score: 90.0)
158
+ @player.record!('test')
159
+ @player.update_attribute(:score, 92.0)
160
+ end
161
+
162
+ it "should return 0" do
163
+ @player.historical_difference(:score, 'invalid').must_equal 0
164
+ end
165
+
166
+ describe "and a default is provided" do
167
+ it "should return specified `default`" do
168
+ @player.historical_difference(:score, 'invalid', default: 'none').must_equal 'none'
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,7 @@
1
+ require 'mongoid'
2
+ require 'minitest/autorun'
3
+
4
+ require_relative '../lib/mongoid/historicals'
5
+ require_relative 'example/player'
6
+
7
+ Mongoid.load!(File.join(File.dirname(__FILE__), "example/mongoid.yml"), :test)
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongoid-historicals
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brian Smith
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mongoid
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
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: minitest
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: 2.6.2
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: 2.6.2
62
+ description: ''
63
+ email: bsmith@swig505.com
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - Rakefile
69
+ - lib/mongoid/historicals/record.rb
70
+ - lib/mongoid/historicals/version.rb
71
+ - lib/mongoid/historicals.rb
72
+ - test/example/mongoid.yml
73
+ - test/example/player.rb
74
+ - test/historicals_test.rb
75
+ - test/test_helper.rb
76
+ - README.md
77
+ homepage: http://github.com/Lytol/mongoid-historicals
78
+ licenses: []
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 1.8.23
98
+ signing_key:
99
+ specification_version: 3
100
+ summary: ''
101
+ test_files: []