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 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