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 2291 def associations 2292 @associations ||= {} 2293 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 2298 def freeze 2299 associations 2300 super 2301 associations.freeze 2302 self 2303 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 2308 def _apply_association_options(opts, ds) 2309 unless ds.kind_of?(AssociationDatasetMethods) 2310 ds = opts.apply_dataset_changes(ds) 2311 end 2312 ds = ds.clone(:model_object => self) 2313 ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] && opts.eager_graph_lazy_dataset? 2314 # block method is private 2315 ds = send(opts[:block_method], ds) if opts[:block_method] 2316 ds 2317 end
Return a dataset for the association after applying any dynamic callback.
# File lib/sequel/model/associations.rb 2320 def _associated_dataset(opts, dynamic_opts) 2321 ds = public_send(opts.dataset_method) 2322 if callback = dynamic_opts[:callback] 2323 ds = callback.call(ds) 2324 end 2325 ds 2326 end
A placeholder literalizer that can be used to load the association, or nil to not use one.
# File lib/sequel/model/associations.rb 2329 def _associated_object_loader(opts, dynamic_opts) 2330 if !dynamic_opts[:callback] && (loader = opts.placeholder_loader) 2331 loader 2332 end 2333 end
Return an association dataset for the given association reflection
# File lib/sequel/model/associations.rb 2336 def _dataset(opts) 2337 raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk 2338 ds = if opts[:dataset_opt_arity] == 1 2339 # dataset_opt_method is private 2340 send(opts[:dataset_opt_method], opts) 2341 else 2342 send(opts[:dataset_opt_method]) 2343 end 2344 _apply_association_options(opts, ds) 2345 end
Dataset
for the join table of the given many to many association reflection
# File lib/sequel/model/associations.rb 2348 def _join_table_dataset(opts) 2349 ds = model.db.from(opts.join_table_source) 2350 opts[:join_table_block] ? opts[:join_table_block].call(ds) : ds 2351 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 2355 def _load_associated_object(opts, dynamic_opts) 2356 _load_associated_object_array(opts, dynamic_opts).first 2357 end
Load the associated objects for the given association reflection and dynamic options as an array.
# File lib/sequel/model/associations.rb 2366 def _load_associated_object_array(opts, dynamic_opts) 2367 if loader = _associated_object_loader(opts, dynamic_opts) 2368 loader.all(*opts.predicate_key_values(self)) 2369 else 2370 _associated_dataset(opts, dynamic_opts).all 2371 end 2372 end
Return the associated single object using a primary key lookup on the associated class.
# File lib/sequel/model/associations.rb 2360 def _load_associated_object_via_primary_key(opts) 2361 opts.associated_class.send(:primary_key_lookup, ((fk = opts[:key]).is_a?(Array) ? fk.map{|c| get_column_value(c)} : get_column_value(fk))) 2362 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 2376 def _load_associated_objects(opts, dynamic_opts=OPTS) 2377 if opts.can_have_associated_objects?(self) 2378 if opts.returns_array? 2379 _load_associated_object_array(opts, dynamic_opts) 2380 elsif load_with_primary_key_lookup?(opts, dynamic_opts) 2381 _load_associated_object_via_primary_key(opts) 2382 else 2383 _load_associated_object(opts, dynamic_opts) 2384 end 2385 elsif opts.returns_array? 2386 [] 2387 end 2388 end
Clear the associations cache when refreshing
# File lib/sequel/model/associations.rb 2391 def _refresh_set_values(hash) 2392 @associations.clear if @associations 2393 super 2394 end
Set the given object as the associated object for the given *_to_one association reflection
# File lib/sequel/model/associations.rb 2624 def _set_associated_object(opts, o) 2625 a = associations[opts[:name]] 2626 reciprocal = opts.reciprocal 2627 if set_associated_object_if_same? 2628 if reciprocal 2629 remove_reciprocal = a && (a != o || a.associations[reciprocal] != self) 2630 add_reciprocal = o && o.associations[reciprocal] != self 2631 end 2632 else 2633 return if a && a == o 2634 if reciprocal 2635 remove_reciprocal = a 2636 add_reciprocal = o 2637 end 2638 end 2639 run_association_callbacks(opts, :before_set, o) 2640 remove_reciprocal_object(opts, a) if remove_reciprocal 2641 # Allow calling private _setter method 2642 send(opts[:_setter_method], o) 2643 associations[opts[:name]] = o 2644 add_reciprocal_object(opts, o) if add_reciprocal 2645 run_association_callbacks(opts, :after_set, o) 2646 o 2647 end
Add the given associated object to the given association
# File lib/sequel/model/associations.rb 2397 def add_associated_object(opts, o, *args) 2398 o = make_add_associated_object(opts, o) 2399 raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk 2400 ensure_associated_primary_key(opts, o, *args) 2401 return if run_association_callbacks(opts, :before_add, o) == false 2402 # Allow calling private _add method 2403 return if !send(opts[:_add_method], o, *args) && opts.handle_silent_modification_failure? 2404 if array = associations[opts[:name]] and !array.include?(o) 2405 array.push(o) 2406 end 2407 add_reciprocal_object(opts, o) 2408 run_association_callbacks(opts, :after_add, o) 2409 o 2410 end
Add/Set the current object to/as the given object's reciprocal association.
# File lib/sequel/model/associations.rb 2413 def add_reciprocal_object(opts, o) 2414 return if o.frozen? 2415 return unless reciprocal = opts.reciprocal 2416 if opts.reciprocal_array? 2417 if array = o.associations[reciprocal] and !array.include?(self) 2418 array.push(self) 2419 end 2420 else 2421 o.associations[reciprocal] = self 2422 end 2423 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 2427 def array_uniq!(a) 2428 a.uniq! 2429 end
If a foreign key column value changes, clear the related cached associations.
# File lib/sequel/model/associations.rb 2433 def change_column_value(column, value) 2434 if assocs = model.autoreloading_associations[column] 2435 vals = @values 2436 if new? 2437 # Do deeper checking for new objects, so that associations are 2438 # not deleted when values do not change. This code is run at 2439 # a higher level for existing objects. 2440 if value == (c = vals[column]) && value.class == c.class 2441 # If the value is the same, there is no reason to delete 2442 # the related associations, so exit early in that case. 2443 return super 2444 end 2445 2446 only_delete_nil = c.nil? 2447 elsif vals[column].nil? 2448 only_delete_nil = true 2449 end 2450 2451 if only_delete_nil 2452 # If the current foreign key value is nil, but the association 2453 # is already present in the cache, it was probably added to the 2454 # cache for a reason, and we do not want to delete it in that case. 2455 # However, we still want to delete associations with nil values 2456 # to remove the cached false negative. 2457 assocs.each{|a| associations.delete(a) if associations[a].nil?} 2458 else 2459 assocs.each{|a| associations.delete(a)} 2460 end 2461 end 2462 super 2463 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 2468 def ensure_associated_primary_key(opts, o, *args) 2469 if opts.need_associated_primary_key? 2470 o.save(:validate=>opts[:validate]) if o.new? 2471 raise(Sequel::Error, "associated object #{o.inspect} does not have a primary key") unless o.pk 2472 end 2473 end
Duplicate the associations hash when duplicating the object.
# File lib/sequel/model/associations.rb 2476 def initialize_copy(other) 2477 super 2478 @associations = Hash[@associations] if @associations 2479 self 2480 end
Load the associated objects using the dataset, handling callbacks, reciprocals, and caching.
# File lib/sequel/model/associations.rb 2493 def load_associated_objects(opts, dynamic_opts, &block) 2494 dynamic_opts = load_association_objects_options(dynamic_opts, &block) 2495 name = opts[:name] 2496 if associations.include?(name) && !dynamic_opts[:callback] && !dynamic_opts[:reload] 2497 associations[name] 2498 else 2499 objs = _load_associated_objects(opts, dynamic_opts) 2500 if opts.set_reciprocal_to_self? 2501 if opts.returns_array? 2502 objs.each{|o| add_reciprocal_object(opts, o)} 2503 elsif objs 2504 add_reciprocal_object(opts, objs) 2505 end 2506 end 2507 2508 # If the current object is frozen, you can't update the associations 2509 # cache. This can cause issues for after_load procs that expect 2510 # the objects to be already cached in the associations, but 2511 # unfortunately that case cannot be handled. 2512 associations[name] = objs unless frozen? 2513 run_association_callbacks(opts, :after_load, objs) 2514 frozen? ? objs : associations[name] 2515 end 2516 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 2483 def load_association_objects_options(dynamic_opts, &block) 2484 if block 2485 dynamic_opts = Hash[dynamic_opts] 2486 dynamic_opts[:callback] = block 2487 end 2488 2489 dynamic_opts 2490 end
Whether to use a simple primary key lookup on the associated class when loading.
# File lib/sequel/model/associations.rb 2519 def load_with_primary_key_lookup?(opts, dynamic_opts) 2520 opts[:type] == :many_to_one && 2521 !dynamic_opts[:callback] && 2522 opts.send(:cached_fetch, :many_to_one_pk_lookup){opts.primary_key == opts.associated_class.primary_key} 2523 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 2529 def make_add_associated_object(opts, o) 2530 klass = opts.associated_class 2531 2532 case o 2533 when Hash 2534 klass.new(o) 2535 when Integer, String, Array 2536 klass.with_pk!(o) 2537 when klass 2538 o 2539 else 2540 raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}") 2541 end 2542 end
Remove all associated objects from the given association
# File lib/sequel/model/associations.rb 2545 def remove_all_associated_objects(opts, *args) 2546 raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk 2547 # Allow calling private _remove_all method 2548 send(opts[:_remove_all_method], *args) 2549 ret = associations[opts[:name]].each{|o| remove_reciprocal_object(opts, o)} if associations.include?(opts[:name]) 2550 associations[opts[:name]] = [] 2551 ret 2552 end
Remove the given associated object from the given association
# File lib/sequel/model/associations.rb 2555 def remove_associated_object(opts, o, *args) 2556 klass = opts.associated_class 2557 if o.is_a?(Integer) || o.is_a?(String) || o.is_a?(Array) 2558 o = remove_check_existing_object_from_pk(opts, o, *args) 2559 elsif !o.is_a?(klass) 2560 raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}") 2561 elsif opts.remove_should_check_existing? && public_send(opts.dataset_method).where(o.pk_hash).empty? 2562 raise(Sequel::Error, "associated object #{o.inspect} is not currently associated to #{inspect}") 2563 end 2564 raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk 2565 raise(Sequel::Error, "associated object #{o.inspect} does not have a primary key") if opts.need_associated_primary_key? && !o.pk 2566 return if run_association_callbacks(opts, :before_remove, o) == false 2567 # Allow calling private _remove method 2568 return if !send(opts[:_remove_method], o, *args) && opts.handle_silent_modification_failure? 2569 associations[opts[:name]].delete_if{|x| o === x} if associations.include?(opts[:name]) 2570 remove_reciprocal_object(opts, o) 2571 run_association_callbacks(opts, :after_remove, o) 2572 o 2573 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 2578 def remove_check_existing_object_from_pk(opts, o, *args) 2579 key = o 2580 pkh = opts.associated_class.qualified_primary_key_hash(key) 2581 raise(Sequel::Error, "no object with key(s) #{key.inspect} is currently associated to #{inspect}") unless o = public_send(opts.dataset_method).first(pkh) 2582 o 2583 end
Remove/unset the current object from/as the given object's reciprocal association.
# File lib/sequel/model/associations.rb 2586 def remove_reciprocal_object(opts, o) 2587 return unless reciprocal = opts.reciprocal 2588 if opts.reciprocal_array? 2589 if array = o.associations[reciprocal] 2590 array.delete_if{|x| self === x} 2591 end 2592 else 2593 o.associations[reciprocal] = nil 2594 end 2595 end
Run the callback for the association with the object.
# File lib/sequel/model/associations.rb 2598 def run_association_callbacks(reflection, callback_type, object) 2599 return unless cbs = reflection[callback_type] 2600 2601 begin 2602 cbs.each do |cb| 2603 case cb 2604 when Symbol 2605 # Allow calling private methods in association callbacks 2606 send(cb, object) 2607 when Proc 2608 cb.call(self, object) 2609 else 2610 raise Error, "callbacks should either be Procs or Symbols" 2611 end 2612 end 2613 rescue HookFailed 2614 # The reason we automatically set raise_error for singular associations is that 2615 # assignment in ruby always returns the argument instead of the result of the 2616 # method, so we can't return nil to signal that the association callback prevented 2617 # the modification 2618 return false unless raise_on_save_failure || !reflection.returns_array? 2619 raise 2620 end 2621 end
Set the given object as the associated object for the given many_to_one association reflection
# File lib/sequel/model/associations.rb 2657 def set_associated_object(opts, o) 2658 raise(Error, "associated object #{o.inspect} does not have a primary key") if o && !o.pk 2659 _set_associated_object(opts, o) 2660 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 2652 def set_associated_object_if_same? 2653 @set_associated_object_if_same 2654 end
Set the given object as the associated object for the given one_through_one association reflection
# File lib/sequel/model/associations.rb 2663 def set_one_through_one_associated_object(opts, o) 2664 raise(Error, "object #{inspect} does not have a primary key") unless pk 2665 raise(Error, "associated object #{o.inspect} does not have a primary key") if o && !o.pk 2666 _set_associated_object(opts, o) 2667 end
Set the given object as the associated object for the given one_to_one association reflection
# File lib/sequel/model/associations.rb 2670 def set_one_to_one_associated_object(opts, o) 2671 raise(Error, "object #{inspect} does not have a primary key") unless pk 2672 _set_associated_object(opts, o) 2673 end