class ActiveFedora::Associations::Association
HasManyAssociation
AssociationCollection
BelongsToAssociation
Association
This is the root class of all associations:
def association_scope
by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
scope method is called. This is because at that point the call may be surrounded
Note that the association_scope is merged into the target_scope only when the
The scope for this association.
def association_scope @association_scope ||= AssociationScope.scope(self) if klass end
def build_record(attributes)
def build_record(attributes) reflection.build_association(attributes) do |record| initialize_attributes(record) end end
def creation_attributes
def creation_attributes attributes = {} if (reflection.has_one? || reflection.collection?) && !options[:through] attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key] if reflection.options[:as] attributes[reflection.type] = owner.class.base_class.name end end attributes end
def find_target?
def find_target? !loaded? && (!owner.new_record? || foreign_key_present?) && klass end
def foreign_key_present?
be present in order to load target.
without a key). If the owner is a new record then foreign_key must
the target if the owner is currently a new record (and therefore
references the target. This is used to determine whether we can load
Returns true if there is a foreign key present on the owner which
def foreign_key_present? false end
def initialize(owner, reflection)
def initialize(owner, reflection) reflection.check_validity! @owner = owner @reflection = reflection reset reset_scope end
def initialize_attributes(record, except_from_scope_attributes = nil) #:nodoc:
def initialize_attributes(record, except_from_scope_attributes = nil) #:nodoc: except_from_scope_attributes ||= {} skip_assign = [reflection.foreign_key].compact assigned_keys = record.changed assigned_keys += except_from_scope_attributes.keys.map(&:to_s) attributes = create_scope.except(*(assigned_keys - skip_assign)) record.assign_attributes(attributes) set_inverse_instance(record) end
def inverse_reflection_for(_record)
The record parameter is necessary to support polymorphic inverses as we must check for
Can be redefined by subclasses, notably polymorphic belongs_to
def inverse_reflection_for(_record) reflection.inverse_of end
def invertible_for?(record)
Returns true if inverse association on the given record needs to be set.
def invertible_for?(record) inverse_reflection_for(record) end
def load_target
ActiveFedora::ObjectNotFoundError is rescued within the method, and it is
+load_target+ unconditionally to get the \target.
If the \target is already \loaded it is just returned. Thus, you can call
which is expected to be provided by descendants.
This method is abstract in the sense that it relies on +find_target+,
Loads the \target if needed and returns it.
def load_target @target = find_target if (@stale_state && stale_target?) || find_target? loaded! unless loaded? target rescue ActiveFedora::ObjectNotFoundError reset end
def loaded!
def loaded! @loaded = true @stale_state = stale_state @inversed = false end
def loaded?
def loaded? @loaded end
def raise_on_type_mismatch!(record)
the kind of the class of the associated objects. Meant to be used as
Raises ActiveFedora::AssociationTypeMismatch unless +record+ is of
def raise_on_type_mismatch!(record) unless record.is_a?(reflection.klass) fresh_class = reflection.class_name.safe_constantize unless fresh_class && record.is_a?(fresh_class) message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})" raise ActiveFedora::AssociationTypeMismatch, message end end type_validator.validate!(self, record) end
def reload
def reload reset reset_scope load_target self unless @target.nil? end
def reset
def reset @loaded = false @target = nil @stale_state = nil @inversed = false end
def reset_scope
def reset_scope @association_scope = nil end
def scope
def scope target_scope.merge(association_scope) end
def set_inverse_instance(record)
def set_inverse_instance(record) return unless record && invertible_for?(record) inverse = record.association(inverse_reflection_for(record).name.to_sym) if inverse.is_a? ActiveFedora::Associations::HasAndBelongsToManyAssociation inverse.target << owner else inverse.target = owner end inverse.inversed = true end
def set_owner_attributes(record)
def set_owner_attributes(record) creation_attributes.each { |key, value| record[key] = value } end
def stale_state
the target is stale.
so that when state_state is different from the value stored on the last find_target,
This should be implemented to return the values of the relevant key(s) on the owner,
def stale_state end
def stale_target?
state_state method if relevant.
on the owner will reload the target. It's up to subclasses to implement the
relevant foreign_key(s) refers to. If stale, the association accessor method
The target is stale if the target no longer points to the record(s) that the
def stale_target? !inversed && loaded? && @stale_state != stale_state end
def target=(target)
def target=(target) @target = target loaded! end
def target_scope
Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
def target_scope klass.all end
def type_validator
def type_validator options[:type_validator] || NullValidator end