module Sequel::Model::Associations::InstanceMethods

Instance methods used to implement the associations support.

Public Instance Methods

associations() click to toggle source

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 2226
def associations
  @associations ||= {}
end
freeze() click to toggle source

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.

Calls superclass method
# File lib/sequel/model/associations.rb, line 2233
def freeze
  associations
  super
  associations.freeze
  self
end

Private Instance Methods

_apply_association_options(opts, ds) click to toggle source

Apply the association options such as :order and :limit to the given dataset, returning a modified dataset.

# File lib/sequel/model/associations.rb, line 2243
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?
  ds = instance_exec(ds, &opts[:block]) if opts[:block]
  ds
end
_associated_dataset(opts, dynamic_opts) click to toggle source

Return a dataset for the association after applying any dynamic callback.

# File lib/sequel/model/associations.rb, line 2254
def _associated_dataset(opts, dynamic_opts)
  ds = send(opts.dataset_method)
  if callback = dynamic_opts[:callback]
    ds = callback.call(ds)
  end
  ds
end
_associated_object_loader(opts, dynamic_opts) click to toggle source

A placeholder literalizer that can be used to load the association, or nil to not use one.

# File lib/sequel/model/associations.rb, line 2263
def _associated_object_loader(opts, dynamic_opts)
  if !dynamic_opts[:callback] && (loader = opts.placeholder_loader)
    loader
  end
end
_dataset(opts) click to toggle source

Return an association dataset for the given association reflection

# File lib/sequel/model/associations.rb, line 2270
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].arity == 1
    instance_exec(opts, &opts[:dataset])
  else
    instance_exec(&opts[:dataset])
  end
  _apply_association_options(opts, ds)
end
_join_table_dataset(opts) click to toggle source

Dataset for the join table of the given many to many association reflection

# File lib/sequel/model/associations.rb, line 2281
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
_load_associated_object(opts, dynamic_opts) click to toggle source

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 2288
def _load_associated_object(opts, dynamic_opts)
  _load_associated_object_array(opts, dynamic_opts).first
end
_load_associated_object_array(opts, dynamic_opts) click to toggle source

Load the associated objects for the given association reflection and dynamic options as an array.

# File lib/sequel/model/associations.rb, line 2299
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
_load_associated_object_via_primary_key(opts) click to toggle source

Return the associated single object using a primary key lookup on the associated class.

# File lib/sequel/model/associations.rb, line 2293
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
_load_associated_objects(opts, dynamic_opts=OPTS) click to toggle source

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 2309
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
_refresh_set_values(hash) click to toggle source

Clear the associations cache when refreshing

Calls superclass method
# File lib/sequel/model/associations.rb, line 2324
def _refresh_set_values(hash)
  @associations.clear if @associations
  super
end
_set_associated_object(opts, o) click to toggle source

Set the given object as the associated object for the given *_to_one association reflection

# File lib/sequel/model/associations.rb, line 2548
def _set_associated_object(opts, o)
  a = associations[opts[:name]]
  return if a && a == o && !set_associated_object_if_same?
  run_association_callbacks(opts, :before_set, o)
  remove_reciprocal_object(opts, a) if a
  send(opts._setter_method, o)
  associations[opts[:name]] = o
  add_reciprocal_object(opts, o) if o
  run_association_callbacks(opts, :after_set, o)
  o
end
add_associated_object(opts, o, *args) click to toggle source

Add the given associated object to the given association

# File lib/sequel/model/associations.rb, line 2330
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
  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_reciprocal_object(opts, o) click to toggle source

Add/Set the current object to/as the given object's reciprocal association.

# File lib/sequel/model/associations.rb, line 2345
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
array_uniq!(a) click to toggle source

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 2359
def array_uniq!(a)
  a.uniq!
end
change_column_value(column, value) click to toggle source

If a foreign key column value changes, clear the related cached associations.

Calls superclass method
# File lib/sequel/model/associations.rb, line 2365
def change_column_value(column, value)
  if assocs = model.autoreloading_associations[column]
    assocs.each{|a| associations.delete(a)}
  end
  super
end
ensure_associated_primary_key(opts, o, *args) click to toggle source

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 2375
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
initialize_copy(other) click to toggle source

