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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 03729b904ce688e312634ce27fc0378510189a06
4
- data.tar.gz: 1da87c9243ce001671e57a92084f82a01c0ccba7
3
+ metadata.gz: 7084212f990738a1e6338e73961ef7f34e3c9670
4
+ data.tar.gz: 2fa840e2f6e16219f66457381d92b1cc2c918a69
5
5
  SHA512:
6
- metadata.gz: 940795913611bb0a21b97c15a79b847fbb1bf62c79447765991aa8d5d389bc763f242510c03a3cb9548b8db3fec36d877df60b71c6b3901a03d2323800da0939
7
- data.tar.gz: e00e0708e39fe04352ea4e1f19799643e1efc9946467d3d63e86fde83225f8cff5e7ed2cef980da84b8db3450ae49a7596f28a9a7e63923be771b14c408bcd24
6
+ metadata.gz: 352deedc590d022c51433522225cf9237207019890fff5994be08e2492e3b4f8dedb867b9adf131716f993c5cd5b4c2bad895b3be0a91caf2f049979322c48f2
7
+ data.tar.gz: 4fab5916ae92cbfc4e9d89d8fc2b75742687e25e2a92eeed6d4275cccbd320ebc96da5406cf6ce25df2785334034ea55496cf2157bf8cccd61c9e193da4d05be
data/.gitignore CHANGED
@@ -9,4 +9,5 @@ spec/dummy/tmp/
9
9
  spec/dummy/.sass-cache
10
10
  coverage/
11
11
  Gemfile.lock
