lookup_by 0.1.3 → 0.2.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/lib/lookup_by/association.rb +35 -15
- data/lib/lookup_by/cache.rb +4 -4
- data/lib/lookup_by/lookup.rb +15 -10
- data/lib/lookup_by/version.rb +1 -1
- data/spec/association_spec.rb +27 -3
- data/spec/dummy/app/models/address.rb +1 -1
- data/spec/support/shared_examples_for_a_lookup.rb +14 -4
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c32a15a02ac5c67549366075fac6d76db2122564
|
4
|
+
data.tar.gz: 07aa69751c19f10a96b573eb08d562e7290aee47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 14f87c363d0c346dafb9f37a1b015a721786c0ba4223ddfa1bb656adfe0dbf1d9764ea3724ac3330b336719382c5f014ae393b97f0c6eeeff3a936844d825af5
|
7
|
+
data.tar.gz: c1cca0a1500aedf3a1f10ffaceca6dbe5d1fd870b86c9a0b23858f5c075aeaf6901c84f0fd73559a31c60a5cc64149c2f1925af69ab57935fa56d623f8550662
|
@@ -16,14 +16,31 @@ module LookupBy
|
|
16
16
|
def lookup_for field, options = {}
|
17
17
|
return unless table_exists?
|
18
18
|
|
19
|
+
options.symbolize_keys!
|
20
|
+
options.assert_valid_keys(:class_name, :foreign_key, :symbolize, :strict, :scope)
|
21
|
+
|
19
22
|
field = field.to_sym
|
20
23
|
|
21
|
-
%W(#{field} raw_#{field} #{field}= #{field}_before_type_cast).map(&:to_sym).each do |method|
|
24
|
+
%W(#{field} raw_#{field} #{field}= #{field}_before_type_cast #{field}?).map(&:to_sym).each do |method|
|
22
25
|
raise Error, "method `#{method}` already exists on #{self.inspect}" if instance_methods.include? method
|
23
26
|
end
|
24
27
|
|
25
|
-
|
26
|
-
|
28
|
+
singleton_class.class_eval do
|
29
|
+
attr_reader :lookups
|
30
|
+
end
|
31
|
+
|
32
|
+
@lookups ||= []
|
33
|
+
@lookups << field
|
34
|
+
|
35
|
+
scope_name = "with_#{field}" unless options[:scope] == false
|
36
|
+
|
37
|
+
if scope_name
|
38
|
+
single_scope = scope_name
|
39
|
+
plural_scope = scope_name.pluralize
|
40
|
+
|
41
|
+
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)
|
42
|
+
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)
|
43
|
+
end
|
27
44
|
|
28
45
|
class_name = options[:class_name] || field
|
29
46
|
class_name = class_name.to_s.camelize
|
@@ -31,22 +48,21 @@ module LookupBy
|
|
31
48
|
foreign_key = options[:foreign_key] || "#{field}_id"
|
32
49
|
foreign_key = foreign_key.to_sym
|
33
50
|
|
51
|
+
raise Error, "foreign key `#{foreign_key}` is required on #{self}" unless attribute_names.include?(foreign_key.to_s)
|
52
|
+
|
34
53
|
strict = options[:strict]
|
35
54
|
strict = true if strict.nil?
|
36
55
|
|
37
|
-
|
38
|
-
|
39
|
-
|
56
|
+
class_eval <<-SCOPES, __FILE__, __LINE__.next if scope_name
|
57
|
+
scope :#{scope_name}, ->(name) { where(#{foreign_key}: #{class_name}[name]) }
|
58
|
+
scope :#{scope_name.pluralize}, ->(*names) { where(#{foreign_key}: #{class_name}[*names]) }
|
59
|
+
SCOPES
|
40
60
|
|
41
61
|
cast = options[:symbolize] ? ".to_sym" : ""
|
42
62
|
|
63
|
+
lookup_field = class_name.constantize.lookup.field
|
43
64
|
lookup_object = "#{class_name}[#{foreign_key}]"
|
44
65
|
|
45
|
-
class << self; attr_reader :lookups; end
|
46
|
-
|
47
|
-
@lookups ||= []
|
48
|
-
@lookups << field
|
49
|
-
|
50
66
|
class_eval <<-METHODS, __FILE__, __LINE__.next
|
51
67
|
def raw_#{field}
|
52
68
|
#{lookup_object}
|
@@ -57,16 +73,20 @@ module LookupBy
|
|
57
73
|
value ? value.#{lookup_field}#{cast} : nil
|
58
74
|
end
|
59
75
|
|
76
|
+
def #{field}?(name)
|
77
|
+
raise ArgumentError, "Invalid #{field} \#{name.inspect}" unless object = #{class_name}[name]
|
78
|
+
#{foreign_key} == object.id
|
79
|
+
end
|
80
|
+
|
60
81
|
def #{field}_before_type_cast
|
61
|
-
|
62
|
-
value.#{lookup_field}_before_type_cast
|
82
|
+
#{lookup_object}.#{lookup_field}_before_type_cast
|
63
83
|
end
|
64
84
|
|
65
85
|
def #{field}=(arg)
|
66
86
|
value = case arg
|
67
87
|
when "", nil
|
68
88
|
nil
|
69
|
-
when String,
|
89
|
+
when String, Integer
|
70
90
|
#{class_name}[arg].try(:id)
|
71
91
|
when Symbol
|
72
92
|
#{%Q(raise ArgumentError, "#{foreign_key}=(Symbol): use `lookup_for :column, symbolize: true` to allow symbols") unless options[:symbolize]}
|
@@ -75,7 +95,7 @@ module LookupBy
|
|
75
95
|
raise ArgumentError, "self.#{foreign_key}=(#{class_name}): must be saved" unless arg.id
|
76
96
|
arg.id
|
77
97
|
else
|
78
|
-
raise TypeError, "#{foreign_key}=(arg): arg must be a String, Symbol,
|
98
|
+
raise TypeError, "#{foreign_key}=(arg): arg must be a String, Symbol, Integer, nil, or #{class_name}"
|
79
99
|
end
|
80
100
|
|
81
101
|
#{%Q(raise LookupBy::Error, "\#{arg.inspect} is not in the <#{class_name}> lookup cache" if arg.present? && value.nil?) if strict}
|
data/lib/lookup_by/cache.rb
CHANGED
@@ -23,7 +23,7 @@ module LookupBy
|
|
23
23
|
when true
|
24
24
|
@type = :all
|
25
25
|
@read ||= false
|
26
|
-
when ::
|
26
|
+
when ::Integer
|
27
27
|
raise ArgumentError, "`#{@klass}.lookup_by :#{@field}` options[:find] must be true when caching N" if @read == false
|
28
28
|
|
29
29
|
@type = :lru
|
@@ -62,7 +62,7 @@ module LookupBy
|
|
62
62
|
def fetch(value)
|
63
63
|
increment :cache, :get
|
64
64
|
|
65
|
-
value = normalize(value)
|
65
|
+
value = normalize(value) if @normalize && !value.is_a?(Integer)
|
66
66
|
|
67
67
|
found = cache_read(value) if cache?
|
68
68
|
found ||= db_read(value) if @read
|
@@ -108,7 +108,7 @@ module LookupBy
|
|
108
108
|
end
|
109
109
|
|
110
110
|
def cache_read(value)
|
111
|
-
if value.is_a?
|
111
|
+
if value.is_a? Integer
|
112
112
|
found = @cache[value]
|
113
113
|
else
|
114
114
|
found = @cache.values.detect { |o| o.send(@field) == value }
|
@@ -138,7 +138,7 @@ module LookupBy
|
|
138
138
|
end
|
139
139
|
|
140
140
|
def column_for(value)
|
141
|
-
value.is_a?(
|
141
|
+
value.is_a?(Integer) ? @primary_key : @field
|
142
142
|
end
|
143
143
|
|
144
144
|
def cache?
|
data/lib/lookup_by/lookup.rb
CHANGED
@@ -71,14 +71,19 @@ module LookupBy
|
|
71
71
|
@lookup.cache.values.map { |o| o.send(column_name) }
|
72
72
|
end
|
73
73
|
|
74
|
-
def [](
|
75
|
-
case
|
76
|
-
when
|
77
|
-
when
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
74
|
+
def [](*args)
|
75
|
+
case args.size
|
76
|
+
when 0 then raise ArgumentError, "#{name}[*args]: at least one argument is required"
|
77
|
+
when 1
|
78
|
+
case arg = args.first
|
79
|
+
when nil, "" then nil
|
80
|
+
when String then @lookup.fetch(arg)
|
81
|
+
when Symbol then @lookup.fetch(arg.to_s)
|
82
|
+
when Integer then @lookup.fetch(arg)
|
83
|
+
when self then arg
|
84
|
+
else raise TypeError, "#{name}[arg]: arg must be at least one String, Symbol, Integer, nil, or #{name}"
|
85
|
+
end
|
86
|
+
else return args.map { |arg| self[arg] }
|
82
87
|
end
|
83
88
|
end
|
84
89
|
end
|
@@ -86,10 +91,10 @@ module LookupBy
|
|
86
91
|
module InstanceMethods
|
87
92
|
def ===(arg)
|
88
93
|
case arg
|
89
|
-
when Symbol, String,
|
94
|
+
when Symbol, String, Integer, nil
|
90
95
|
return self == self.class[arg]
|
91
96
|
when Array
|
92
|
-
return
|
97
|
+
return arg.any? { |i| self === i }
|
93
98
|
end
|
94
99
|
|
95
100
|
super
|
data/lib/lookup_by/version.rb
CHANGED
data/spec/association_spec.rb
CHANGED
@@ -17,13 +17,25 @@ describe ::ActiveRecord::Base do
|
|
17
17
|
public :define_method, :remove_method
|
18
18
|
end
|
19
19
|
|
20
|
-
[:foo, :foo=, :raw_foo, :foo_before_type_cast].each do |method|
|
20
|
+
[:foo, :foo=, :raw_foo, :foo_before_type_cast, :foo?].each do |method|
|
21
21
|
subject.define_method(method) { }
|
22
22
|
|
23
23
|
expect { subject.lookup_for :foo }.to raise_error LookupBy::Error, /already exists/
|
24
24
|
|
25
25
|
subject.remove_method(method)
|
26
26
|
end
|
27
|
+
|
28
|
+
class << subject.singleton_class
|
29
|
+
public :define_method, :remove_method
|
30
|
+
end
|
31
|
+
|
32
|
+
[:with_foo, :with_foos].each do |method|
|
33
|
+
subject.singleton_class.define_method(method) { }
|
34
|
+
|
35
|
+
expect { subject.lookup_for :foo }.to raise_error LookupBy::Error, /already exists/
|
36
|
+
|
37
|
+
subject.singleton_class.remove_method(method)
|
38
|
+
end
|
27
39
|
end
|
28
40
|
|
29
41
|
it "requires a foreign key" do
|
@@ -33,6 +45,18 @@ describe ::ActiveRecord::Base do
|
|
33
45
|
it "rejects unsaved lookup values" do
|
34
46
|
expect { subject.new.city = City.new(name: "Toronto") }.to raise_error ArgumentError, /must be saved/
|
35
47
|
end
|
48
|
+
|
49
|
+
context "scope: nil" do
|
50
|
+
it { should respond_to(:with_city).with(1).arguments }
|
51
|
+
it { should respond_to(:with_cities).with(2).arguments }
|
52
|
+
end
|
53
|
+
|
54
|
+
context "scope: false" do
|
55
|
+
it { should_not respond_to(:with_postal_code) }
|
56
|
+
it { should_not respond_to(:with_postal_codes) }
|
57
|
+
end
|
58
|
+
|
59
|
+
its(:lookups) { should include(:city) }
|
36
60
|
end
|
37
61
|
end
|
38
62
|
|
@@ -46,7 +70,7 @@ describe LookupBy::Association do
|
|
46
70
|
context "Address.lookup_for :city, strict: false" do
|
47
71
|
it_behaves_like "a lookup for", :city
|
48
72
|
|
49
|
-
it "accepts
|
73
|
+
it "accepts Integers" do
|
50
74
|
subject.city = City.where(city: "New York").first.id
|
51
75
|
subject.city.should eq "New York"
|
52
76
|
end
|
@@ -92,7 +116,7 @@ describe LookupBy::Association do
|
|
92
116
|
|
93
117
|
context "Missing.lookup_for :city" do
|
94
118
|
it "does not raise foreign key error when table hasn't been created" do
|
95
|
-
expect { require "missing"
|
119
|
+
expect { require "missing" }.to_not raise_error
|
96
120
|
end
|
97
121
|
end
|
98
122
|
end
|
@@ -3,24 +3,34 @@ shared_examples "a lookup" do
|
|
3
3
|
it { should respond_to :lookup }
|
4
4
|
it { should respond_to :lookup_by }
|
5
5
|
it { should respond_to :lookup_for }
|
6
|
+
|
6
7
|
its(:is_a_lookup?) { should be_true }
|
7
8
|
|
9
|
+
it "raises with no args" do
|
10
|
+
expect { subject[] }.to raise_error ArgumentError
|
11
|
+
end
|
12
|
+
|
8
13
|
it "returns nil for nil" do
|
9
14
|
subject[nil].should be_nil
|
15
|
+
subject[nil, nil].should == [nil, nil]
|
10
16
|
end
|
11
17
|
|
12
18
|
it "returns nil for empty strings" do
|
13
19
|
subject[""].should be_nil
|
20
|
+
subject["", ""].should == [nil, nil]
|
14
21
|
end
|
15
22
|
|
16
23
|
it "returns itself" do
|
17
|
-
first = subject.first
|
18
|
-
|
24
|
+
if first = subject.first
|
25
|
+
subject[first].should eq first
|
26
|
+
subject[first, first].should == [first, first]
|
27
|
+
end
|
19
28
|
end
|
20
29
|
|
21
30
|
it "rejects other argument types" do
|
22
|
-
[1.00, true, false, Address.new].each do |value|
|
23
|
-
expect { subject[value] }.to
|
31
|
+
[1.00, true, false, Rational(1), Address.new, Array.new, Hash.new].each do |value|
|
32
|
+
expect { subject[value] }.to raise_error TypeError
|
33
|
+
expect { subject[value, value] }.to raise_error TypeError
|
24
34
|
end
|
25
35
|
end
|
26
36
|
|