Duplicate the associations hash when duplicating the object.

Calls superclass method
# File lib/sequel/model/associations.rb, line 2383
def initialize_copy(other)
  super
  @associations = Hash[@associations] if @associations
  self
end
load_associated_objects(opts, dynamic_opts=nil, &block) click to toggle source

Load the associated objects using the dataset, handling callbacks, reciprocals, and caching.

# File lib/sequel/model/associations.rb, line 2418
def load_associated_objects(opts, dynamic_opts=nil, &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
load_association_objects_options(dynamic_opts, &block) click to toggle source

Handle parsing of options when loading associations. For historical reasons, you can pass true/false/nil or a callable argument to associations. That will be going away in Sequel 5, but we'll still support it until then.

# File lib/sequel/model/associations.rb, line 2393
def load_association_objects_options(dynamic_opts, &block)
  dynamic_opts = case dynamic_opts
  when true, false, nil
    Sequel::Deprecation.deprecate("Passing #{dynamic_opts.inspect} an argument to an association loading method", "Pass {:reload=>#{dynamic_opts.inspect}} instead")
    {:reload=>dynamic_opts}
  when Hash
    Hash[dynamic_opts]
  else
    if dynamic_opts.respond_to?(:call)
      Sequel::Deprecation.deprecate("Passing callbable argument #{dynamic_opts.inspect} to an association loading method", "Pass a block to the method to use a callback")
      {:callback=>dynamic_opts}
    else
      Sequel::Deprecation.deprecate("Passing #{dynamic_opts.inspect} an argument to an association loading method", "Pass {:reload=>true} if you would like to reload the association")
      {:reload=>true}
    end
  end

  if block_given?
    dynamic_opts[:callback] = block
  end

  dynamic_opts
end
load_with_primary_key_lookup?(opts, dynamic_opts) click to toggle source

Whether to use a simple primary key lookup on the associated class when loading.

# File lib/sequel/model/associations.rb, line 2444
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
make_add_associated_object(opts, o) click to toggle source

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 2454
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(opts, *args) click to toggle source

Remove all associated objects from the given association

# File lib/sequel/model/associations.rb, line 2470
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
  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_associated_object(opts, o, *args) click to toggle source

Remove the given associated object from the given association

# File lib/sequel/model/associations.rb, line 2479
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? && 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
  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
remove_check_existing_object_from_pk(opts, o, *args) click to toggle source

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 2501
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 = send(opts.dataset_method).first(pkh)
  o
end
remove_reciprocal_object(opts, o) click to toggle source

Remove/unset the current object from/as the given object's reciprocal association.

# File lib/sequel/model/associations.rb, line 2509
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_association_callbacks(reflection, callback_type, object) click to toggle source

Run the callback for the association with the object.

# File lib/sequel/model/associations.rb, line 2521
def run_association_callbacks(reflection, callback_type, object)
  # 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
  raise_error = raise_on_save_failure || !reflection.returns_array?
  stop_on_false = [:before_add, :before_remove, :before_set].include?(callback_type)
  reflection[callback_type].each do |cb|
    res = case cb
    when Symbol
      send(cb, object)
    when Proc
      cb.call(self, object)
    else
      raise Error, "callbacks should either be Procs or Symbols"
    end

    if res == false and stop_on_false
      raise(HookFailed, "Unable to modify association for #{inspect}: one of the #{callback_type} hooks returned false")
    end
  end
rescue HookFailed
  return false unless raise_error
  raise
end
set_associated_object(opts, o) click to toggle source

Set the given object as the associated object for the given many_to_one association reflection

# File lib/sequel/model/associations.rb, line 2568
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
set_associated_object_if_same?() click to toggle source

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 2563
def set_associated_object_if_same?
  @set_associated_object_if_same
end
set_one_through_one_associated_object(opts, o) click to toggle source

Set the given object as the associated object for the given one_through_one association reflection

# File lib/sequel/model/associations.rb, line 2574
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_one_to_one_associated_object(opts, o) click to toggle source

Set the given object as the associated object for the given one_to_one association reflection

# File lib/sequel/model/associations.rb, line 2581
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