17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
# File 'lib/lookup_by/association.rb', line 17
def lookup_for field, options = {}
begin
return unless table_exists?
rescue => error
Rails.logger.error "lookup_by caught #{error.class.name} when connecting - skipping initialization (#{error.inspect})"
return
end
options.symbolize_keys!
options.assert_valid_keys(:class_name, :foreign_key, :symbolize, :strict, :scope, :inverse_scope)
field = field.to_sym
%W(#{field} raw_#{field} #{field}= #{field}_before_type_cast #{field}?).map(&:to_sym).each do |method|
raise Error, "method `#{method}` already exists on #{self.inspect}" if instance_methods.include? method
end
singleton_class.class_eval do
attr_reader :lookups
end
@lookups ||= []
@lookups << field
scope_name =
if options[:scope] == false
nil
elsif !options.key?(:scope) || options[:scope] == true
"with_#{field}"
else
options[:scope].to_s
end
inverse_scope_name =
if options[:inverse_scope] == false
nil
elsif !options.key?(:inverse_scope) || options[:inverse_scope] == true
"without_#{field}"
else
options[:inverse_scope].to_s
end
if scope_name && respond_to?(scope_name)
raise Error, "#{scope_name} already exists on #{self}."
end
if inverse_scope_name && respond_to?(inverse_scope_name)
raise Error, "#{inverse_scope_name} already exists on #{self}."
end
class_name = options[:class_name] || field
class_name = class_name.to_s.camelize
begin
klass = class_name.constantize
rescue NameError
raise Error, "uninitialized constant #{class_name}, call lookup_for with `class_name` option if it doesn't match the foreign key"
end
raise Error, "class #{class_name} does not use lookup_by" unless klass.respond_to?(:lookup)
foreign_key = options[:foreign_key] || "#{field}_id"
foreign_key = foreign_key.to_sym
Rails.logger.error "foreign key `#{foreign_key}` is required on #{self}" unless attribute_names.include?(foreign_key.to_s)
strict = options[:strict]
strict = true if strict.nil?
class_eval <<-SCOPES, __FILE__, __LINE__.next if scope_name
scope :#{scope_name}, ->(*names) { where(#{foreign_key}: #{class_name}[*names]) }
SCOPES
class_eval <<-SCOPES, __FILE__, __LINE__.next if inverse_scope_name
scope :#{inverse_scope_name}, ->(*names) {
if names.length != 1
where('#{foreign_key} NOT IN (?)', #{class_name}[*names])
else
where('#{foreign_key} <> ?', #{class_name}[*names])
end
}
SCOPES
cast = options[:symbolize] ? ".to_sym" : ""
lookup_field = klass.lookup.field
lookup_object = "#{class_name}[#{foreign_key}]"
class_eval <<-METHODS, __FILE__, __LINE__.next
def raw_#{field}
#{lookup_object}
end
def #{field}
value = #{lookup_object}
value ? value.#{lookup_field}#{cast} : nil
end
def #{field}?(name)
raise ArgumentError, "Invalid #{field} \#{name.inspect}" unless object = #{class_name}[name]
#{foreign_key} == object.id
end
def #{field}_before_type_cast
#{lookup_object}.#{lookup_field}_before_type_cast
end
def #{field}=(arg)
result = case arg
when nil
nil
when String, Integer, IPAddr
#{class_name}[arg]
when Symbol
#{%Q(raise ArgumentError, "#{foreign_key}=(Symbol): use `lookup_for :column, symbolize: true` to allow symbols") unless options[:symbolize]}
#{class_name}[arg]
when #{class_name}
raise ArgumentError, "self.#{foreign_key}=(#{class_name}): must be saved" unless arg.persisted?
arg
else
raise TypeError, "#{foreign_key}=(arg): arg must be a String, Symbol, Integer, IPAddr, nil, or #{class_name}"
end
#{ %Q(raise LookupBy::Error, "\#{arg.inspect} is not in the <#{class_name}> lookup cache" if arg.present? && result.nil?) if strict }
if result.blank?
self.#{foreign_key} = nil
elsif result.persisted?
self.#{foreign_key} = result.id
elsif lookup_errors = result.errors[:#{lookup_field}]
lookup_errors.each do |msg|
errors.add :#{field}, msg
end
end
end
METHODS
end
|