lookup_by 0.9.1 → 0.10.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: f743786ef23e1404d94ab4e182d9fc4e56ebf02b
4
- data.tar.gz: 544619c07c7172702cba5729e8a8c1bb80783ddf
3
+ metadata.gz: b7b482a9755de8d24837b3555d53cc416b77ac87
4
+ data.tar.gz: 087b776e7ad8c89b4fcb90a63769820c6279d81e
5
5
  SHA512:
6
- metadata.gz: c5e44713f89aaca0651af41331620efec4fd7e42a9dbf2d72b2617ddb95ee77d96b8f1bcb17832a31d65365aa62af4820ed29c6b23fc84e99c262446c875af59
7
- data.tar.gz: 46803218755acd5241351f1ae535a6a71b41f8adfbd0fe9d53897ea677cd11358318a134c14a5ddf61487c4f823cd9eab7a6123d36c6a5ee3b2d1e211ad7577c
6
+ metadata.gz: 2d728f55c1da30eb53de8d33397bb4b6fbe1c1d1b9003d03f11db296d34455c65d5a0f16058f904b298b9050cc5b86ee69d88223cb942c6ff9419dadf34e2501
7
+ data.tar.gz: 62479abc739d5125a2feffa8589236f4e1f83fd9a391b343bb5f49ac3510dd05293c221cb4b3521362a659804a021ce7a114556d045fc2644a223faa88a7afd9
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-2.1.2
1
+ ruby-2.2.0
data/.travis.yml CHANGED
@@ -1,9 +1,10 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
4
- - 2.0.0
5
- - 2.1.2
6
- - rbx-2.2.10
3
+ - 1.9.3-p551
4
+ - 2.0.0-p598
5
+ - 2.1.5
6
+ - 2.2.0
7
+ - rbx-2.5.2
7
8
  services:
8
9
  - postgresql
9
10
  before_script:
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup markdown
data/Appraisals CHANGED
@@ -1,7 +1,3 @@
1
- appraise 'rails-3.2' do
2
- gem 'rails', '~> 3.2.0'
3
- end
4
-
5
1
  appraise 'rails-4.0' do
6
2
  gem 'rails', '~> 4.0.0'
7
3
  end
@@ -11,5 +7,5 @@ appraise 'rails-4.1' do
11
7
  end
12
8
 
13
9
  appraise 'rails-4.2' do
14
- gem 'rails', '~> 4.2.0.beta1'
10
+ gem 'rails', '~> 4.2.0'
15
11
  end
data/Gemfile CHANGED
@@ -6,7 +6,7 @@ source "https://rubygems.org"
6
6
  gemspec
7
7
 
8
8
  group :development, :test do
9
- gem 'appraisal', '~> 1.0', require: false
9
+ gem 'appraisal', '~> 1.0.0', require: false
10
10
 
11
11
  gem 'rspec'
12
12
  gem 'rspec-its'
data/README.md CHANGED
@@ -25,7 +25,8 @@ LookupBy is a thread-safe lookup table cache for ActiveRecord that reduces norma
25
25
 
26
26
  ### Dependencies
27
27
 
28
- * Rails 3.2+, _tested on Rails 3.2, 4.0, 4.1, and 4.2_
28
+ * Rails 4.0+, _tested on Rails 4.0, 4.1, and 4.2_
29
+ * Ruby 1.9.3+, _tested on Ruby 1.9.3, 2.0, 2.1, 2.2 and Rubinius 1.5.2_
29
30
  * PostgreSQL
30
31
 
31
32
  ### Development
@@ -64,12 +65,16 @@ Or install it manually:
64
65
  LookupBy adds two "macro" methods to `ActiveRecord::Base`
65
66
 
66
67
  ```ruby
67
- lookup_by :column_name
68
- # Defines .[], .lookup, and .is_a_lookup? class methods.
69
-
70
- lookup_for :status
71
- # Defines #status and #status= instance methods that transparently reference the lookup table.
72
- # Defines .with_status(*names) and .without_status(*names) scopes on the model.
68
+ class ExampleLookup < ActiveRecord::Base
69
+ lookup_by :column_name
70
+ # Defines .[], .lookup, .is_a_lookup?, and .seed class methods.
71
+ end
72
+
73
+ class ExampleObject < ActiveRecord::Base
74
+ lookup_for :status
75
+ # Defines #status and #status= instance methods that transparently reference the lookup table.
76
+ # Defines .with_status(*names) and .without_status(*names) scopes on the model.
77
+ end
73
78
  ```
74
79
 
75
80
  ### Define the lookup model
@@ -98,6 +103,9 @@ class Status < ActiveRecord::Base
98
103
  lookup_by :status
99
104
  end
100
105
 
106
+ # Seed some values
107
+ Status.seed *%w[unpaid paid shipped]
108
+
101
109
  # Aliases :name to the lookup attribute
102
110
  Status.new(name: "paid")
103
111
  ```
@@ -140,6 +148,29 @@ Order.column_names
140
148
  => ["order_id", "status_id"]
141
149
  ```
142
150
 
