Module: LookupBy::Association::MacroMethods

Defined in:
lib/lookup_by/association.rb

Instance Method Summary collapse

Instance Method Details

#lookup_for(field, options = {}) ⇒ Object

Raises:



16
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
# File 'lib/lookup_by/association.rb', line 16

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)

  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 = "with_#{field}" unless options[:scope] == false

  if scope_name
    single_scope = scope_name
    plural_scope = scope_name.pluralize

    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)
    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)
  end

  class_name = options[:class_name] || field
  class_name = class_name.to_s.camelize

  foreign_key = options[:foreign_key] || "#{field}_id"
  foreign_key = foreign_key.to_sym

  raise 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},           ->(name)   { where(#{foreign_key}: #{class_name}[name]) }
    scope :#{scope_name.pluralize}, ->(*names) { where(#{foreign_key}: #{class_name}[*names]) }
  SCOPES

  cast = options[:symbolize] ? ".to_sym" : ""

  lookup_field  = class_name.constantize.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)
      value = case arg
      when nil
        nil
      when String, Integer, IPAddr
        #{class_name}[arg].try(:id)
      when Symbol
        #{%Q(raise ArgumentError, "#{foreign_key}=(Symbol): use `lookup_for :column, symbolize: true` to allow symbols") unless options[:symbolize]}
        #{class_name}[arg].try(:id)
      when #{class_name}
        raise ArgumentError, "self.#{foreign_key}=(#{class_name}): must be saved" unless arg.id
        arg.id
      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? && value.nil?) if strict}

      self.#{foreign_key} = value
    end
  METHODS
end