12
+ gemfiles/*.lock
12
13
  tags
data/.travis.yml CHANGED
@@ -10,4 +10,5 @@ before_script:
10
10
  - psql -c 'create database lookup_by_test' -U postgres
11
11
  script:
12
12
  - bundle exec rake app:db:test:prepare
13
- - bundle exec rake
13
+ - bundle exec appraisal install
14
+ - bundle exec appraisal rake
data/Appraisals ADDED
@@ -0,0 +1,11 @@
1
+ appraise 'rails-3.2' do
2
+ gem 'rails', '~> 3.2.0'
3
+ end
4
+
5
+ appraise 'rails-4.0' do
6
+ gem 'rails', '~> 4.0.0'
7
+ end
8
+
9
+ appraise 'rails-4.1' do
10
+ gem 'rails', '~> 4.1.0'
11
+ end
data/Gemfile CHANGED
@@ -6,6 +6,8 @@ source "https://rubygems.org"
6
6
  gemspec
7
7
 
8
8
  group :development, :test do
9
+ gem 'appraisal', '~> 1.0', require: false
10
+
9
11
  gem 'rspec'
10
12
  gem 'rspec-its'
11
13
  gem 'database_cleaner'
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(name) and .with_statuses(*names) scopes on the model.
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 = "with_#{field}" unless options[:scope] == false
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
- single_scope = scope_name
44
- plural_scope = scope_name.pluralize
58
+ if scope_name && respond_to?(scope_name)
59
+ raise Error, "#{scope_name} already exists on #{self}."
60
+ end
45
61
 
46
- raise Error, "#{single_scope} already exists on #{self}. Use `lookup_for #{field}, scope: false` if you don't want scope :#{single_scope}" if respond_to?(single_scope)
47
- raise Error, "#{plural_scope} already exists on #{self}. Use `lookup_for #{field}, scope: false` if you don't want scope :#{plural_scope}" if respond_to?(plural_scope)
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}, ->(name) { where(#{foreign_key}: #{class_name}[name]) }
63
- scope :#{scope_name.pluralize}, ->(*names) { where(#{foreign_key}: #{class_name}[*names]) }
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" : ""
@@ -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
@@ -28,8 +28,13 @@ module LookupBy
28
28
  prune
29
29
  end
30
30
 
31
+ def merge(hash)
32
+ dup.merge!(hash)
33
+ end
34
+
31
35
  def merge!(hash)
32
36
  hash.each { |k, v| self[k] = v }
37
+ self
33
38
  end
34
39
 
35
40
  def delete(key)
@@ -1,4 +1,7 @@
1
1
  module LookupBy
2
+ class RecordNotFound < ActiveRecord::RecordNotFound
3
+ end
4
+
2
5
  module Lookup
3
6
  module MacroMethods
4
7
  def is_a_lookup?
@@ -1,3 +1,3 @@
1
1
  module LookupBy
2
- VERSION = "0.7.0"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -5,7 +5,7 @@ describe ::ActiveRecord::Base do
5
5
  describe "macro methods" do
6
6
  subject { described_class }
7
7
 
8
- it { should respond_to :lookup_for }
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
- [:with_foo, :with_foos].each do |method|
32
- subject.singleton_class.define_method(method) { }
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.should eq "New York"
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.should eq "New York"
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.should be_nil
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.should eq :AL
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.should eq :AL
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
@@ -15,56 +15,59 @@ module LookupBy::Caching
15
15
  subject { @cache }
16
16
 
17
17
  it "stores entries" do
18
- @cache[1].should eq "one"
19
- @cache[2].should eq "two"
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].should be_nil
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].should eq "one"
33
- @cache[2].should be_nil
34
- @cache[3].should eq "three"
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].should eq "one"
42
- @cache[2].should be_nil
43
- @cache[3].should eq "three"
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.should eq 1
50
+ expect(cache.size).to eq 1
51
51
  cache.clear
52
- cache.size.should eq 0
52
+ expect(cache.size).to eq 0
53
53
  end
54
54
 
55
55
  specify "#merge" do
56
- @cache.merge(1 => "change", 3 => "three").should
57
- eq(1 => "change", 2 => "two", 3 => "three")
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 = LRU.new(3)
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.should eq(1 => "change", 2 => "two", 3 => "three")
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
@@ -0,0 +1,3 @@
1
+ class Raisin < ActiveRecord::Base
2
+ lookup_by :raisin, cache: true, raise: true
3
+ end
@@ -0,0 +1,3 @@
1
+ class ReadThroughRaisin < ActiveRecord::Base
2
+ lookup_by :read_through_raisin, cache: true, find: true, raise: true
3
+ end
@@ -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;'
@@ -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
  --
@@ -6,14 +6,14 @@ describe ::ActiveRecord::Base do
6
6
  describe "macro methods" do
7
7
  subject { described_class }
8
8
 
9
- it { should respond_to :lookup_by }
10
- it { should respond_to :is_a_lookup? }
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 { should respond_to :name }
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.should eq(['Boston', 'Chicago', 'New York City'])
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"].should be_nil
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.should == status.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
- expect { Status.create(status_id: 100_000, status: "too_big") }.to raise_error
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.should_not be_empty
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.should be false
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.should be true
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')].should == ip
154
- subject[ip.id].should == ip
155
- subject['127.0.0.1'].should == ip
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: true, find_or_create: true (UUID primary key)" do
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.should match(LookupBy::UUID_REGEX)
170
- subject[path.id].should == path
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 { should respond_to :[] }
3
- it { should respond_to :lookup }
4
- it { should respond_to :lookup_by }
5
- it { should respond_to :lookup_for }
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].should be_nil
17
- subject[nil, nil].should == [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[""].should be_nil
22
- subject["", ""].should == [nil, nil]
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].should eq first
28
- subject[first, first].should == [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.should_not eq original.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).should == instance
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).should == 0
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.should eq "original"
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.should_not include(new)
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.should_not include(new)
119
- subject.all({}).to_a.should include(new)
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).should_not include("pluck this")
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"].should be_nil
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"].should be_nil
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.should include (new)
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).should include("pluck this")
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.should eq created.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.should eq "cached"
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.should eq "missing"
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 { should respond_to field }
198
- it { should respond_to "#{field}=" }
199
- it { should respond_to "raw_#{field}" }
200
- it { should respond_to "#{field}_before_type_cast" }
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).should be_nil
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.7.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-06-19 00:00:00.000000000 Z
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