module Sequel::Model::Associations::InstanceMethods
Instance methods used to implement the associations support.
Public Instance Methods
The currently cached associations. A hash with the keys being the association name symbols and the values being the associated object or nil (many_to_one), or the array of associated objects (*_to_many).
# File lib/sequel/model/associations.rb, line 2291 def associations @associations ||= {} end
Freeze the associations cache when freezing the object. Note that retrieving associations after freezing will still work in most cases, but the associations will not be cached in the association cache.
# File lib/sequel/model/associations.rb, line 2298 def freeze associations super associations.freeze self end
Private Instance Methods
Apply the association options such as :order and :limit to the given dataset, returning a modified dataset.
# File lib/sequel/model/associations.rb, line 2308 def _apply_association_options(opts, ds) unless ds.kind_of?(AssociationDatasetMethods) ds = opts.apply_dataset_changes(ds) end ds = ds.clone(:model_object => self) ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] && opts.eager_graph_lazy_dataset? # block method is private ds = send(opts[:block_method], ds) if opts[:block_method] ds end
Return a dataset for the association after applying any dynamic callback.
# File lib/sequel/model/associations.rb, line 2320 def _associated_dataset(opts, dynamic_opts) ds = public_send(opts.dataset_method) if callback = dynamic_opts[:callback] ds = callback.call(ds) end ds end
A placeholder literalizer that can be used to load the association, or nil to not use one.
# File lib/sequel/model/associations.rb, line 2329 def _associated_object_loader(opts, dynamic_opts) if !dynamic_opts[:callback] && (loader = opts.placeholder_loader) loader end end
Return an association dataset for the given association reflection
# File lib/sequel/model/associations.rb, line 2336 def _dataset(opts) raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk ds = if opts[:dataset_opt_arity] == 1 # dataset_opt_method is private send(opts[:dataset_opt_method], opts) else send(opts[:dataset_opt_method]) end _apply_association_options(opts, ds) end
Dataset for the join table of the given many to many association reflection
# File lib/sequel/model/associations.rb, line 2348 def _join_table_dataset(opts) ds = model.db.from(opts.join_table_source) opts[:join_table_block] ? opts[:join_table_block].call(ds) : ds end
Return the associated single object for the given association reflection and dynamic options (or nil if no associated object).
# File lib/sequel/model/associations.rb, line 2355 def _load_associated_object(opts, dynamic_opts) _load_associated_object_array(opts, dynamic_opts).first end
Load the associated objects for the given association reflection and dynamic options as an array.
# File lib/sequel/model/associations.rb, line 2366 def _load_associated_object_array(opts, dynamic_opts) if loader = _associated_object_loader(opts, dynamic_opts) loader.all(*opts.predicate_key_values(self)) else _associated_dataset(opts, dynamic_opts).all end end
Return the associated single object using a primary key lookup on the associated class.
# File lib/sequel/model/associations.rb, line 2360 def _load_associated_object_via_primary_key(opts) opts.associated_class.send(:primary_key_lookup, ((fk = opts[:key]).is_a?(Array) ? fk.map{|c| get_column_value(c)} : get_column_value(fk))) end
Return the associated objects from the dataset, without association callbacks, reciprocals, and caching. Still apply the dynamic callback if present.
# File lib/sequel/model/associations.rb, line 2376 def _load_associated_objects(opts, dynamic_opts=OPTS) if opts.can_have_associated_objects?(self) if opts.returns_array? _load_associated_object_array(opts, dynamic_opts) elsif load_with_primary_key_lookup?(opts, dynamic_opts) _load_associated_object_via_primary_key(opts) else _load_associated_object(opts, dynamic_opts) end elsif opts.returns_array? [] end end
Clear the associations cache when refreshing
# File lib/sequel/model/associations.rb, line 2391 def _refresh_set_values(hash) @associations.clear if @associations super end
Set the given object as the associated object for the given *_to_one association reflection
# File lib/sequel/model/associations.rb, line 2624 def _set_associated_object(opts, o) a = associations[opts[:name]] reciprocal = opts.reciprocal if set_associated_object_if_same? if reciprocal remove_reciprocal = a && (a != o || a.associations[reciprocal] != self) add_reciprocal = o && o.associations[reciprocal] != self end else return if a && a == o if reciprocal remove_reciprocal = a add_reciprocal = o end end run_association_callbacks(opts, :before_set, o) remove_reciprocal_object(opts, a) if remove_reciprocal # Allow calling private _setter method send(opts[:_setter_method], o) associations[opts[:name]] = o add_reciprocal_object(opts, o) if add_reciprocal run_association_callbacks(opts, :after_set, o) o end
Add the given associated object to the given association
# File lib/sequel/model/associations.rb, line 2397 def add_associated_object(opts, o, *args) o = make_add_associated_object(opts, o) raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk ensure_associated_primary_key(opts, o, *args) return if run_association_callbacks(opts, :before_add, o) == false # Allow calling private _add method return if !send(opts[:_add_method], o, *args) && opts.handle_silent_modification_failure? if array = associations[opts[:name]] and !array.include?(o) array.push(o) end add_reciprocal_object(opts, o) run_association_callbacks(opts, :after_add, o) o end
Add/Set the current object to/as the given object's reciprocal association.
# File lib/sequel/model/associations.rb, line 2413 def add_reciprocal_object(opts, o) return if o.frozen? return unless reciprocal = opts.reciprocal if opts.reciprocal_array? if array = o.associations[reciprocal] and !array.include?(self) array.push(self) end else o.associations[reciprocal] = self end end
Call uniq! on the given array. This is used by the :uniq option, and is an actual method for memory reasons.
# File lib/sequel/model/associations.rb, line 2427 def array_uniq!(a) a.uniq! end
If a foreign key column value changes, clear the related cached associations.
# File lib/sequel/model/associations.rb, line 2433 def change_column_value(column, value) if assocs = model.autoreloading_associations[column] vals = @values if new? # Do deeper checking for new objects, so that associations are # not deleted when values do not change. This code is run at # a higher level for existing objects. if value == (c = vals[column]) && value.class == c.class # If the value is the same, there is no reason to delete # the related associations, so exit early in that case. return super end only_delete_nil = c.nil? elsif vals[column].nil? only_delete_nil = true end if only_delete_nil # If the current foreign key value is nil, but the association # is already present in the cache, it was probably added to the # cache for a reason, and we do not want to delete it in that case. # However, we still want to delete associations with nil values # to remove the cached false negative. assocs.each{|a| associations.delete(a) if associations[a].nil?} else assocs.each{|a| associations.delete(a)} end end super end
Save the associated object if the associated object needs a primary key and the associated object is new and does not have one. Raise an error if the object still does not have a primary key
# File lib/sequel/model/associations.rb, line 2468 def ensure_associated_primary_key(opts, o, *args) if opts.need_associated_primary_key? o.save(:validate=>opts[:validate]) if o.new? raise(Sequel::Error, "associated object #{o.inspect} does not have a primary key") unless o.pk end end
Duplicate the associations hash when duplicating the object.
# File lib/sequel/model/associations.rb, line 2476 def initialize_copy(other) super @associations = Hash[@associations] if @associations self end
Load the associated objects using the dataset, handling callbacks, reciprocals, and caching.
# File lib/sequel/model/associations.rb, line 2493 def load_associated_objects(opts, dynamic_opts, &block) dynamic_opts = load_association_objects_options(dynamic_opts, &block) name = opts[:name] if associations.include?(name) && !dynamic_opts[:callback] && !dynamic_opts[:reload] associations[name] else objs = _load_associated_objects(opts, dynamic_opts) if opts.set_reciprocal_to_self? if opts.returns_array? objs.each{|o| add_reciprocal_object(opts, o)} elsif objs add_reciprocal_object(opts, objs) end end # If the current object is frozen, you can't update the associations # cache. This can cause issues for after_load procs that expect # the objects to be already cached in the associations, but # unfortunately that case cannot be handled. associations[name] = objs unless frozen? run_association_callbacks(opts, :after_load, objs) frozen? ? objs : associations[name] end end
If a block is given, assign it as the :callback option in the hash, and return the hash.
# File lib/sequel/model/associations.rb, line 2483 def load_association_objects_options(dynamic_opts, &block) if block dynamic_opts = Hash[dynamic_opts] dynamic_opts[:callback] = block end dynamic_opts end
Whether to use a simple primary key lookup on the associated class when loading.
# File lib/sequel/model/associations.rb, line 2519 def load_with_primary_key_lookup?(opts, dynamic_opts) opts[:type] == :many_to_one && !dynamic_opts[:callback] && opts.send(:cached_fetch, :many_to_one_pk_lookup){opts.primary_key == opts.associated_class.primary_key} end
Convert the input of the add_* association method into an associated object. For hashes, this creates a new object using the hash. For integers, strings, and arrays, assume the value specifies a primary key, and lookup an existing object with that primary key. Otherwise, if the object is not already an instance of the class, raise an exception.
# File lib/sequel/model/associations.rb, line 2529 def make_add_associated_object(opts, o) klass = opts.associated_class case o when Hash klass.new(o) when Integer, String, Array klass.with_pk!(o) when klass o else raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}") end end
Remove all associated objects from the given association
# File lib/sequel/model/associations.rb, line 2545 def remove_all_associated_objects(opts, *args) raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk # Allow calling private _remove_all method send(opts[:_remove_all_method], *args) ret = associations[opts[:name]].each{|o| remove_reciprocal_object(opts, o)} if associations.include?(opts[:name]) associations[opts[:name]] = [] ret end
Remove the given associated object from the given association
# File lib/sequel/model/associations.rb, line 2555 def remove_associated_object(opts, o, *args) klass = opts.associated_class if o.is_a?(Integer) || o.is_a?(String) || o.is_a?(Array) o = remove_check_existing_object_from_pk(opts, o, *args) elsif !o.is_a?(klass) raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}") elsif opts.remove_should_check_existing? && public_send(opts.dataset_method).where(o.pk_hash).empty? raise(Sequel::Error, "associated object #{o.inspect} is not currently associated to #{inspect}") end raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk raise(Sequel::Error, "associated object #{o.inspect} does not have a primary key") if opts.need_associated_primary_key? && !o.pk return if run_association_callbacks(opts, :before_remove, o) == false # Allow calling private _remove method return if !send(opts[:_remove_method], o, *args) && opts.handle_silent_modification_failure? associations[opts[:name]].delete_if{|x| o === x} if associations.include?(opts[:name]) remove_reciprocal_object(opts, o) run_association_callbacks(opts, :after_remove, o) o end
Check that the object from the associated table specified by the primary key is currently associated to the receiver. If it is associated, return the object, otherwise raise an error.
# File lib/sequel/model/associations.rb, line 2578 def remove_check_existing_object_from_pk(opts, o, *args) key = o pkh = opts.associated_class.qualified_primary_key_hash(key) raise(Sequel::Error, "no object with key(s) #{key.inspect} is currently associated to #{inspect}") unless o = public_send(opts.dataset_method).first(pkh) o end
Remove/unset the current object from/as the given object's reciprocal association.
# File lib/sequel/model/associations.rb, line 2586 def remove_reciprocal_object(opts, o) return unless reciprocal = opts.reciprocal if opts.reciprocal_array? if array = o.associations[reciprocal] array.delete_if{|x| self === x} end else o.associations[reciprocal] = nil end end
Run the callback for the association with the object.
# File lib/sequel/model/associations.rb, line 2598 def run_association_callbacks(reflection, callback_type, object) return unless cbs = reflection[callback_type] begin cbs.each do |cb| case cb when Symbol # Allow calling private methods in association callbacks send(cb, object) when Proc cb.call(self, object) else raise Error, "callbacks should either be Procs or Symbols" end end rescue HookFailed # The reason we automatically set raise_error for singular associations is that # assignment in ruby always returns the argument instead of the result of the # method, so we can't return nil to signal that the association callback prevented # the modification return false unless raise_on_save_failure || !reflection.returns_array? raise end end
Set the given object as the associated object for the given many_to_one association reflection
# File lib/sequel/model/associations.rb, line 2657 def set_associated_object(opts, o) raise(Error, "associated object #{o.inspect} does not have a primary key") if o && !o.pk _set_associated_object(opts, o) end
Whether run the associated object setter code if passed the same object as the one already cached in the association. Usually not set (so nil), can be set on a per-object basis if necessary.
# File lib/sequel/model/associations.rb, line 2652 def set_associated_object_if_same? @set_associated_object_if_same end
Set the given object as the associated object for the given one_through_one association reflection
# File lib/sequel/model/associations.rb, line 2663 def set_one_through_one_associated_object(opts, o) raise(Error, "object #{inspect} does not have a primary key") unless pk raise(Error, "associated object #{o.inspect} does not have a primary key") if o && !o.pk _set_associated_object(opts, o) end
Set the given object as the associated object for the given one_to_one association reflection
# File lib/sequel/model/associations.rb, line 2670 def set_one_to_one_associated_object(opts, o) raise(Error, "object #{inspect} does not have a primary key") unless pk _set_associated_object(opts, o) end