lookup_by 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|