lookup_by 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|