lookup_by 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/.travis.yml +5 -4
- data/.yardopts +1 -0
- data/Appraisals +1 -5
- data/Gemfile +1 -1
- data/README.md +39 -8
- data/gemfiles/rails_4.0.gemfile +1 -1
- data/gemfiles/rails_4.1.gemfile +1 -1
- data/gemfiles/rails_4.2.gemfile +2 -2
- data/lib/lookup_by/association.rb +16 -7
- data/lib/lookup_by/cache.rb +64 -25
- data/lib/lookup_by/lookup.rb +29 -5
- data/lib/lookup_by/version.rb +1 -1
- data/lib/lookup_by.rb +34 -0
- data/lookup_by.gemspec +2 -2
- data/spec/association_spec.rb +19 -2
- data/spec/dummy/app/models/account.rb +3 -1
- data/spec/dummy/app/models/country.rb +1 -0
- data/spec/dummy/app/models/phone_number.rb +5 -0
- data/spec/dummy/config/application.rb +0 -2
- data/spec/dummy/config/database.yml +0 -8
- data/spec/dummy/db/migrate/20121019040009_create_tables.rb +8 -2
- data/spec/dummy/db/structure.sql +61 -1
- data/spec/lookup_by_spec.rb +75 -10
- data/spec/rails_helper.rb +0 -2
- data/spec/spec_helper.rb +30 -19
- data/spec/support/shared_examples_for_a_lookup.rb +1 -1
- metadata +12 -10
- data/gemfiles/rails_3.2.gemfile +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b7b482a9755de8d24837b3555d53cc416b77ac87
|
4
|
+
data.tar.gz: 087b776e7ad8c89b4fcb90a63769820c6279d81e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d728f55c1da30eb53de8d33397bb4b6fbe1c1d1b9003d03f11db296d34455c65d5a0f16058f904b298b9050cc5b86ee69d88223cb942c6ff9419dadf34e2501
|
7
|
+
data.tar.gz: 62479abc739d5125a2feffa8589236f4e1f83fd9a391b343bb5f49ac3510dd05293c221cb4b3521362a659804a021ce7a114556d045fc2644a223faa88a7afd9
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby-2.
|
1
|
+
ruby-2.2.0
|
data/.travis.yml
CHANGED
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
|
10
|
+
gem 'rails', '~> 4.2.0'
|
15
11
|
end
|
data/Gemfile
CHANGED
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
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
273
|
+
### Raise on miss
|
243
274
|
|
244
275
|
Configure cache misses to raise a `LookupBy::RecordNotFound` error.
|
245
276
|
|
data/gemfiles/rails_4.0.gemfile
CHANGED
data/gemfiles/rails_4.1.gemfile
CHANGED
data/gemfiles/rails_4.2.gemfile
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
|
-
gem "rails", "~> 4.2.0
|
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
|
-
|
120
|
+
result = case arg
|
120
121
|
when nil
|
121
122
|
nil
|
122
123
|
when String, Integer, IPAddr
|
123
|
-
#{class_name}[arg]
|
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]
|
127
|
+
#{class_name}[arg]
|
127
128
|
when #{class_name}
|
128
|
-
raise ArgumentError, "self.#{foreign_key}=(#{class_name}): must be saved" unless arg.
|
129
|
-
arg
|
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? &&
|
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
|
-
|
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
|
data/lib/lookup_by/cache.rb
CHANGED
@@ -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 |
|
57
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
-
|
178
|
+
increment :cache, found ? :hit : :miss
|
165
179
|
|
166
|
-
|
180
|
+
found
|
181
|
+
end
|
167
182
|
end
|
168
183
|
|
169
|
-
|
170
|
-
|
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
|
-
|
192
|
+
found = @klass.where(column_for(value) => value).first
|
173
193
|
|
174
|
-
|
194
|
+
increment :db, found ? :hit : :miss
|
175
195
|
|
176
|
-
|
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
|
224
|
+
@klass.create(column => value)
|
186
225
|
end
|
187
226
|
rescue ActiveRecord::RecordNotUnique
|
188
227
|
db_read(value)
|
data/lib/lookup_by/lookup.rb
CHANGED
@@ -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
|
36
|
-
raise "#{self} responds_to
|
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
|
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(
|
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(
|
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
|
|
data/lib/lookup_by/version.rb
CHANGED
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", ">=
|
25
|
+
gem.add_dependency "rails", ">= 4.0.0"
|
26
26
|
|
27
|
-
gem.add_development_dependency "bundler", "
|
27
|
+
gem.add_development_dependency "bundler", ">= 1.7.0"
|
28
28
|
gem.add_development_dependency "rake"
|
29
29
|
end
|
data/spec/association_spec.rb
CHANGED
@@ -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
|
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 = :
|
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
|
@@ -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
|
-
|
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
|
-
|
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
|
data/spec/dummy/db/structure.sql
CHANGED
@@ -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
|
--
|
data/spec/lookup_by_spec.rb
CHANGED
@@ -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 "
|
22
|
-
it "
|
23
|
-
City.
|
24
|
-
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
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
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
|
-
|
53
|
-
|
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
|
-
|
63
|
-
|
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
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lookup_by
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.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:
|
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:
|
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:
|
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:
|
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:
|
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.
|
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
|
data/gemfiles/rails_3.2.gemfile
DELETED
@@ -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 => "../"
|