state_of_the_nation 1.1.2 → 1.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/README.md +20 -22
- data/lib/state_of_the_nation.rb +6 -5
- data/lib/state_of_the_nation/{errors.rb → errors/configuration_error.rb} +0 -1
- data/lib/state_of_the_nation/errors/conflict_error.rb +16 -0
- data/lib/state_of_the_nation/version.rb +1 -1
- data/state_of_the_nation.gemspec +6 -4
- metadata +29 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 611580ac0c208f088555adadcd019dc5a6b424c0
|
4
|
+
data.tar.gz: f1d1819db563048a4b7a980dbe9db17b72989735
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 71c4a3084eeae10dae78e09c1139132671d3d5e5bafe47f4f72deaeac8ecbcb0543d6d10f80d77166ea47ea7ec74d1b275ebf44390f1639383da1c830cb6896e
|
7
|
+
data.tar.gz: aec17ea9744fa0e7dc764944f4084922a4d4e2bed263892273047077a277d21936c36dc4d397057e2bbf00dff8ad2f7a0e81610a25a6bcdaf606bb3ec9402c26
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -2,9 +2,11 @@
|
|
2
2
|
|
3
3
|
[![Build Status](https://travis-ci.org/intercom/state_of_the_nation.svg?branch=master)](https://travis-ci.org/intercom/state_of_the_nation)
|
4
4
|
|
5
|
-
StateOfTheNation
|
5
|
+
StateOfTheNation helps model data whose _active_ state changes over time. It provides out-of-the-box query methods to locate the record or records active at any moment. Optionally, it also enforces _uniquely_ active constraints at the application level – ensuring that only one record in a collection is active at once.
|
6
6
|
|
7
|
-
|
7
|
+
## Example
|
8
|
+
|
9
|
+
Take elected officials in the US Government: multiple Senators are in office (i.e. active) at any point in time, but there's only one President.
|
8
10
|
|
9
11
|
```ruby
|
10
12
|
class Country < ActiveRecord::Base
|
@@ -25,8 +27,6 @@ class Senator < ActiveRecord::Base
|
|
25
27
|
belongs_to :country
|
26
28
|
considered_active.from(:entered_office_at).until(:left_office_at)
|
27
29
|
end
|
28
|
-
|
29
|
-
|
30
30
|
```
|
31
31
|
|
32
32
|
With this collection of models we can easy record and query the list of elected officials at any point in time, and be confident that any new records that we create don't collide.
|
@@ -34,43 +34,42 @@ With this collection of models we can easy record and query the list of elected
|
|
34
34
|
```ruby
|
35
35
|
|
36
36
|
usa = Country.create(name: "United States of America")
|
37
|
-
obama = usa.presidents.create!(entered_office_at: Date.new(2009, 1, 20)
|
38
|
-
|
39
|
-
wyden = usa.senators.create!(entered_office_at: Date.new(1996, 2, 6), left_office_at: nil, name: "Ron Wyden")
|
40
|
-
boxer = usa.senators.create!(entered_office_at: Date.new(1993, 1, 3), left_office_at: nil, name: "Barbara Boxer")
|
37
|
+
obama = usa.presidents.create!(name: "Barack Obama", entered_office_at: Date.new(2009, 1, 20))
|
41
38
|
|
42
|
-
usa.
|
39
|
+
usa.senators.create!(name: "Ron Wyden", entered_office_at: Date.new(1996, 2, 6))
|
40
|
+
usa.senators.create!(name: "Barbara Boxer", entered_office_at: Date.new(1993, 1, 3))
|
41
|
+
usa.senators.create!(name: "Alan Cranston", entered_office_at: Date.new(1969, 1, 3), left_office_at: Date.new(1993, 1, 3))
|
43
42
|
|
44
|
-
|
43
|
+
usa.active_president(Date.new(2015))
|
44
|
+
# => President(id: 1, name: "Barack Obama", …)
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
# Senator(id: 1, name: "Ron Wyden"),
|
49
|
-
# Senator(id: 2, name: "Barbara Boxer")
|
50
|
-
# ...
|
51
|
-
# ]
|
46
|
+
obama.active?
|
47
|
+
#=> true
|
52
48
|
|
49
|
+
usa.active_senators(Date.new(2015))
|
50
|
+
# => [Senator(id: 1, name: "Ron Wyden", …), Senator(id: 2, name: "Barbara Boxer", …)]
|
53
51
|
|
52
|
+
usa.presidents.create!(name: "Mitt Romney", entered_office_at: Date.new(2013, 1, 20))
|
53
|
+
# => StateOfTheNation::ConflictError
|
54
54
|
```
|
55
|
+
|
55
56
|
## IdentityCache Support
|
56
57
|
|
57
|
-
StateOfTheNation optionally supports fetching
|
58
|
+
StateOfTheNation optionally supports fetching records through [IdentityCache](https://github.com/Shopify/identity_cache) instead of reading directly from the database.
|
58
59
|
|
59
|
-
For example if the Country model uses IdentityCache to cache the has_many relationship to President
|
60
|
+
For example if the `Country` model uses IdentityCache to cache the `has_many` relationship to `President`, you can instruct StateOfTheNation to fetch from the cache by calling `.with_identity_cache` on your `has_active` or `has_uniquely_active` definitions:
|
60
61
|
|
61
62
|
```ruby
|
62
63
|
class Country
|
63
64
|
include IdentityCache
|
64
65
|
include StateOfTheNation
|
65
|
-
|
66
|
+
|
66
67
|
has_many(:presidents)
|
67
68
|
cache_has_many(:presidents)
|
68
69
|
has_uniquely_active(:president).with_identity_cache
|
69
70
|
end
|
70
71
|
```
|
71
72
|
|
72
|
-
Now every time the `Country#active_president` method is called StateOfTheNation will read through the IdentityCache methods and avoid a SELECT operation if possible.
|
73
|
-
|
74
73
|
## Installation
|
75
74
|
|
76
75
|
Add this line to your application's Gemfile:
|
@@ -96,4 +95,3 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
96
95
|
## Contributing
|
97
96
|
|
98
97
|
Bug reports and pull requests are welcome on GitHub at https://github.com/intercom/state_of_the_nation. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org/) code of conduct.
|
99
|
-
|
data/lib/state_of_the_nation.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
require "state_of_the_nation/errors"
|
1
|
+
require "state_of_the_nation/errors/conflict_error"
|
2
|
+
require "state_of_the_nation/errors/configuration_error"
|
2
3
|
require "state_of_the_nation/version"
|
3
4
|
require "state_of_the_nation/query_string"
|
4
5
|
require "active_support/all"
|
@@ -105,13 +106,13 @@ module StateOfTheNation
|
|
105
106
|
|
106
107
|
def prevent_active_collisions
|
107
108
|
return unless prevent_multiple_active
|
108
|
-
raise ConfigurationError
|
109
|
+
raise ConfigurationError if bad_configuration?
|
109
110
|
return unless model.present?
|
110
111
|
|
111
|
-
raise ConflictError.new if
|
112
|
+
raise ConflictError.new(self, other_records_active_in_range) if other_records_active_in_range.any?
|
112
113
|
end
|
113
114
|
|
114
|
-
def
|
115
|
+
def other_records_active_in_range
|
115
116
|
records = self.class.where(parent_association => model)
|
116
117
|
# all records scoped to the model (e.g. all subscriptions for a customer)
|
117
118
|
|
@@ -126,7 +127,7 @@ module StateOfTheNation
|
|
126
127
|
# find competing records which *finish* being active AFTER this record *starts* being active
|
127
128
|
# (or ones which are not set to finish being active)
|
128
129
|
|
129
|
-
records
|
130
|
+
records
|
130
131
|
end
|
131
132
|
|
132
133
|
def model
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module StateOfTheNation
|
2
|
+
class ConflictError < StandardError
|
3
|
+
def initialize(record, conflicting_records)
|
4
|
+
super(<<-MSG.strip_heredoc)
|
5
|
+
Attempted to commit record
|
6
|
+
|
7
|
+
#{record.inspect}
|
8
|
+
|
9
|
+
But encountered a conflict with timestamps on the following records
|
10
|
+
|
11
|
+
#{conflicting_records.map { |record| "- #{record.inspect}" }.join("\n")}
|
12
|
+
|
13
|
+
MSG
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/state_of_the_nation.gemspec
CHANGED
@@ -24,14 +24,16 @@ Gem::Specification.new do |spec|
|
|
24
24
|
|
25
25
|
spec.add_development_dependency "bundler"
|
26
26
|
spec.add_development_dependency "database_cleaner"
|
27
|
-
spec.add_development_dependency "mysql"
|
27
|
+
# spec.add_development_dependency "mysql"
|
28
28
|
spec.add_development_dependency "pg"
|
29
|
+
spec.add_development_dependency "pry", "0.12.2"
|
30
|
+
spec.add_development_dependency "rb-readline"
|
29
31
|
spec.add_development_dependency "rake", "~> 10.0"
|
30
32
|
spec.add_development_dependency "rspec", "~> 3.0"
|
31
33
|
spec.add_development_dependency "rspec-rails"
|
32
34
|
spec.add_development_dependency "shoulda-matchers"
|
33
|
-
spec.add_development_dependency "sqlite3"
|
35
|
+
spec.add_development_dependency "sqlite3", "1.4.0"
|
34
36
|
|
35
|
-
spec.add_runtime_dependency "activesupport", ">=
|
36
|
-
spec.add_runtime_dependency "activerecord", ">=
|
37
|
+
spec.add_runtime_dependency "activesupport", ">= 5.0.0"
|
38
|
+
spec.add_runtime_dependency "activerecord", ">= 5.0.0"
|
37
39
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: state_of_the_nation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Patrick O'Doherty
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2019-03-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -40,7 +40,7 @@ dependencies:
|
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: '0'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
|
-
name:
|
43
|
+
name: pg
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
46
|
- - ">="
|
@@ -54,7 +54,21 @@ dependencies:
|
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: '0'
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
|
-
name:
|
57
|
+
name: pry
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - '='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 0.12.2
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - '='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.12.2
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rb-readline
|
58
72
|
requirement: !ruby/object:Gem::Requirement
|
59
73
|
requirements:
|
60
74
|
- - ">="
|
@@ -127,44 +141,44 @@ dependencies:
|
|
127
141
|
name: sqlite3
|
128
142
|
requirement: !ruby/object:Gem::Requirement
|
129
143
|
requirements:
|
130
|
-
- -
|
144
|
+
- - '='
|
131
145
|
- !ruby/object:Gem::Version
|
132
|
-
version:
|
146
|
+
version: 1.4.0
|
133
147
|
type: :development
|
134
148
|
prerelease: false
|
135
149
|
version_requirements: !ruby/object:Gem::Requirement
|
136
150
|
requirements:
|
137
|
-
- -
|
151
|
+
- - '='
|
138
152
|
- !ruby/object:Gem::Version
|
139
|
-
version:
|
153
|
+
version: 1.4.0
|
140
154
|
- !ruby/object:Gem::Dependency
|
141
155
|
name: activesupport
|
142
156
|
requirement: !ruby/object:Gem::Requirement
|
143
157
|
requirements:
|
144
158
|
- - ">="
|
145
159
|
- !ruby/object:Gem::Version
|
146
|
-
version:
|
160
|
+
version: 5.0.0
|
147
161
|
type: :runtime
|
148
162
|
prerelease: false
|
149
163
|
version_requirements: !ruby/object:Gem::Requirement
|
150
164
|
requirements:
|
151
165
|
- - ">="
|
152
166
|
- !ruby/object:Gem::Version
|
153
|
-
version:
|
167
|
+
version: 5.0.0
|
154
168
|
- !ruby/object:Gem::Dependency
|
155
169
|
name: activerecord
|
156
170
|
requirement: !ruby/object:Gem::Requirement
|
157
171
|
requirements:
|
158
172
|
- - ">="
|
159
173
|
- !ruby/object:Gem::Version
|
160
|
-
version:
|
174
|
+
version: 5.0.0
|
161
175
|
type: :runtime
|
162
176
|
prerelease: false
|
163
177
|
version_requirements: !ruby/object:Gem::Requirement
|
164
178
|
requirements:
|
165
179
|
- - ">="
|
166
180
|
- !ruby/object:Gem::Version
|
167
|
-
version:
|
181
|
+
version: 5.0.0
|
168
182
|
description: State of the Nation makes modeling object history easy.
|
169
183
|
email:
|
170
184
|
- patrick@intercom.io
|
@@ -183,7 +197,8 @@ files:
|
|
183
197
|
- bin/console
|
184
198
|
- bin/setup
|
185
199
|
- lib/state_of_the_nation.rb
|
186
|
-
- lib/state_of_the_nation/errors.rb
|
200
|
+
- lib/state_of_the_nation/errors/configuration_error.rb
|
201
|
+
- lib/state_of_the_nation/errors/conflict_error.rb
|
187
202
|
- lib/state_of_the_nation/query_string.rb
|
188
203
|
- lib/state_of_the_nation/version.rb
|
189
204
|
- state_of_the_nation.gemspec
|
@@ -207,9 +222,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
207
222
|
version: '0'
|
208
223
|
requirements: []
|
209
224
|
rubyforge_project:
|
210
|
-
rubygems_version: 2.
|
225
|
+
rubygems_version: 2.6.14.1
|
211
226
|
signing_key:
|
212
227
|
specification_version: 4
|
213
228
|
summary: An easy way to model state that changes over time with ActiveRecord
|
214
229
|
test_files: []
|
215
|
-
has_rdoc:
|