Class: NoBrainer::Document::Association::BelongsTo::Metadata

Inherits:
Object
  • Object
show all
Includes:
Core::Metadata, EagerLoader::Generic
Defined in:
lib/no_brainer/document/association/belongs_to.rb

Constant Summary collapse

VALID_OPTIONS =
%i[
  primary_key foreign_key foreign_type class_name foreign_key_store_as
  index validates required uniq unique polymorphic
]

Instance Attribute Summary

Attributes included from Core::Metadata

#options, #owner_model, #target_name

Instance Method Summary collapse

Methods included from EagerLoader::Generic

#eager_load

Methods included from Core::Metadata

#add_callback_for, #association_model, #delegate, #get_model_by_name, #initialize, #new

Instance Method Details

#base_criteria(target_class = nil) ⇒ Object



54
55
56
57
58
# File 'lib/no_brainer/document/association/belongs_to.rb', line 54

def base_criteria(target_class = nil)
  model = target_model(target_class)

  model ? model.without_ordering : nil
end

#cast_attr(key, value, target_class = nil) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/no_brainer/document/association/belongs_to.rb', line 109

def cast_attr(key, value, target_class = nil)
  case value
  when target_model(target_class)
    [foreign_key, value.__send__(primary_key)]
  when nil
    if options[:polymorphic]
      [[foreign_type, foreign_key], nil]
    else
      [foreign_key, nil]
    end
  else
    raise NoBrainer::Error::InvalidType.new(
      model: owner_model, attr_name: key, type: target_model, value: value
    )
  end
end

#eager_load_owner_keyObject



126
# File 'lib/no_brainer/document/association/belongs_to.rb', line 126

def eager_load_owner_key;  foreign_key; end

#eager_load_owner_typeObject



127
# File 'lib/no_brainer/document/association/belongs_to.rb', line 127

def eager_load_owner_type; foreign_type; end

#eager_load_target_keyObject



128
# File 'lib/no_brainer/document/association/belongs_to.rb', line 128

def eager_load_target_key; primary_key; end

#foreign_keyObject



13
14
15
# File 'lib/no_brainer/document/association/belongs_to.rb', line 13

def foreign_key
  options[:foreign_key].try(:to_sym) || :"#{target_name}_#{primary_key}"
end

#foreign_typeObject



17
18
19
20
21
# File 'lib/no_brainer/document/association/belongs_to.rb', line 17

def foreign_type
  return nil unless options[:polymorphic]

  options[:foreign_type].try(:to_sym) || (:"#{target_name}_type")
end

#hookObject



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
# File 'lib/no_brainer/document/association/belongs_to.rb', line 60

def hook
  super

  # TODO set the type of the foreign key to be the same as the target's primary key
  if owner_model..values.any? { |assoc|
      assoc.is_a?(self.class) && assoc != self && assoc.foreign_key == foreign_key }
    raise "Cannot declare `#{target_name}' in #{owner_model}: the foreign_key `#{foreign_key}' is already used"
  end

  if options[:polymorphic] && options[:class_name]
    raise 'You cannot set class_name on a polymorphic belongs_to'
  end

  if options[:polymorphic]
    if options[:uniq] || options[:unique]
      owner_model.field(foreign_type, uniq: { scope: foreign_key })
      owner_model.index([foreign_type, foreign_key])
    else
      owner_model.field(foreign_type)
    end
  end

  owner_model.field(foreign_key, store_as: options[:foreign_key_store_as], index: options[:index])

  unless options[:validates] == false
    owner_model.validates(target_name, options[:validates]) if options[:validates]

    uniq = options[:uniq] || options[:unique]
    if uniq
      owner_model.validates(foreign_key, :uniqueness => uniq)
    end

    if options[:required]
      owner_model.validates(target_name, :presence => options[:required])
    else
      # Always validate the foreign_key if not nil.
      owner_model.validates_each(foreign_key) do |doc, attr, value|
        if !value.nil? && value != doc.pk_value && doc.read_attribute_for_validation(target_name).nil?
          doc.errors.add(attr, :invalid_foreign_key, :target_model => target_model, :primary_key => primary_key)
        end
      end
    end
  end

  delegate("#{foreign_key}=", :assign_foreign_key, :call_super => true)
  delegate("#{target_name}_changed?", "#{foreign_key}_changed?", :to => :self)
  add_callback_for(:after_validation)
end

#primary_keyObject



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/no_brainer/document/association/belongs_to.rb', line 23

def primary_key
  # We default the primary_key to `:id' and not `target_model.pk_name',
  # because we don't want to require the target_model to be already loaded.
  # (We want the ability to load models in any order).
  # Using target_model.pk_name and allowing lazy loading of models is
  # difficult due to the inexistant API to remove validations if the
  # foreign_key name was to be changed as the pk_name gets renamed.
  return options[:primary_key].to_sym if options[:primary_key]

  NoBrainer::Document::PrimaryKey::DEFAULT_PK_NAME.tap do |default_pk_name|
    # We'll try to give a warning when we see a different target pk name (best effort).
    real_pk_name = target_model.pk_name rescue nil
    if real_pk_name && real_pk_name != default_pk_name
      raise "Please specify the primary_key name on the following belongs_to association as such:\n" +
            "  belongs_to :#{target_name}, :primary_key => :#{real_pk_name}"
    end
  end
end

#target_model(target_class = nil) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
# File 'lib/no_brainer/document/association/belongs_to.rb', line 42

def target_model(target_class = nil)
  return if options[:polymorphic] && target_class.nil?

  model_name = if options[:polymorphic]
                 target_class
               else
                 options[:class_name] || target_name.to_s.camelize
               end

  get_model_by_name(model_name)
end