lookup_by 0.7.0 → 0.8.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +2 -1
- data/Appraisals +11 -0
- data/Gemfile +2 -0
- data/README.md +18 -1
- data/gemfiles/rails_3.2.gemfile +22 -0
- data/gemfiles/rails_4.0.gemfile +22 -0
- data/gemfiles/rails_4.1.gemfile +22 -0
- data/lib/lookup_by/association.rb +34 -9
- data/lib/lookup_by/cache.rb +11 -1
- data/lib/lookup_by/caching/lru.rb +5 -0
- data/lib/lookup_by/lookup.rb +3 -0
- data/lib/lookup_by/version.rb +1 -1
- data/spec/association_spec.rb +90 -23
- data/spec/caching/lru_spec.rb +18 -15
- data/spec/dummy/app/models/raisin.rb +3 -0
- data/spec/dummy/app/models/read_through_raisin.rb +3 -0
- data/spec/dummy/config/environments/test.rb +5 -0
- data/spec/dummy/db/migrate/20121019040009_create_tables.rb +3 -0
- data/spec/dummy/db/structure.sql +102 -0
- data/spec/lookup_by_spec.rb +56 -16
- data/spec/spec_helper.rb +5 -1
- data/spec/support/shared_examples_for_a_lookup.rb +30 -30
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7084212f990738a1e6338e73961ef7f34e3c9670
|
4
|
+
data.tar.gz: 2fa840e2f6e16219f66457381d92b1cc2c918a69
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 352deedc590d022c51433522225cf9237207019890fff5994be08e2492e3b4f8dedb867b9adf131716f993c5cd5b4c2bad895b3be0a91caf2f049979322c48f2
|
7
|
+
data.tar.gz: 4fab5916ae92cbfc4e9d89d8fc2b75742687e25e2a92eeed6d4275cccbd320ebc96da5406cf6ce25df2785334034ea55496cf2157bf8cccd61c9e193da4d05be
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Appraisals
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -74,7 +74,7 @@ lookup_by :column_name
|
|
74
74
|
|
75
75
|
lookup_for :status
|
76
76
|
# Defines #status and #status= instance methods that transparently reference the lookup table.
|
77
|
-
# Defines .with_status(
|
77
|
+
# Defines .with_status(*names) and .without_status(*names) scopes on the model.
|
78
78
|
```
|
79
79
|
|
80
80
|
### Define the lookup model
|
@@ -244,6 +244,23 @@ lookup_by :column_name
|
|
244
244
|
lookup_by :column_name, cache: 20, find_or_create: true
|
245
245
|
```
|
246
246
|
|
247
|
+
### Raise on Miss
|
248
|
+
|
249
|
+
You can configure cache misses to raise a `LookupBy::RecordNotFound` error.
|
250
|
+
|
251
|
+
```ruby
|
252
|
+
# Return nil
|
253
|
+
# Default
|
254
|
+
lookup_by :column_name, cache: true
|
255
|
+
|
256
|
+
# Raise if not found pre-loaded cache
|
257
|
+
lookup_by :column_name, cache: true, raise: true
|
258
|
+
|
259
|
+
# Raise if not found in DB, either
|
260
|
+
lookup_by :column_name, cache: true, find: true, raise: true
|
261
|
+
```
|
262
|
+
|
263
|
+
|
247
264
|
### Normalize values
|
248
265
|
|
249
266
|
```ruby
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "rails", "~> 3.2.0"
|
6
|
+
|
7
|
+
group :development, :test do
|
8
|
+
gem "appraisal", "~> 1.0", :require => false
|
9
|
+
gem "rspec"
|
10
|
+
gem "rspec-its"
|
11
|
+
gem "database_cleaner"
|
12
|
+
gem "pg", :platform => :ruby
|
13
|
+
gem "activerecord-jdbcpostgresql-adapter", :platform => :jruby
|
14
|
+
gem "simplecov", :require => false
|
15
|
+
gem "coveralls", :require => false
|
16
|
+
gem "pry", :require => false
|
17
|
+
gem "colored", :require => false
|
18
|
+
gem "racc"
|
19
|
+
gem "json"
|
20
|
+
end
|
21
|
+
|
22
|
+
gemspec :path => "../"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "rails", "~> 4.0.0"
|
6
|
+
|
7
|
+
group :development, :test do
|
8
|
+
gem "appraisal", "~> 1.0", :require => false
|
9
|
+
gem "rspec"
|
10
|
+
gem "rspec-its"
|
11
|
+
gem "database_cleaner"
|
12
|
+
gem "pg", :platform => :ruby
|
13
|
+
gem "activerecord-jdbcpostgresql-adapter", :platform => :jruby
|
14
|
+
gem "simplecov", :require => false
|
15
|
+
gem "coveralls", :require => false
|
16
|
+
gem "pry", :require => false
|
17
|
+
gem "colored", :require => false
|
18
|
+
gem "racc"
|
19
|
+
gem "json"
|
20
|
+
end
|
21
|
+
|
22
|
+
gemspec :path => "../"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "rails", "~> 4.1.0"
|
6
|
+
|
7
|
+
group :development, :test do
|
8
|
+
gem "appraisal", "~> 1.0", :require => false
|
9
|
+
gem "rspec"
|
10
|
+
gem "rspec-its"
|
11
|
+
gem "database_cleaner"
|
12
|
+
gem "pg", :platform => :ruby
|
13
|
+
gem "activerecord-jdbcpostgresql-adapter", :platform => :jruby
|
14
|
+
gem "simplecov", :require => false
|
15
|
+
gem "coveralls", :require => false
|
16
|
+
gem "pry", :require => false
|
17
|
+
gem "colored", :require => false
|
18
|
+
gem "racc"
|
19
|
+
gem "json"
|
20
|
+
end
|
21
|
+
|
22
|
+
gemspec :path => "../"
|
@@ -22,7 +22,7 @@ module LookupBy
|
|
22
22
|
end
|
23
23
|
|
24
24
|
options.symbolize_keys!
|
25
|
-
options.assert_valid_keys(:class_name, :foreign_key, :symbolize, :strict, :scope)
|
25
|
+
options.assert_valid_keys(:class_name, :foreign_key, :symbolize, :strict, :scope, :inverse_scope)
|
26
26
|
|
27
27
|
field = field.to_sym
|
28
28
|
|
@@ -37,14 +37,30 @@ module LookupBy
|
|
37
37
|
@lookups ||= []
|
38
38
|
@lookups << field
|
39
39
|
|
40
|
-
scope_name =
|
40
|
+
scope_name =
|
41
|
+
if options[:scope] == false
|
42
|
+
nil
|
43
|
+
elsif !options.key?(:scope) || options[:scope] == true
|
44
|
+
"with_#{field}"
|
45
|
+
else
|
46
|
+
options[:scope].to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
inverse_scope_name =
|
50
|
+
if options[:inverse_scope] == false
|
51
|
+
nil
|
52
|
+
elsif !options.key?(:inverse_scope) || options[:inverse_scope] == true
|
53
|
+
"without_#{field}"
|
54
|
+
else
|
55
|
+
options[:inverse_scope].to_s
|
56
|
+
end
|
41
57
|
|
42
|
-
if scope_name
|
43
|
-
|
44
|
-
|
58
|
+
if scope_name && respond_to?(scope_name)
|
59
|
+
raise Error, "#{scope_name} already exists on #{self}."
|
60
|
+
end
|
45
61
|
|
46
|
-
|
47
|
-
raise Error, "#{
|
62
|
+
if inverse_scope_name && respond_to?(inverse_scope_name)
|
63
|
+
raise Error, "#{inverse_scope_name} already exists on #{self}."
|
48
64
|
end
|
49
65
|
|
50
66
|
class_name = options[:class_name] || field
|
@@ -59,8 +75,17 @@ module LookupBy
|
|
59
75
|
strict = true if strict.nil?
|
60
76
|
|
61
77
|
class_eval <<-SCOPES, __FILE__, __LINE__.next if scope_name
|
62
|
-
scope :#{scope_name},
|
63
|
-
|
78
|
+
scope :#{scope_name}, ->(*names) { where(#{foreign_key}: #{class_name}[*names]) }
|
79
|
+
SCOPES
|
80
|
+
|
81
|
+
class_eval <<-SCOPES, __FILE__, __LINE__.next if inverse_scope_name
|
82
|
+
scope :#{inverse_scope_name}, ->(*names) {
|
83
|
+
if names.length != 1
|
84
|
+
where('#{foreign_key} NOT IN (?)', #{class_name}[*names])
|
85
|
+
else
|
86
|
+
where('#{foreign_key} <> ?', #{class_name}[*names])
|
87
|
+
end
|
88
|
+
}
|
64
89
|
SCOPES
|
65
90
|
|
66
91
|
cast = options[:symbolize] ? ".to_sym" : ""
|
data/lib/lookup_by/cache.rb
CHANGED
@@ -14,6 +14,7 @@ module LookupBy
|
|
14
14
|
@write = options[:find_or_create]
|
15
15
|
@allow_blank = options[:allow_blank] || false
|
16
16
|
@normalize = options[:normalize]
|
17
|
+
@raise_on_miss = options[:raise] || false
|
17
18
|
@testing = false
|
18
19
|
@enabled = true
|
19
20
|
|
@@ -39,6 +40,10 @@ module LookupBy
|
|
39
40
|
else
|
40
41
|
@read = true
|
41
42
|
end
|
43
|
+
|
44
|
+
if @write && @raise_on_miss
|
45
|
+
raise ArgumentError, "`#{@klass}.lookup_by :#{@field}` can not use `raise: true` and `find_or_create: true` together."
|
46
|
+
end
|
42
47
|
end
|
43
48
|
|
44
49
|
def reload
|
@@ -86,6 +91,11 @@ module LookupBy
|
|
86
91
|
@cache[found.id] = found if found && cache?
|
87
92
|
|
88
93
|
found ||= db_write(value) if @write
|
94
|
+
|
95
|
+
if @raise_on_miss && found.nil?
|
96
|
+
raise LookupBy::RecordNotFound, "No #{@klass.name} lookup record found for value: #{value.inspect}"
|
97
|
+
end
|
98
|
+
|
89
99
|
found
|
90
100
|
end
|
91
101
|
|
@@ -125,7 +135,7 @@ module LookupBy
|
|
125
135
|
case @primary_key_type
|
126
136
|
when :integer
|
127
137
|
value.is_a? Integer
|
128
|
-
when :uuid
|
138
|
+
when :uuid, :string
|
129
139
|
value =~ UUID_REGEX
|
130
140
|
end
|
131
141
|
end
|
data/lib/lookup_by/lookup.rb
CHANGED
data/lib/lookup_by/version.rb
CHANGED
data/spec/association_spec.rb
CHANGED
@@ -5,7 +5,7 @@ describe ::ActiveRecord::Base do
|
|
5
5
|
describe "macro methods" do
|
6
6
|
subject { described_class }
|
7
7
|
|
8
|
-
it {
|
8
|
+
it { is_expected.to respond_to :lookup_for }
|
9
9
|
end
|
10
10
|
|
11
11
|
describe ".lookup_for" do
|
@@ -28,13 +28,9 @@ describe ::ActiveRecord::Base do
|
|
28
28
|
public :define_method, :remove_method
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
expect { subject.lookup_for :foo }.to raise_error LookupBy::Error, /already exists/
|
35
|
-
|
36
|
-
subject.singleton_class.remove_method(method)
|
37
|
-
end
|
31
|
+
subject.singleton_class.define_method(:with_foo) {}
|
32
|
+
expect { subject.lookup_for :foo }.to raise_error LookupBy::Error, /already exists/
|
33
|
+
subject.singleton_class.remove_method(:with_foo)
|
38
34
|
end
|
39
35
|
|
40
36
|
it "requires a foreign key" do
|
@@ -49,16 +45,6 @@ describe ::ActiveRecord::Base do
|
|
49
45
|
expect { subject.lookup_for :country }.to raise_error LookupBy::Error, /Country does not use lookup_by/
|
50
46
|
end
|
51
47
|
|
52
|
-
context "scope: nil" do
|
53
|
-
it { should respond_to(:with_city).with(1).arguments }
|
54
|
-
it { should respond_to(:with_cities).with(2).arguments }
|
55
|
-
end
|
56
|
-
|
57
|
-
context "scope: false" do
|
58
|
-
it { should_not respond_to(:with_postal_code) }
|
59
|
-
it { should_not respond_to(:with_postal_codes) }
|
60
|
-
end
|
61
|
-
|
62
48
|
it "better include the association under test in lookups" do
|
63
49
|
expect(subject.lookups).to include(:city)
|
64
50
|
end
|
@@ -77,7 +63,7 @@ describe LookupBy::Association do
|
|
77
63
|
|
78
64
|
it "accepts Integers" do
|
79
65
|
subject.city = City.where(city: "New York").first.id
|
80
|
-
subject.city.
|
66
|
+
expect(subject.city).to eq "New York"
|
81
67
|
end
|
82
68
|
|
83
69
|
it "rejects symbols" do
|
@@ -86,12 +72,12 @@ describe LookupBy::Association do
|
|
86
72
|
|
87
73
|
it "returns strings" do
|
88
74
|
subject.city = "New York"
|
89
|
-
subject.city.
|
75
|
+
expect(subject.city).to eq "New York"
|
90
76
|
end
|
91
77
|
|
92
78
|
it "allows missing values" do
|
93
79
|
subject.city = "Chicago"
|
94
|
-
subject.city.
|
80
|
+
expect(subject.city).to be_nil
|
95
81
|
end
|
96
82
|
end
|
97
83
|
|
@@ -100,12 +86,12 @@ describe LookupBy::Association do
|
|
100
86
|
|
101
87
|
it "allows symbols" do
|
102
88
|
subject.state = :AL
|
103
|
-
subject.state.
|
89
|
+
expect(subject.state).to eq :AL
|
104
90
|
end
|
105
91
|
|
106
92
|
it "returns symbols" do
|
107
93
|
subject.state = "AL"
|
108
|
-
subject.state.
|
94
|
+
expect(subject.state).to eq :AL
|
109
95
|
end
|
110
96
|
|
111
97
|
it "rejects missing values" do
|
@@ -125,3 +111,84 @@ describe LookupBy::Association do
|
|
125
111
|
end
|
126
112
|
end
|
127
113
|
end
|
114
|
+
|
115
|
+
describe LookupBy::Association, 'scopes' do
|
116
|
+
subject(:klass) do
|
117
|
+
Class.new(ActiveRecord::Base) do
|
118
|
+
self.table_name = 'addresses'
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe 'nomenclature' do
|
123
|
+
context 'default of :with_<foreign_key>' do
|
124
|
+
before { klass.lookup_for :city }
|
125
|
+
it { is_expected.to respond_to(:with_city) }
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'scope: false' do
|
129
|
+
before { klass.lookup_for :city, scope: false }
|
130
|
+
it { is_expected.not_to respond_to(:with_city) }
|
131
|
+
end
|
132
|
+
|
133
|
+
context 'scope: :with_alternate_name' do
|
134
|
+
before { klass.lookup_for :city, scope: :with_home }
|
135
|
+
it { is_expected.to respond_to(:with_home) }
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'scope: "with_alternate_name"' do
|
139
|
+
before { klass.lookup_for :city, scope: "with_home" }
|
140
|
+
it { is_expected.to respond_to(:with_home) }
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'default inverse scope of :without_<foreign_key>' do
|
144
|
+
before { klass.lookup_for :city }
|
145
|
+
it { is_expected.to respond_to(:without_city) }
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'inverse_scope: false' do
|
149
|
+
before { klass.lookup_for :city, inverse_scope: false }
|
150
|
+
it { is_expected.not_to respond_to(:without_city) }
|
151
|
+
end
|
152
|
+
|
153
|
+
context 'inverse_scope: true' do
|
154
|
+
before { klass.lookup_for :city, inverse_scope: true }
|
155
|
+
it { is_expected.to respond_to(:without_city) }
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'inverse_scope: :with_alternate_name' do
|
159
|
+
before { klass.lookup_for :city, inverse_scope: :foreign_to }
|
160
|
+
it { is_expected.to respond_to(:foreign_to) }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context 'functionality' do
|
165
|
+
before do
|
166
|
+
City.create!(city: 'Chicago')
|
167
|
+
City.create!(city: 'Madison')
|
168
|
+
klass.lookup_for :city, scope: :inside_city, inverse_scope: :outside_city
|
169
|
+
end
|
170
|
+
|
171
|
+
let(:chicago) { City['Chicago'] }
|
172
|
+
let(:madison) { City['Madison'] }
|
173
|
+
|
174
|
+
specify 'with_city(a)' do
|
175
|
+
scope = klass.inside_city('Chicago')
|
176
|
+
expect(scope.to_sql).to eq klass.where(city_id: chicago.id).to_sql
|
177
|
+
end
|
178
|
+
|
179
|
+
specify 'with_city(a, b)' do
|
180
|
+
scope = klass.inside_city('Chicago', 'Madison')
|
181
|
+
expect(scope.to_sql).to eq klass.where(city_id: [chicago.id, madison.id]).to_sql
|
182
|
+
end
|
183
|
+
|
184
|
+
specify 'without_city(a)' do
|
185
|
+
scope = klass.outside_city('Chicago')
|
186
|
+
expect(scope.to_sql).to eq klass.where('city_id <> ?', chicago.id).to_sql
|
187
|
+
end
|
188
|
+
|
189
|
+
specify 'without_city(a, b)' do
|
190
|
+
scope = klass.outside_city('Chicago', 'Madison')
|
191
|
+
expect(scope.to_sql).to eq klass.where('city_id NOT IN (?)', [chicago.id, madison.id]).to_sql
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
data/spec/caching/lru_spec.rb
CHANGED
@@ -15,56 +15,59 @@ module LookupBy::Caching
|
|
15
15
|
subject { @cache }
|
16
16
|
|
17
17
|
it "stores entries" do
|
18
|
-
@cache[1].
|
19
|
-
@cache[2].
|
18
|
+
expect(@cache[1]).to eq "one"
|
19
|
+
expect(@cache[2]).to eq "two"
|
20
20
|
end
|
21
21
|
|
22
22
|
it "drops oldest" do
|
23
23
|
@cache[3] = "three"
|
24
24
|
|
25
|
-
@cache[1].
|
25
|
+
expect(@cache[1]).to be_nil
|
26
26
|
end
|
27
27
|
|
28
28
|
it "keeps gets" do
|
29
29
|
@cache[1]
|
30
30
|
@cache[3] = "three"
|
31
31
|
|
32
|
-
@cache[1].
|
33
|
-
@cache[2].
|
34
|
-
@cache[3].
|
32
|
+
expect(@cache[1]).to eq "one"
|
33
|
+
expect(@cache[2]).to be_nil
|
34
|
+
expect(@cache[3]).to eq "three"
|
35
35
|
end
|
36
36
|
|
37
37
|
it "keeps sets" do
|
38
38
|
@cache[1] = "one"
|
39
39
|
@cache[3] = "three"
|
40
40
|
|
41
|
-
@cache[1].
|
42
|
-
@cache[2].
|
43
|
-
@cache[3].
|
41
|
+
expect(@cache[1]).to eq "one"
|
42
|
+
expect(@cache[2]).to be_nil
|
43
|
+
expect(@cache[3]).to eq "three"
|
44
44
|
end
|
45
45
|
|
46
46
|
it "#clear" do
|
47
47
|
cache = LRU.new(2)
|
48
48
|
|
49
49
|
cache[1] = "one"
|
50
|
-
cache.size.
|
50
|
+
expect(cache.size).to eq 1
|
51
51
|
cache.clear
|
52
|
-
cache.size.
|
52
|
+
expect(cache.size).to eq 0
|
53
53
|
end
|
54
54
|
|
55
55
|
specify "#merge" do
|
56
|
-
@cache.merge(1 => "change", 3 => "three")
|
57
|
-
|
56
|
+
merged = @cache.merge(1 => "change", 3 => "three")
|
57
|
+
expect(merged).to be_instance_of(LRU)
|
58
|
+
expect(merged).to eq(1 => "change", 3 => "three")
|
58
59
|
end
|
59
60
|
|
60
61
|
specify "#merge!" do
|
61
|
-
cache
|
62
|
+
cache = LRU.new(3)
|
63
|
+
object_id = cache.object_id
|
62
64
|
|
63
65
|
cache[1] = "one"
|
64
66
|
cache[2] = "two"
|
65
67
|
|
66
68
|
cache.merge!(1 => "change", 3 => "three")
|
67
|
-
cache.
|
69
|
+
expect(cache.object_id).to eql(object_id)
|
70
|
+
expect(cache).to eq(1 => "change", 2 => "two", 3 => "three")
|
68
71
|
end
|
69
72
|
|
70
73
|
it "better include the values under test" do
|
@@ -11,4 +11,9 @@ Dummy::Application.configure do
|
|
11
11
|
# just for the purpose of running a single test. If you are using a tool that
|
12
12
|
# preloads Rails for running tests, you may have to set it to true.
|
13
13
|
config.eager_load = false
|
14
|
+
config.eager_load_paths = []
|
15
|
+
|
16
|
+
if config.active_support.respond_to?(:deprecation=)
|
17
|
+
config.active_support.deprecation = :stderr
|
18
|
+
end
|
14
19
|
end
|
@@ -13,6 +13,9 @@ class CreateTables < ActiveRecord::Migration
|
|
13
13
|
create_lookup_table :uncacheables
|
14
14
|
create_lookup_table :unfindables
|
15
15
|
|
16
|
+
create_lookup_table :raisins
|
17
|
+
create_lookup_table :read_through_raisins
|
18
|
+
|
16
19
|
enable_extension 'uuid-ossp'
|
17
20
|
|
18
21
|
execute 'CREATE SCHEMA traffic;'
|
data/spec/dummy/db/structure.sql
CHANGED
@@ -257,6 +257,64 @@ CREATE SEQUENCE postal_codes_postal_code_id_seq
|
|
257
257
|
ALTER SEQUENCE postal_codes_postal_code_id_seq OWNED BY postal_codes.postal_code_id;
|
258
258
|
|
259
259
|
|
260
|
+
--
|
261
|
+
-- Name: raisins; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
262
|
+
--
|
263
|
+
|
264
|
+
CREATE TABLE raisins (
|
265
|
+
raisin_id integer NOT NULL,
|
266
|
+
raisin text NOT NULL
|
267
|
+
);
|
268
|
+
|
269
|
+
|
270
|
+
--
|
271
|
+
-- Name: raisins_raisin_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
272
|
+
--
|
273
|
+
|
274
|
+
CREATE SEQUENCE raisins_raisin_id_seq
|
275
|
+
START WITH 1
|
276
|
+
INCREMENT BY 1
|
277
|
+
NO MINVALUE
|
278
|
+
NO MAXVALUE
|
279
|
+
CACHE 1;
|
280
|
+
|
281
|
+
|
282
|
+
--
|
283
|
+
-- Name: raisins_raisin_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
284
|
+
--
|
285
|
+
|
286
|
+
ALTER SEQUENCE raisins_raisin_id_seq OWNED BY raisins.raisin_id;
|
287
|
+
|
288
|
+
|
289
|
+
--
|
290
|
+
-- Name: read_through_raisins; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
291
|
+
--
|
292
|
+
|
293
|
+
CREATE TABLE read_through_raisins (
|
294
|
+
read_through_raisin_id integer NOT NULL,
|
295
|
+
read_through_raisin text NOT NULL
|
296
|
+
);
|
297
|
+
|
298
|
+
|
299
|
+
--
|
300
|
+
-- Name: read_through_raisins_read_through_raisin_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
301
|
+
--
|
302
|
+
|
303
|
+
CREATE SEQUENCE read_through_raisins_read_through_raisin_id_seq
|
304
|
+
START WITH 1
|
305
|
+
INCREMENT BY 1
|
306
|
+
NO MINVALUE
|
307
|
+
NO MAXVALUE
|
308
|
+
CACHE 1;
|
309
|
+
|
310
|
+
|
311
|
+
--
|
312
|
+
-- Name: read_through_raisins_read_through_raisin_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
313
|
+
--
|
314
|
+
|
315
|
+
ALTER SEQUENCE read_through_raisins_read_through_raisin_id_seq OWNED BY read_through_raisins.read_through_raisin_id;
|
316
|
+
|
317
|
+
|
260
318
|
--
|
261
319
|
-- Name: schema_migrations; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
262
320
|
--
|
@@ -503,6 +561,20 @@ ALTER TABLE ONLY ip_addresses ALTER COLUMN ip_address_id SET DEFAULT nextval('ip
|
|
503
561
|
ALTER TABLE ONLY postal_codes ALTER COLUMN postal_code_id SET DEFAULT nextval('postal_codes_postal_code_id_seq'::regclass);
|
504
562
|
|
505
563
|
|
564
|
+
--
|
565
|
+
-- Name: raisin_id; Type: DEFAULT; Schema: public; Owner: -
|
566
|
+
--
|
567
|
+
|
568
|
+
ALTER TABLE ONLY raisins ALTER COLUMN raisin_id SET DEFAULT nextval('raisins_raisin_id_seq'::regclass);
|
569
|
+
|
570
|
+
|
571
|
+
--
|
572
|
+
-- Name: read_through_raisin_id; Type: DEFAULT; Schema: public; Owner: -
|
573
|
+
--
|
574
|
+
|
575
|
+
ALTER TABLE ONLY read_through_raisins ALTER COLUMN read_through_raisin_id SET DEFAULT nextval('read_through_raisins_read_through_raisin_id_seq'::regclass);
|
576
|
+
|
577
|
+
|
506
578
|
--
|
507
579
|
-- Name: state_id; Type: DEFAULT; Schema: public; Owner: -
|
508
580
|
--
|
@@ -601,6 +673,22 @@ ALTER TABLE ONLY postal_codes
|
|
601
673
|
ADD CONSTRAINT postal_codes_pkey PRIMARY KEY (postal_code_id);
|
602
674
|
|
603
675
|
|
676
|
+
--
|
677
|
+
-- Name: raisins_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
678
|
+
--
|
679
|
+
|
680
|
+
ALTER TABLE ONLY raisins
|
681
|
+
ADD CONSTRAINT raisins_pkey PRIMARY KEY (raisin_id);
|
682
|
+
|
683
|
+
|
684
|
+
--
|
685
|
+
-- Name: read_through_raisins_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
686
|
+
--
|
687
|
+
|
688
|
+
ALTER TABLE ONLY read_through_raisins
|
689
|
+
ADD CONSTRAINT read_through_raisins_pkey PRIMARY KEY (read_through_raisin_id);
|
690
|
+
|
691
|
+
|
604
692
|
--
|
605
693
|
-- Name: states_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
606
694
|
--
|
@@ -703,6 +791,20 @@ CREATE UNIQUE INDEX ip_addresses__u_ip_address ON ip_addresses USING btree (ip_a
|
|
703
791
|
CREATE UNIQUE INDEX postal_codes__u_postal_code ON postal_codes USING btree (postal_code);
|
704
792
|
|
705
793
|
|
794
|
+
--
|
795
|
+
-- Name: raisins__u_raisin; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
796
|
+
--
|
797
|
+
|
798
|
+
CREATE UNIQUE INDEX raisins__u_raisin ON raisins USING btree (raisin);
|
799
|
+
|
800
|
+
|
801
|
+
--
|
802
|
+
-- Name: read_through_raisins__u_read_through_raisin; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
803
|
+
--
|
804
|
+
|
805
|
+
CREATE UNIQUE INDEX read_through_raisins__u_read_through_raisin ON read_through_raisins USING btree (read_through_raisin);
|
806
|
+
|
807
|
+
|
706
808
|
--
|
707
809
|
-- Name: states__u_state; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
708
810
|
--
|
data/spec/lookup_by_spec.rb
CHANGED
@@ -6,14 +6,14 @@ describe ::ActiveRecord::Base do
|
|
6
6
|
describe "macro methods" do
|
7
7
|
subject { described_class }
|
8
8
|
|
9
|
-
it {
|
10
|
-
it {
|
9
|
+
it { is_expected.to respond_to :lookup_by }
|
10
|
+
it { is_expected.to respond_to :is_a_lookup? }
|
11
11
|
end
|
12
12
|
|
13
13
|
describe "instance methods" do
|
14
14
|
subject { Status.new }
|
15
15
|
|
16
|
-
it {
|
16
|
+
it { is_expected.to respond_to :name }
|
17
17
|
end
|
18
18
|
|
19
19
|
end
|
@@ -24,7 +24,8 @@ describe LookupBy::Lookup do
|
|
24
24
|
City.lookup.seed 'Boston'
|
25
25
|
City.lookup.seed 'Chicago', 'New York City'
|
26
26
|
|
27
|
-
City.all.map(&:name).sort
|
27
|
+
names = City.all.map(&:name).sort
|
28
|
+
expect(names.sort).to eq(['Boston', 'Chicago', 'New York City'])
|
28
29
|
City.lookup.clear
|
29
30
|
end
|
30
31
|
end
|
@@ -43,7 +44,7 @@ describe LookupBy::Lookup do
|
|
43
44
|
it_behaves_like "a read-through proxy"
|
44
45
|
|
45
46
|
it "returns nil on db miss" do
|
46
|
-
subject["foo"].
|
47
|
+
expect(subject["foo"]).to be_nil
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
@@ -57,11 +58,12 @@ describe LookupBy::Lookup do
|
|
57
58
|
it "normalizes the lookup field" do
|
58
59
|
status = subject.create(subject.lookup.field => "paid")
|
59
60
|
|
60
|
-
subject[" paid "].id.
|
61
|
+
expect(subject[" paid "].id).to eq(status.id)
|
61
62
|
end
|
62
63
|
|
63
64
|
it "has a small primary key" do
|
64
|
-
|
65
|
+
sql_type = Status.columns_hash['status_id'].sql_type
|
66
|
+
expect(sql_type).to eq('smallint')
|
65
67
|
end
|
66
68
|
end
|
67
69
|
|
@@ -82,7 +84,7 @@ describe LookupBy::Lookup do
|
|
82
84
|
it_behaves_like "a strict cache"
|
83
85
|
|
84
86
|
it "preloads the cache" do
|
85
|
-
subject.lookup.cache.
|
87
|
+
expect(subject.lookup.cache).not_to be_empty
|
86
88
|
end
|
87
89
|
end
|
88
90
|
|
@@ -102,7 +104,7 @@ describe LookupBy::Lookup do
|
|
102
104
|
it_behaves_like "a read-through cache"
|
103
105
|
|
104
106
|
it "is not testing when not writing through the LRU" do
|
105
|
-
subject.lookup.testing.
|
107
|
+
expect(subject.lookup.testing).to be false
|
106
108
|
end
|
107
109
|
end
|
108
110
|
|
@@ -115,7 +117,7 @@ describe LookupBy::Lookup do
|
|
115
117
|
it_behaves_like "a write-through cache"
|
116
118
|
|
117
119
|
it "sets testing when RAILS_ENV=test" do
|
118
|
-
subject.lookup.testing.
|
120
|
+
expect(subject.lookup.testing).to be true
|
119
121
|
end
|
120
122
|
|
121
123
|
it "does not write primary keys" do
|
@@ -150,13 +152,13 @@ describe LookupBy::Lookup do
|
|
150
152
|
it "allows lookup by IPAddr" do
|
151
153
|
ip = subject['127.0.0.1']
|
152
154
|
|
153
|
-
subject[IPAddr.new('127.0.0.1')].
|
154
|
-
subject[ip.id].
|
155
|
-
subject['127.0.0.1'].
|
155
|
+
expect(subject[IPAddr.new('127.0.0.1')]).to eq(ip)
|
156
|
+
expect(subject[ip.id]).to eq(ip)
|
157
|
+
expect(subject['127.0.0.1']).to eq(ip)
|
156
158
|
end
|
157
159
|
end
|
158
160
|
|
159
|
-
context "Path.lookup_by :column, cache:
|
161
|
+
context "Path.lookup_by :column, cache: N, find_or_create: true (UUID primary key)" do
|
160
162
|
subject { Path }
|
161
163
|
|
162
164
|
it_behaves_like "a lookup"
|
@@ -166,8 +168,46 @@ describe LookupBy::Lookup do
|
|
166
168
|
|
167
169
|
it 'treats UUIDs as the primary key' do
|
168
170
|
path = subject['/']
|
169
|
-
path.id.
|
170
|
-
subject[path.id].
|
171
|
+
expect(path.id).to match(LookupBy::UUID_REGEX)
|
172
|
+
expect(subject[path.id]).to eq(path)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context "Raisin.lookup_by :column, cache: true, raise: true" do
|
177
|
+
subject { Raisin }
|
178
|
+
|
179
|
+
it_behaves_like "a lookup"
|
180
|
+
it_behaves_like "a cache"
|
181
|
+
|
182
|
+
it "raises LookupBy::RecordNotFound on cache miss" do
|
183
|
+
expect {
|
184
|
+
subject[:not_disgusting]
|
185
|
+
}.to raise_error(LookupBy::RecordNotFound, /Raisin.*not_disgusting/)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context "ReadThroughRaisin.lookup_by :column, cache: true, find: true, raise: true" do
|
190
|
+
subject { ReadThroughRaisin }
|
191
|
+
|
192
|
+
it_behaves_like "a lookup"
|
193
|
+
it_behaves_like "a cache"
|
194
|
+
it_behaves_like "a read-through cache"
|
195
|
+
|
196
|
+
it "raises LookupBy::RecordNotFound on cache and DB miss" do
|
197
|
+
expect {
|
198
|
+
subject[:tasty]
|
199
|
+
}.to raise_error(LookupBy::RecordNotFound, /Raisin.*tasty/)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
context "Raisin.lookup_by :column, find_or_create: true, raise: true" do
|
204
|
+
it "raises ArgumentError, as `raise` and `find_or_create` can not exist" do
|
205
|
+
expect {
|
206
|
+
class WriteThroughRaisin < ActiveRecord::Base
|
207
|
+
self.table_name = 'raisins'
|
208
|
+
lookup_by :raisin, find_or_create: true, raise: true
|
209
|
+
end
|
210
|
+
}.to raise_error(ArgumentError)
|
171
211
|
end
|
172
212
|
end
|
173
213
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -24,7 +24,7 @@ require 'rspec/its'
|
|
24
24
|
require 'database_cleaner'
|
25
25
|
require 'pry'
|
26
26
|
|
27
|
-
ActiveRecord::Migration.maintain_test_schema! if defined?(ActiveRecord::Migration)
|
27
|
+
ActiveRecord::Migration.maintain_test_schema! if defined?(ActiveRecord::Migration) && ActiveRecord::Migration.respond_to?(:maintain_test_schema!)
|
28
28
|
|
29
29
|
# Requires supporting ruby files with custom matchers and macros, etc,
|
30
30
|
# in spec/support/ and its subdirectories.
|
@@ -34,6 +34,10 @@ RSpec.configure do |config|
|
|
34
34
|
config.backtrace_exclusion_patterns << /vendor\//
|
35
35
|
config.backtrace_exclusion_patterns << /lib\/rspec\/rails/
|
36
36
|
|
37
|
+
# Use `fit`, `fcontext`, etc. to focus on particular examples.
|
38
|
+
config.filter_run focus: true
|
39
|
+
config.run_all_when_everything_filtered = true
|
40
|
+
|
37
41
|
# Run specs in random order to surface order dependencies. If you find an
|
38
42
|
# order dependency and want to debug it, you can fix the order by providing
|
39
43
|
# the seed, which is printed after each run.
|
@@ -1,8 +1,8 @@
|
|
1
1
|
shared_examples "a lookup" do
|
2
|
-
it {
|
3
|
-
it {
|
4
|
-
it {
|
5
|
-
it {
|
2
|
+
it { is_expected.to respond_to :[] }
|
3
|
+
it { is_expected.to respond_to :lookup }
|
4
|
+
it { is_expected.to respond_to :lookup_by }
|
5
|
+
it { is_expected.to respond_to :lookup_for }
|
6
6
|
|
7
7
|
it "better be a lookup" do
|
8
8
|
expect(subject.is_a_lookup?).to be true
|
@@ -13,19 +13,19 @@ shared_examples "a lookup" do
|
|
13
13
|
end
|
14
14
|
|
15
15
|
it "returns nil for nil" do
|
16
|
-
subject[nil].
|
17
|
-
subject[nil, nil].
|
16
|
+
expect(subject[nil]).to be_nil
|
17
|
+
expect(subject[nil, nil]).to eq([nil, nil])
|
18
18
|
end
|
19
19
|
|
20
20
|
it "returns nil for empty strings" do
|
21
|
-
subject[""].
|
22
|
-
subject["", ""].
|
21
|
+
expect(subject[""]).to be_nil
|
22
|
+
expect(subject["", ""]).to eq([nil, nil])
|
23
23
|
end
|
24
24
|
|
25
25
|
it "returns itself" do
|
26
26
|
if first = subject.first
|
27
|
-
subject[first].
|
28
|
-
subject[first, first].
|
27
|
+
expect(subject[first]).to eq first
|
28
|
+
expect(subject[first, first]).to eq([first, first])
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
@@ -49,7 +49,7 @@ shared_examples "a proxy" do
|
|
49
49
|
|
50
50
|
subject[original.name]
|
51
51
|
subject.update(original.id, name: "updated")
|
52
|
-
subject[original.id].name.
|
52
|
+
expect(subject[original.id].name).not_to eq original.name
|
53
53
|
end
|
54
54
|
|
55
55
|
it "allows .destroy_all" do
|
@@ -58,7 +58,7 @@ shared_examples "a proxy" do
|
|
58
58
|
|
59
59
|
it "allows .destroy" do
|
60
60
|
instance = subject.create(name: "foo")
|
61
|
-
subject.destroy(instance.id).
|
61
|
+
expect(subject.destroy(instance.id)).to eq(instance)
|
62
62
|
end
|
63
63
|
|
64
64
|
it "allows .delete_all" do
|
@@ -66,7 +66,7 @@ shared_examples "a proxy" do
|
|
66
66
|
end
|
67
67
|
|
68
68
|
it "allows .delete" do
|
69
|
-
subject.delete(1).
|
69
|
+
expect(subject.delete(1)).to eq(0)
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
@@ -81,7 +81,7 @@ shared_examples "a cache" do
|
|
81
81
|
subject[original.name]
|
82
82
|
|
83
83
|
subject.update(original.id, subject.lookup.field => "updated")
|
84
|
-
subject[original.id].name.
|
84
|
+
expect(subject[original.id].name).to eq "original"
|
85
85
|
|
86
86
|
subject.lookup.testing = was_testing
|
87
87
|
end
|
@@ -110,28 +110,28 @@ shared_examples "a strict cache" do
|
|
110
110
|
|
111
111
|
xit "does cache .all" do
|
112
112
|
new = subject.create(name: 'add')
|
113
|
-
subject.all.to_a.
|
113
|
+
expect(subject.all.to_a).not_to include(new)
|
114
114
|
end
|
115
115
|
|
116
116
|
xit "reloads .all when called with args" do
|
117
117
|
new = subject.create(name: "new")
|
118
|
-
subject.all.to_a.
|
119
|
-
subject.all({}).to_a.
|
118
|
+
expect(subject.all.to_a).not_to include(new)
|
119
|
+
expect(subject.all({}).to_a).to include(new)
|
120
120
|
end
|
121
121
|
|
122
122
|
it "caches .pluck" do
|
123
123
|
subject.create(name: "pluck this")
|
124
|
-
subject.pluck(:name).
|
124
|
+
expect(subject.pluck(:name)).not_to include("pluck this")
|
125
125
|
end
|
126
126
|
|
127
127
|
it "returns nil on miss" do
|
128
|
-
subject["foo"].
|
128
|
+
expect(subject["foo"]).to be_nil
|
129
129
|
end
|
130
130
|
|
131
131
|
it "ignores new records" do
|
132
132
|
subject.create(name: "new record")
|
133
133
|
|
134
|
-
subject["new record"].
|
134
|
+
expect(subject["new record"]).to be_nil
|
135
135
|
end
|
136
136
|
end
|
137
137
|
|
@@ -142,17 +142,17 @@ shared_examples "a read-through proxy" do
|
|
142
142
|
|
143
143
|
it "reloads .all" do
|
144
144
|
new = subject.create(name: 'add')
|
145
|
-
subject.all.to_a.
|
145
|
+
expect(subject.all.to_a).to include (new)
|
146
146
|
end
|
147
147
|
|
148
148
|
it "reloads .pluck" do
|
149
149
|
subject.create(name: "pluck this")
|
150
|
-
subject.pluck(subject.lookup.field).
|
150
|
+
expect(subject.pluck(subject.lookup.field)).to include("pluck this")
|
151
151
|
end
|
152
152
|
|
153
153
|
it "finds new records" do
|
154
154
|
created = subject.create(name: "new record")
|
155
|
-
subject["new record"].id.
|
155
|
+
expect(subject["new record"].id).to eq created.id
|
156
156
|
end
|
157
157
|
end
|
158
158
|
|
@@ -169,7 +169,7 @@ shared_examples "a read-through cache" do
|
|
169
169
|
subject[created.name]
|
170
170
|
|
171
171
|
subject.update(created.id, name: "changed")
|
172
|
-
subject[created.id].name.
|
172
|
+
expect(subject[created.id].name).to eq "cached"
|
173
173
|
|
174
174
|
subject.lookup.testing = was_testing
|
175
175
|
end
|
@@ -189,15 +189,15 @@ shared_examples "a write-through cache" do
|
|
189
189
|
found = subject["found"]
|
190
190
|
|
191
191
|
subject.update(found.id, name: "missing")
|
192
|
-
subject[found.id].name.
|
192
|
+
expect(subject[found.id].name).to eq "missing"
|
193
193
|
end
|
194
194
|
end
|
195
195
|
|
196
196
|
shared_examples "a lookup for" do |field|
|
197
|
-
it {
|
198
|
-
it {
|
199
|
-
it {
|
200
|
-
it {
|
197
|
+
it { is_expected.to respond_to field }
|
198
|
+
it { is_expected.to respond_to "#{field}=" }
|
199
|
+
it { is_expected.to respond_to "raw_#{field}" }
|
200
|
+
it { is_expected.to respond_to "#{field}_before_type_cast" }
|
201
201
|
|
202
202
|
it "accepts nil" do
|
203
203
|
expect { subject.send "#{field}=", nil }.to_not raise_error
|
@@ -205,7 +205,7 @@ shared_examples "a lookup for" do |field|
|
|
205
205
|
|
206
206
|
it "converts empty strings to nil" do
|
207
207
|
subject.send "#{field}=", ""
|
208
|
-
subject.send(field).
|
208
|
+
expect(subject.send(field)).to be_nil
|
209
209
|
end
|
210
210
|
|
211
211
|
it "rejects other argument types" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lookup_by
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Erik Peterson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -62,11 +62,15 @@ files:
|
|
62
62
|
- ".gitignore"
|
63
63
|
- ".ruby-version"
|
64
64
|
- ".travis.yml"
|
65
|
+
- Appraisals
|
65
66
|
- Gemfile
|
66
67
|
- MIT-LICENSE
|
67
68
|
- README.md
|
68
69
|
- Rakefile
|
69
70
|
- TODO.md
|
71
|
+
- gemfiles/rails_3.2.gemfile
|
72
|
+
- gemfiles/rails_4.0.gemfile
|
73
|
+
- gemfiles/rails_4.1.gemfile
|
70
74
|
- lib/lookup_by.rb
|
71
75
|
- lib/lookup_by/association.rb
|
72
76
|
- lib/lookup_by/cache.rb
|
@@ -92,6 +96,8 @@ files:
|
|
92
96
|
- spec/dummy/app/models/ip_address.rb
|
93
97
|
- spec/dummy/app/models/path.rb
|
94
98
|
- spec/dummy/app/models/postal_code.rb
|
99
|
+
- spec/dummy/app/models/raisin.rb
|
100
|
+
- spec/dummy/app/models/read_through_raisin.rb
|
95
101
|
- spec/dummy/app/models/state.rb
|
96
102
|
- spec/dummy/app/models/status.rb
|
97
103
|
- spec/dummy/app/models/street.rb
|
@@ -159,6 +165,8 @@ test_files:
|
|
159
165
|
- spec/dummy/app/models/ip_address.rb
|
160
166
|
- spec/dummy/app/models/path.rb
|
161
167
|
- spec/dummy/app/models/postal_code.rb
|
168
|
+
- spec/dummy/app/models/raisin.rb
|
169
|
+
- spec/dummy/app/models/read_through_raisin.rb
|
162
170
|
- spec/dummy/app/models/state.rb
|
163
171
|
- spec/dummy/app/models/status.rb
|
164
172
|
- spec/dummy/app/models/street.rb
|