mongoid-historicals 0.1.0
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/README.md +5 -0
- data/Rakefile +8 -0
- data/lib/mongoid/historicals/record.rb +10 -0
- data/lib/mongoid/historicals/version.rb +7 -0
- data/lib/mongoid/historicals.rb +103 -0
- data/test/example/mongoid.yml +6 -0
- data/test/example/player.rb +9 -0
- data/test/historicals_test.rb +173 -0
- data/test/test_helper.rb +7 -0
- metadata +101 -0
data/README.md
ADDED
data/Rakefile
ADDED
|
@@ -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,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
|
data/test/test_helper.rb
ADDED
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: []
|