151
+ ### Seed the lookup table
152
+
153
+ ```ruby
154
+ # Find or create each argument
155
+ Status.seed *%w[unpaid paid shipped returned]
156
+ ```
157
+
158
+ ### Manage lookups globally
159
+
160
+ ```ruby
161
+ # Clear all caches
162
+ LookupBy.clear
163
+
164
+ # Disable all
165
+ LookupBy.disable
166
+
167
+ # Enable all, this will reload the caches
168
+ LookupBy.enable
169
+
170
+ # Reload all caches
171
+ LookupBy.reload
172
+ ```
173
+
143
174
  # Configuration
144
175
 
145
176
  ### Symbolize
@@ -239,7 +270,7 @@ lookup_by :column_name
239
270
  lookup_by :column_name, cache: 20, find_or_create: true
240
271
  ```
241
272
 
242
- ### Raise on Miss
273
+ ### Raise on miss
243
274
 
244
275
  Configure cache misses to raise a `LookupBy::RecordNotFound` error.
245
276
 
@@ -5,7 +5,7 @@ source "https://rubygems.org"
5
5
  gem "rails", "~> 4.0.0"
6
6
 
7
7
  group :development, :test do
8
- gem "appraisal", "~> 1.0", :require => false
8
+ gem "appraisal", "~> 1.0.0", :require => false
9
9
  gem "rspec"
10
10
  gem "rspec-its"
11
11
  gem "rspec-rails"
@@ -5,7 +5,7 @@ source "https://rubygems.org"
5
5
  gem "rails", "~> 4.1.0"
6
6
 
7
7
  group :development, :test do
8
- gem "appraisal", "~> 1.0", :require => false
8
+ gem "appraisal", "~> 1.0.0", :require => false
9
9
  gem "rspec"
10
10
  gem "rspec-its"
11
11
  gem "rspec-rails"
@@ -2,10 +2,10 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "~> 4.2.0.beta1"
5
+ gem "rails", "~> 4.2.0"
6
6
 
7
7
  group :development, :test do
8
- gem "appraisal", "~> 1.0", :require => false
8
+ gem "appraisal", "~> 1.0.0", :require => false
9
9
  gem "rspec"
10
10
  gem "rspec-its"
11
11
  gem "rspec-rails"
@@ -13,6 +13,7 @@
13
13
  module LookupBy
14
14
  module Association
15
15
  module MacroMethods
16
+ # @see https://practicingruby.com/articles/closures-are-complicated
16
17
  def lookup_for field, options = {}
17
18
  begin
18
19
  return unless table_exists?
@@ -116,24 +117,32 @@ module LookupBy
116
117
  end
117
118
 
118
119
  def #{field}=(arg)
119
- value = case arg
120
+ result = case arg
120
121
  when nil
121
122
  nil
122
123
  when String, Integer, IPAddr
123
- #{class_name}[arg].try(:id)
124
+ #{class_name}[arg]
124
125
  when Symbol
125
126
  #{%Q(raise ArgumentError, "#{foreign_key}=(Symbol): use `lookup_for :column, symbolize: true` to allow symbols") unless options[:symbolize]}
126
- #{class_name}[arg].try(:id)
127
+ #{class_name}[arg]
127
128
  when #{class_name}
128
- raise ArgumentError, "self.#{foreign_key}=(#{class_name}): must be saved" unless arg.id
129
- arg.id
129
+ raise ArgumentError, "self.#{foreign_key}=(#{class_name}): must be saved" unless arg.persisted?
130
+ arg
130
131
  else
131
132
  raise TypeError, "#{foreign_key}=(arg): arg must be a String, Symbol, Integer, IPAddr, nil, or #{class_name}"
132
133
  end
133
134
 
134
- #{%Q(raise LookupBy::Error, "\#{arg.inspect} is not in the <#{class_name}> lookup cache" if arg.present? && value.nil?) if strict}
135
+ #{ %Q(raise LookupBy::Error, "\#{arg.inspect} is not in the <#{class_name}> lookup cache" if arg.present? && result.nil?) if strict }
135
136
 
136
- self.#{foreign_key} = value
137
+ if result.blank?
138
+ self.#{foreign_key} = nil
139
+ elsif result.persisted?
140
+ self.#{foreign_key} = result.id
141
+ elsif lookup_errors = result.errors[:#{lookup_field}]
142
+ lookup_errors.each do |msg|
143
+ errors.add :#{field}, msg
144
+ end
145
+ end
137
146
  end
138
147
  METHODS
139
148
  end
@@ -9,6 +9,7 @@ module LookupBy
9
9
  @primary_key_type = klass.columns_hash[@primary_key].type
10
10
  @field = options[:field].to_sym
11
11
  @cache = {}
12
+ @reverse = {}
12
13
  @order = options[:order] || @field
13
14
  @read = options[:find_or_create] || options[:find]
14
15
  @write = options[:find_or_create]
@@ -18,11 +19,13 @@ module LookupBy
18
19
  @testing = false
19
20
  @enabled = true
20
21
  @safe = options[:safe] || concurrent?
22
+ @mutex = Mutex.new if @safe
21
23
 
22
24
  @stats = { db: Hash.new(0), cache: Hash.new(0) }
23
25
 
24
26
  raise ArgumentError, %Q(unknown attribute "#{@field}" for <#{klass}>) unless klass.column_names.include?(@field.to_s)
25
27
 
28
+ # Order matters here, some instance variables depend on prior assignments.
26
29
  case options[:cache]
27
30
  when true
28
31
  @type = :all
@@ -35,6 +38,7 @@ module LookupBy
35
38
  @type = :lru
36
39
  @limit = options[:cache]
37
40
  @cache = @safe ? Caching::SafeLRU.new(@limit) : Caching::LRU.new(@limit)
41
+ @reverse = @safe ? Caching::SafeLRU.new(@limit) : Caching::LRU.new(@limit)
38
42
  @read = true
39
43
  @write ||= false
40
44
  @testing = true if Rails.env.test? && @write
@@ -53,8 +57,8 @@ module LookupBy
53
57
  clear
54
58
 
55
59
  ::ActiveRecord::Base.connection.send :log, "", "#{@klass.name} Load Cache All" do
56
- @klass.order(@order).each do |i|
57
- @cache[i.id] = i
60
+ @klass.order(@order).each do |object|
61
+ cache_write(object)
58
62
  end
59
63
  end
60
64
  end
@@ -65,19 +69,23 @@ module LookupBy
65
69
 
66
70
  def create(*args, &block)
67
71
  created = @klass.create(*args, &block)
68
- @cache[created.id] = created if created && cache?
72
+
73
+ cache_write(created) if cache?
74
+
69
75
  created
70
76
  end
71
77
 
72
78
  def create!(*args, &block)
73
79
  created = @klass.create!(*args, &block)
74
- @cache[created.id] = created if cache?
80
+
81
+ cache_write(created) if cache?
82
+
75
83
  created
76
84
  end
77
85
 
78
86
  def seed(*values)
79
87
  @klass.transaction(requires_new: true) do
80
- values.each { |value| create!(@field => value) }
88
+ values.map { |value| @klass.where(@field => value).first_or_create! }
81
89
  end
82
90
  end
83
91
 
@@ -89,7 +97,7 @@ module LookupBy
89
97
  found = cache_read(value) if cache?
90
98
  found ||= db_read(value) if @read || !@enabled
91
99
 
92
- @cache[found.id] = found if found && cache?
100
+ cache_write(found) if cache?
93
101
 
94
102
  found ||= db_write(value) if @write
95
103
 
@@ -132,13 +140,9 @@ module LookupBy
132
140
 
133
141
  private
134
142
 
143
+ # RAILS_ENV=test will not use the SafeLRU
135
144
  def concurrent?
136
- case Rails::VERSION::MAJOR
137
- when 4 then Rails.configuration.cache_classes && Rails.configuration.eager_load
138
- when 3 then Rails.configuration.allow_concurrency
139
- else
140
- true
141
- end
145
+ Rails.configuration.cache_classes && Rails.configuration.eager_load
142
146
  end
143
147
 
144
148
  def primary_key?(value)
@@ -154,26 +158,61 @@ module LookupBy
154
158
  @klass.new(@field => value).send(@field)
155
159
  end
156
160
 
157
- def cache_read(value)
158
- if primary_key?(value)
159
- found = @cache[value]
160
- else
161
- found = @cache.values.detect { |o| o.send(@field) == value }
161
+
162
+ if Rails.env.production?
163
+ def cache_read(value)
164
+ if primary_key?(value)
165
+ @cache[value]
166
+ else
167
+ @reverse[value]
168
+ end
162
169
  end
170
+ else
171
+ def cache_read(value)
172
+ found = if primary_key?(value)
173
+ @cache[value]
174
+ else
175
+ @reverse[value]
176
+ end
163
177
 
164
- increment :cache, found ? :hit : :miss
178
+ increment :cache, found ? :hit : :miss
165
179
 
166
- found
180
+ found
181
+ end
167
182
  end
168
183
 
169
- def db_read(value)
170
- increment :db, :get
184
+ if Rails.env.production?
185
+ def db_read(value)
186
+ @klass.where(column_for(value) => value).first
187
+ end
188
+ else
189
+ def db_read(value)
190
+ increment :db, :get
171
191
 
172
- found = @klass.where(column_for(value) => value).first
192
+ found = @klass.where(column_for(value) => value).first
173
193
 
174
- increment :db, found ? :hit : :miss
194
+ increment :db, found ? :hit : :miss
175
195
 
176
- found
196
+ found
197
+ end
198
+ end
199
+
200
+ if @safe
201
+ def cache_write(object)
202
+ return unless object
203
+
204
+ @mutex.synchronize do
205
+ @cache[object.id] = object
206
+ @reverse[object.send(@field)] = object
207
+ end
208
+ end
209
+ else
210
+ def cache_write(object)
211
+ return unless object
212
+
213
+ @cache[object.id] = object
214
+ @reverse[object.send(@field)] = object
215
+ end
177
216
  end
178
217
 
179
218
  def db_write(value)
@@ -182,7 +221,7 @@ module LookupBy
182
221
  return if column == @primary_key
183
222
 
184
223
  @klass.transaction(requires_new: true) do
185
- @klass.create!(column => value)
224
+ @klass.create(column => value)
186
225
  end
187
226
  rescue ActiveRecord::RecordNotUnique
188
227
  db_read(value)
@@ -32,8 +32,8 @@ module LookupBy
32
32
  options.assert_valid_keys :allow_blank, :order, :cache, :normalize, :find, :find_or_create, :raise, :safe
33
33
 
34
34
  raise "#{self} already called lookup_by" if is_a? Lookup::ClassMethods
35
- raise "#{self} responds_to :[], needed for lookup_by" if respond_to? :[]
36
- raise "#{self} responds_to :lookup, needed for lookup_by" if respond_to? :lookup
35
+ raise "#{self} responds_to .[], needed for lookup_by" if respond_to? :[]
36
+ raise "#{self} responds_to .lookup, needed for lookup_by" if respond_to? :lookup
37
37
 
38
38
  extend ClassMethods
39
39
 
@@ -57,13 +57,15 @@ module LookupBy
57
57
  @lookup = Cache.new(self, options.merge(field: field))
58
58
  @lookup.reload
59
59
  end
60
+
61
+ LookupBy.register self
60
62
  end
61
63
  end
62
64
 
63
65
  module ClassMethods
64
66
  # TODO: Rails 4 needs to return a proxy object here
65
67
  def all(*args)
66
- return super if Rails::VERSION::MAJOR > 3
68
+ return super if Rails::VERSION::MAJOR >= 4
67
69
  return super if @lookup.read_through?
68
70
  return super if args.any?
69
71
 
@@ -77,10 +79,11 @@ module LookupBy
77
79
  @lookup.cache.size
78
80
  end
79
81
 
80
- def pluck(column_name)
82
+ def pluck(*column_names)
81
83
  return super if @lookup.read_through?
84
+ return super if column_names.size > 1
82
85
 
83
- @lookup.cache.values.map { |o| o.send(column_name) }
86
+ @lookup.cache.values.map { |o| o.send(column_names.first) }
84
87
  end
85
88
 
86
89
  def [](*args)
@@ -100,6 +103,12 @@ module LookupBy
100
103
  else return args.map { |arg| self[arg] }
101
104
  end
102
105
  end
106
+
107
+ def seed(*args)
108
+ super if defined?(super)
109
+
110
+ @lookup.seed *args
111
+ end
103
112
  end
104
113
 
105
114
  module InstanceMethods
@@ -116,6 +125,21 @@ module LookupBy
116
125
  end
117
126
 
118
127
  module SchemaMethods
128
+ # Create a lookup table.
129
+ #
130
+ # @example
131
+ # create_lookup_table :statuses, schema: "custom", small: true
132
+ #
133
+ # create_lookup_table :companies do |t|
134
+ # t.string :short_name
135
+ # end
136
+ #
137
+ # @param [Symbol] name
138
+ # @param [Hash] options
139
+ # @option options [Symbol] lookup_column Name of the lookup column.
140
+ # @option options [Symbol] lookup_type Type of the lookup column, _e.g. :text, :uuid, or :inet_.
141
+ # @option options [String] primary_key Name of the primary key.
142
+ # @option options [String] schema
119
143
  def create_lookup_table(name, options = {})
120
144
  options.symbolize_keys!
121
145
 
@@ -1,3 +1,3 @@
1
1
  module LookupBy
2
- VERSION = "0.9.1"
2
+ VERSION = "0.10.0"
3
3
  end
data/lib/lookup_by.rb CHANGED
@@ -4,6 +4,12 @@ require "lookup_by/railtie" if defined? Rails
4
4
  module LookupBy
5
5
  class Error < StandardError; end
6
6
 
7
+ mattr_accessor :classes
8
+ self.classes = []
9
+
10
+ mattr_accessor :mutex
11
+ self.mutex = Mutex.new
12
+
7
13
  UUID_REGEX = /\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\Z/
8
14
  UUID_REGEX_V4 = /\A\h{8}-\h{4}-4\h{3}-[89aAbB]\h{3}-\h{12}\Z/
9
15
 
@@ -16,6 +22,34 @@ module LookupBy
16
22
  autoload :LRU, "lookup_by/caching/lru"
17
23
  autoload :SafeLRU, "lookup_by/caching/safe_lru"
18
24
  end
25
+
26
+ class << self
27
+ def register(klass)
28
+ mutex.synchronize do
29
+ self.classes << klass unless classes.include?(klass)
30
+ end
31
+ end
32
+
33
+ def lookups
34
+ classes.map { |klass| klass.lookup }
35
+ end
36
+
37
+ def clear
38
+ lookups.each { |lookup| lookup.clear }
39
+ end
40
+
41
+ def disable
42
+ lookups.each { |lookup| lookup.disable! }
43
+ end
44
+
45
+ def enable
46
+ lookups.each { |lookup| lookup.enable! }
47
+ end
48
+
49
+ def reload
50
+ lookups.each { |lookup| lookup.reload }
51
+ end
52
+ end
19
53
  end
20
54
 
21
55
  begin
data/lookup_by.gemspec CHANGED
@@ -22,8 +22,8 @@ Gem::Specification.new do |gem|
22
22
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
23
23
  gem.require_paths = ["lib"]
24
24
 
25
- gem.add_dependency "rails", ">= 3.2.0"
25
+ gem.add_dependency "rails", ">= 4.0.0"
26
26
 
27
- gem.add_development_dependency "bundler", "~> 1.3"
27
+ gem.add_development_dependency "bundler", ">= 1.7.0"
28
28
  gem.add_development_dependency "rake"
29
29
  end
@@ -62,12 +62,12 @@ describe LookupBy::Association do
62
62
  it_behaves_like "a lookup for", :city
63
63
 
64
64
  it "accepts Integers" do
65
- subject.city = City.where(city: "New York").first.id
65
+ subject.city = City["New York"].id
66
66
  expect(subject.city).to eq "New York"
67
67
  end
68
68
 
69
69
  it "rejects symbols" do
70
- expect { subject.city = :'New York' }.to raise_error ArgumentError
70
+ expect { subject.city = :invalid }.to raise_error ArgumentError
71
71
  end
72
72
 
73
73
  it "returns strings" do
@@ -100,6 +100,8 @@ describe LookupBy::Association do
100
100
  end
101
101
 
102
102
  context "Address.lookup_for :street" do
103
+ it_behaves_like "a lookup for", :street
104
+
103
105
  it "accepts write-through values" do
104
106
  expect { subject.street = "Dearborn Street" }.to change(Street, :count)
105
107
  end
@@ -192,3 +194,18 @@ describe LookupBy::Association, 'scopes' do
192
194
  end
193
195
  end
194
196
  end
197
+
198
+ context 'validation' do
199
+ subject { Account.new(phone_number: "invalid") }
200
+
201
+ # it { is_expected.to have(2).errors_on(:phone_number) }
202
+ # it { expect(subject).to have(2).errors_on(:phone_number) }
203
+
204
+ # it 'bubbles errors' do
205
+ # expect(subject).to have(2).errors_on(:phone_number)
206
+ # end
207
+
208
+ it 'bubbles errors' do
209
+ expect(subject.errors[:phone_number].size).to eq(2)
210
+ end
211
+ end
@@ -1,3 +1,5 @@
1
1
  class Account < ActiveRecord::Base
2
- lookup_by :account, cache: true, find: true
2
+ lookup_by :account, cache: true, find: true
3
+
4
+ lookup_for :phone_number
3
5
  end
@@ -1,2 +1,3 @@
1
1
  class Country < ActiveRecord::Base
2
+ # Empty intentionally
2
3
  end
@@ -0,0 +1,5 @@
1
+ class PhoneNumber < ActiveRecord::Base
2
+ lookup_by :phone_number, find_or_create: true, allow_blank: false
3
+
4
+ validates :phone_number, format: /\d{3}-\d{3}-\d{4}/, length: 10..11
5
+ end
@@ -1,7 +1,5 @@
1
1
  require File.expand_path('../boot', __FILE__)
2
2
 
3
- # require "rails/all"
4
-
5
3
  require 'active_record/railtie'
6
4
 
7
5
  # Require the gems listed in Gemfile, including any gems
@@ -42,11 +42,3 @@ test:
42
42
  adapter: postgresql
43
43
  database: lookup_by_test
44
44
  username: postgres
45
-
46
- production:
47
- adapter: postgresql
48
- encoding: unicode
49
- database: dummy_production
50
- pool: 5
51
- username: dummy
52
- password:
@@ -5,11 +5,13 @@ class CreateTables < ActiveRecord::Migration
5
5
  create_lookup_table :user_agents
6
6
  create_lookup_table :email_addresses
7
7
 
8
- create_lookup_table :accounts
8
+
9
9
  create_lookup_table :statuses, small: true
10
10
 
11
11
  create_lookup_table :ip_addresses, lookup_type: :inet
12
12
 
13
+ create_lookup_table :phone_numbers
14
+
13
15
  create_lookup_table :uncacheables
14
16
  create_lookup_table :unfindables
15
17
 
@@ -24,7 +26,11 @@ class CreateTables < ActiveRecord::Migration
24
26
 
25
27
  create_lookup_table :paths, schema: 'traffic', id: :uuid
26
28
 
27
- create_table :addresses, primary_key: "address_id" do |t|
29
+ create_lookup_table :accounts do |t|
30
+ t.belongs_to :phone_number
31
+ end
32
+
33
+ create_lookup_table :addresses do |t|
28
34
  t.belongs_to :city
29
35
  t.belongs_to :state
30
36
  t.belongs_to :postal_code
@@ -56,7 +56,8 @@ SET default_with_oids = false;
56
56
 
57
57
  CREATE TABLE accounts (
58
58
  account_id integer NOT NULL,
59
- account text NOT NULL
59
+ account text NOT NULL,
60
+ phone_number_id integer
60
61
  );
61
62
 
62
63
 
@@ -85,6 +86,7 @@ ALTER SEQUENCE accounts_account_id_seq OWNED BY accounts.account_id;
85
86
 
86
87
  CREATE TABLE addresses (
87
88
  address_id integer NOT NULL,
89
+ address text NOT NULL,
88
90
  city_id integer,
89
91
  state_id integer,
90
92
  postal_code_id integer,
@@ -228,6 +230,35 @@ CREATE SEQUENCE ip_addresses_ip_address_id_seq
228
230
  ALTER SEQUENCE ip_addresses_ip_address_id_seq OWNED BY ip_addresses.ip_address_id;
229
231
 
230
232
 
233
+ --
234
+ -- Name: phone_numbers; Type: TABLE; Schema: public; Owner: -; Tablespace:
235
+ --
236
+
237
+ CREATE TABLE phone_numbers (
238
+ phone_number_id integer NOT NULL,
239
+ phone_number text NOT NULL
240
+ );
241
+
242
+
243
+ --
244
+ -- Name: phone_numbers_phone_number_id_seq; Type: SEQUENCE; Schema: public; Owner: -
245
+ --
246
+
247
+ CREATE SEQUENCE phone_numbers_phone_number_id_seq
248
+ START WITH 1
249
+ INCREMENT BY 1
250
+ NO MINVALUE
251
+ NO MAXVALUE
252
+ CACHE 1;
253
+
254
+
255
+ --
256
+ -- Name: phone_numbers_phone_number_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
257
+ --
258
+
259
+ ALTER SEQUENCE phone_numbers_phone_number_id_seq OWNED BY phone_numbers.phone_number_id;
260
+
261
+
231
262
  --
232
263
  -- Name: postal_codes; Type: TABLE; Schema: public; Owner: -; Tablespace:
233
264
  --
@@ -583,6 +614,13 @@ ALTER TABLE ONLY email_addresses ALTER COLUMN email_address_id SET DEFAULT nextv
583
614
  ALTER TABLE ONLY ip_addresses ALTER COLUMN ip_address_id SET DEFAULT nextval('ip_addresses_ip_address_id_seq'::regclass);
584
615
 
585
616
 
617
+ --
618
+ -- Name: phone_number_id; Type: DEFAULT; Schema: public; Owner: -
619
+ --
620
+
621
+ ALTER TABLE ONLY phone_numbers ALTER COLUMN phone_number_id SET DEFAULT nextval('phone_numbers_phone_number_id_seq'::regclass);
622
+
623
+
586
624
  --
587
625
  -- Name: postal_code_id; Type: DEFAULT; Schema: public; Owner: -
588
626
  --
@@ -701,6 +739,14 @@ ALTER TABLE ONLY ip_addresses
701
739
  ADD CONSTRAINT ip_addresses_pkey PRIMARY KEY (ip_address_id);
702
740
 
703
741
 
742
+ --
743
+ -- Name: phone_numbers_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
744
+ --
745
+
746
+ ALTER TABLE ONLY phone_numbers
747
+ ADD CONSTRAINT phone_numbers_pkey PRIMARY KEY (phone_number_id);
748
+
749
+
704
750
  --
705
751
  -- Name: postal_codes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
706
752
  --
@@ -800,6 +846,13 @@ SET search_path = public, pg_catalog;
800
846
  CREATE UNIQUE INDEX accounts__u_account ON accounts USING btree (account);
801
847
 
802
848
 
849
+ --
850
+ -- Name: addresses__u_address; Type: INDEX; Schema: public; Owner: -; Tablespace:
851
+ --
852
+
853
+ CREATE UNIQUE INDEX addresses__u_address ON addresses USING btree (address);
854
+
855
+
803
856
  --
804
857
  -- Name: cities__u_city; Type: INDEX; Schema: public; Owner: -; Tablespace:
805
858
  --
@@ -828,6 +881,13 @@ CREATE UNIQUE INDEX email_addresses__u_email_address ON email_addresses USING bt
828
881
  CREATE UNIQUE INDEX ip_addresses__u_ip_address ON ip_addresses USING btree (ip_address);
829
882
 
830
883
 
884
+ --
885
+ -- Name: phone_numbers__u_phone_number; Type: INDEX; Schema: public; Owner: -; Tablespace:
886
+ --
887
+
888
+ CREATE UNIQUE INDEX phone_numbers__u_phone_number ON phone_numbers USING btree (phone_number);
889
+
890
+
831
891
  --
832
892
  -- Name: postal_codes__u_postal_code; Type: INDEX; Schema: public; Owner: -; Tablespace:
833
893
  --
@@ -1,6 +1,42 @@
1
1
  require "rails_helper"
2
2
  require "lookup_by"
3
3
 
4
+ describe LookupBy do
5
+ describe ".register" do
6
+ it "adds its argument to .lookups" do
7
+ LookupBy.register(Array)
8
+ expect(LookupBy.classes).to include(Array)
9
+ LookupBy.classes.delete(Array)
10
+ end
11
+
12
+ it "doesn't register classes twice" do
13
+ LookupBy.register(Array)
14
+ LookupBy.register(Array)
15
+
16
+ expect(LookupBy.classes.select { |l| l == Array }.size).to eq(1)
17
+
18
+ LookupBy.classes.delete(Array)
19
+ end
20
+ end
21
+
22
+ describe ".clear" do
23
+ it "clears all lookup caches" do
24
+ Path.lookup.cache[1] = "remove-this"
25
+ expect { LookupBy.clear }.to change { Path.lookup.cache.size }
26
+ end
27
+ end
28
+
29
+ describe ".disable and .enable" do
30
+ it "affects all lookups" do
31
+ expect(City.lookup.enabled?).to eq(true)
32
+ LookupBy.disable
33
+ expect(City.lookup.enabled?).to eq(false)
34
+ LookupBy.enable
35
+ expect(City.lookup.enabled?).to eq(true)
36
+ end
37
+ end
38
+ end
39
+
4
40
  describe ::ActiveRecord::Base do
5
41
  describe "macro methods" do
6
42
  subject { described_class }
@@ -9,26 +45,50 @@ describe ::ActiveRecord::Base do
9
45
  it { is_expected.to respond_to :is_a_lookup? }
10
46
  end
11
47
 
48
+ describe ".lookup_by" do
49
+ class CityTest < ActiveRecord::Base
50
+ self.table_name = "cities"
51
+ lookup_by :city
52
+ end
53
+
54
+ it "registers lookup models" do
55
+ expect(LookupBy.classes).to include(CityTest)
56
+ end
57
+ end
58
+
12
59
  describe "instance methods" do
13
60
  subject { Status.new }
14
61
 
15
62
  it { is_expected.to respond_to :name }
16
63
  end
17
-
18
64
  end
19
65
 
20
- describe LookupBy::Lookup do
21
- describe "cache" do
22
- it "seeds values" do
23
- City.lookup.seed 'Boston'
24
- City.lookup.seed 'Chicago', 'New York City'
66
+ describe LookupBy::Lookup::ClassMethods do
67
+ describe "#seed" do
68
+ it "accepts multiple values" do
69
+ City.seed 'Boston'
70
+ City.seed 'Chicago', 'New York City'
25
71
 
26
- names = City.all.map(&:name).sort
27
- expect(names.sort).to eq(['Boston', 'Chicago', 'New York City'])
28
- City.lookup.clear
72
+ expect(City.pluck(:city).sort).to eq(['Boston', 'Chicago', 'New York City'])
73
+ end
74
+
75
+ it "accepts duplicates" do
76
+ expect { City.seed 'Chicago', 'Chicago' }.not_to raise_error
77
+
78
+ if Rails::VERSION::MAJOR == 4 && Rails::VERSION::MINOR == 0
79
+ expect(City.pluck(:city)).to eq(['Chicago'])
80
+ else
81
+ expect(City.pluck(:name)).to eq(['Chicago'])
82
+ end
83
+ end
84
+
85
+ it "returns objects" do
86
+ expect(City.seed 'Chicago').to eq(City.all)
29
87
  end
30
88
  end
89
+ end
31
90
 
91
+ describe LookupBy::Lookup do
32
92
  context "Uncacheable.lookup_by :column, cache: true, find_or_create: true" do
33
93
  it "fails when trying to cache and write-through" do
34
94
  expect { Uncacheable }.to raise_error
@@ -83,7 +143,12 @@ describe LookupBy::Lookup do
83
143
  it_behaves_like "a strict cache"
84
144
 
85
145
  it "preloads the cache" do
86
- expect(subject.lookup.cache).not_to be_empty
146
+ class StateTest < ActiveRecord::Base
147
+ self.table_name = "states"
148
+ lookup_by :state, cache: true
149
+ end
150
+
151
+ expect(StateTest.lookup.cache).not_to be_empty
87
152
  end
88
153
  end
89
154
 
data/spec/rails_helper.rb CHANGED
@@ -39,8 +39,6 @@ Dir[Rails.root.join( "../support/**/*.rb")].each { |f| require f }
39
39
  # If you are not using ActiveRecord, you can remove this line.
40
40
  ActiveRecord::Migration.maintain_test_schema! if ActiveRecord::Migration.respond_to?(:maintain_test_schema!)
41
41
 
42
- # ActiveRecord::Migration.maintain_test_schema! if defined?(ActiveRecord::Migration) && ActiveRecord::Migration.respond_to?(:maintain_test_schema!)
43
-
44
42
  RSpec.configure do |config|
45
43
  config.use_transactional_fixtures = true
46
44
 
data/spec/spec_helper.rb CHANGED
@@ -15,6 +15,32 @@
15
15
  #
16
16
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
17
17
  RSpec.configure do |config|
18
+ # rspec-expectations config goes here. You can use an alternate
19
+ # assertion/expectation library such as wrong or the stdlib/minitest
20
+ # assertions if you prefer.
21
+ config.expect_with :rspec do |expectations|
22
+ # Enable only the newer, non-monkey-patching expect syntax.
23
+ # For more details, see:
24
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
25
+ expectations.syntax = :expect
26
+ end
27
+
28
+ # rspec-mocks config goes here. You can use an alternate test double
29
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
30
+ config.mock_with :rspec do |mocks|
31
+ # Enable only the newer, non-monkey-patching expect syntax.
32
+ # For more details, see:
33
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
34
+ mocks.syntax = :expect
35
+
36
+ # Prevents you from mocking or stubbing a method that does not exist on
37
+ # a real object. This is generally recommended.
38
+ mocks.verify_partial_doubles = true
39
+ end
40
+
41
+ # The settings below are suggested to provide a good initial experience
42
+ # with RSpec, but feel free to customize to your heart's content.
43
+
18
44
  # These two settings work together to allow you to limit a spec run
19
45
  # to individual examples or groups you care about by tagging them with
20
46
  # `:focus` metadata. When nothing is tagged with `:focus`, all examples
@@ -49,26 +75,11 @@ RSpec.configure do |config|
49
75
  # as the one that triggered the failure.
50
76
  Kernel.srand config.seed
51
77
 
52
- # rspec-expectations config goes here. You can use an alternate
53
- # assertion/expectation library such as wrong or the stdlib/minitest
54
- # assertions if you prefer.
55
- config.expect_with :rspec do |expectations|
56
- # Enable only the newer, non-monkey-patching expect syntax.
57
- # For more details, see:
58
- # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
59
- expectations.syntax = :expect
78
+ config.before(:each) do
79
+ LookupBy.reload
60
80
  end
61
81
 
62
- # rspec-mocks config goes here. You can use an alternate test double
63
- # library (such as bogus or mocha) by changing the `mock_with` option here.
64
- config.mock_with :rspec do |mocks|
65
- # Enable only the newer, non-monkey-patching expect syntax.
66
- # For more details, see:
67
- # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
68
- mocks.syntax = :expect
69
-
70
- # Prevents you from mocking or stubbing a method that does not exist on
71
- # a real object. This is generally recommended.
72
- mocks.verify_partial_doubles = true
82
+ config.after(:each) do
83
+ LookupBy.clear
73
84
  end
74
85
  end
@@ -204,7 +204,7 @@ shared_examples "a lookup for" do |field|
204
204
  end
205
205
 
206
206
  it "converts empty strings to nil" do
207
- subject.send "#{field}=", ""
207
+ subject.send "#{field}=", ""
208
208
  expect(subject.send(field)).to be_nil
209
209
  end
210
210
 
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.9.1
4
+ version: 0.10.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-08-21 00:00:00.000000000 Z
11
+ date: 2015-02-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 3.2.0
19
+ version: 4.0.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 3.2.0
26
+ version: 4.0.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '1.3'
33
+ version: 1.7.0
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '1.3'
40
+ version: 1.7.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -62,13 +62,13 @@ files:
62
62
  - ".gitignore"
63
63
  - ".ruby-version"
64
64
  - ".travis.yml"
65
+ - ".yardopts"
65
66
  - Appraisals
66
67
  - Gemfile
67
68
  - MIT-LICENSE
68
69
  - README.md
69
70
  - Rakefile
70
71
  - TODO.md
71
- - gemfiles/rails_3.2.gemfile
72
72
  - gemfiles/rails_4.0.gemfile
73
73
  - gemfiles/rails_4.1.gemfile
74
74
  - gemfiles/rails_4.2.gemfile
@@ -96,6 +96,7 @@ files:
96
96
  - spec/dummy/app/models/email_address.rb
97
97
  - spec/dummy/app/models/ip_address.rb
98
98
  - spec/dummy/app/models/path.rb
99
+ - spec/dummy/app/models/phone_number.rb
99
100
  - spec/dummy/app/models/postal_code.rb
100
101
  - spec/dummy/app/models/raisin.rb
101
102
  - spec/dummy/app/models/read_through_raisin.rb
@@ -151,7 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
151
152
  version: '0'
152
153
  requirements: []
153
154
  rubyforge_project:
154
- rubygems_version: 2.4.1
155
+ rubygems_version: 2.4.5
155
156
  signing_key:
156
157
  specification_version: 4
157
158
  summary: A thread-safe lookup table cache for ActiveRecord
@@ -167,6 +168,7 @@ test_files:
167
168
  - spec/dummy/app/models/email_address.rb
168
169
  - spec/dummy/app/models/ip_address.rb
169
170
  - spec/dummy/app/models/path.rb
171
+ - spec/dummy/app/models/phone_number.rb
170
172
  - spec/dummy/app/models/postal_code.rb
171
173
  - spec/dummy/app/models/raisin.rb
172
174
  - spec/dummy/app/models/read_through_raisin.rb
@@ -1,22 +0,0 @@
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 "rspec-rails"
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 => "../"