class Sequel::Model::Associations::AssociationReflection

  1. lib/sequel/model/associations.rb
Superclass: Hash

AssociationReflection is a Hash subclass that keeps information on Sequel::Model associations. It provides methods to reduce internal code duplication. It should not be instantiated by the user.

Included modules

  1. Sequel::Inflections

Constants

ASSOCIATION_DATASET_PROC = proc{|r| r.association_dataset_for(self)}  
FINALIZE_SETTINGS = { :associated_class=>:class, :associated_dataset=>:_dataset, :eager_limit_strategy=>:_eager_limit_strategy, :placeholder_loader=>:placeholder_loader, :predicate_key=>:predicate_key, :predicate_keys=>:predicate_keys, :reciprocal=>:reciprocal, }.freeze  

Map of methods to cache keys used for finalizing associations.

Public Instance methods

_add_method()

Name symbol for the _add internal association method

[show source]
   # File lib/sequel/model/associations.rb
36 def _add_method
37   self[:_add_method]
38 end
_remove_all_method()

Name symbol for the _remove_all internal association method

[show source]
   # File lib/sequel/model/associations.rb
41 def _remove_all_method
42   self[:_remove_all_method]
43 end
_remove_method()

Name symbol for the _remove internal association method

[show source]
   # File lib/sequel/model/associations.rb
46 def _remove_method
47   self[:_remove_method]
48 end
_setter_method()

Name symbol for the _setter association method

[show source]
   # File lib/sequel/model/associations.rb
51 def _setter_method
52   self[:_setter_method]
53 end
add_method()

Name symbol for the add association method

[show source]
   # File lib/sequel/model/associations.rb
56 def add_method
57   self[:add_method]
58 end
apply_dataset_changes(ds)

Apply all non-instance specific changes to the given dataset and return it.

[show source]
   # File lib/sequel/model/associations.rb
84 def apply_dataset_changes(ds)
85   ds = ds.with_extend(AssociationDatasetMethods).clone(:association_reflection => self)
86   if exts = self[:reverse_extend]
87     ds = ds.with_extend(*exts)
88   end
89   ds = ds.select(*select) if select
90   if c = self[:conditions]
91     ds = (c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.where(*c) : ds.where(c)
92   end
93   ds = ds.order(*self[:order]) if self[:order]
94   ds = ds.limit(*self[:limit]) if self[:limit]
95   ds = ds.limit(1).skip_limit_check if limit_to_single_row?
96   ds = ds.eager(self[:eager]) if self[:eager]
97   ds = ds.distinct if self[:distinct]
98   ds
99 end
apply_distinct_on_eager_limit_strategy(ds)

Use DISTINCT ON and ORDER BY clauses to limit the results to the first record with matching keys.

[show source]
    # File lib/sequel/model/associations.rb
140 def apply_distinct_on_eager_limit_strategy(ds)
141   keys = predicate_key
142   ds.distinct(*keys).order_prepend(*keys)
143 end
apply_eager_dataset_changes(ds)

Apply all non-instance specific changes and the eager_block option to the given dataset and return it.

[show source]
    # File lib/sequel/model/associations.rb
103 def apply_eager_dataset_changes(ds)
104   ds = apply_dataset_changes(ds)
105   if block = self[:eager_block]
106     ds = block.call(ds)
107   end
108   ds
109 end
apply_eager_graph_limit_strategy(strategy, ds)

Apply the eager graph limit strategy to the dataset to graph into the current dataset, or return the dataset unmodified if no SQL limit strategy is needed.

[show source]
    # File lib/sequel/model/associations.rb
113 def apply_eager_graph_limit_strategy(strategy, ds)
114   case strategy
115   when :lateral_subquery
116     apply_lateral_subquery_eager_graph_limit_strategy(ds)
117   when :distinct_on
118     apply_distinct_on_eager_limit_strategy(ds.order_prepend(*self[:order]))
119   when :window_function
120     apply_window_function_eager_limit_strategy(ds.order_prepend(*self[:order])).select(*ds.columns)
121   else
122     ds
123   end
124 end
apply_eager_limit_strategy(ds, strategy=eager_limit_strategy, limit_and_offset=limit_and_offset())

Apply an eager limit strategy to the dataset, or return the dataset unmodified if it doesn’t need an eager limit strategy.

[show source]
    # File lib/sequel/model/associations.rb
128 def apply_eager_limit_strategy(ds, strategy=eager_limit_strategy, limit_and_offset=limit_and_offset())
129   case strategy
130   when :distinct_on
131     apply_distinct_on_eager_limit_strategy(ds)
132   when :window_function
133     apply_window_function_eager_limit_strategy(ds, limit_and_offset)
134   else
135     ds
136   end
137 end
apply_ruby_eager_limit_strategy(rows, limit_and_offset = limit_and_offset())

If the ruby eager limit strategy is being used, slice the array using the slice range to return the object(s) at the correct offset/limit.

[show source]
    # File lib/sequel/model/associations.rb
167 def apply_ruby_eager_limit_strategy(rows, limit_and_offset = limit_and_offset())
168   name = self[:name]
169   return unless range = slice_range(limit_and_offset)
170   if returns_array?
171     rows.each{|o| o.associations[name] = o.associations[name][range] || []}
172   else
173     offset = range.begin
174     rows.each{|o| o.associations[name] = o.associations[name][offset]}
175   end
176 end
apply_window_function_eager_limit_strategy(ds, limit_and_offset=limit_and_offset())

Use a window function to limit the results of the eager loading dataset.

[show source]
    # File lib/sequel/model/associations.rb
146 def apply_window_function_eager_limit_strategy(ds, limit_and_offset=limit_and_offset())
147   rn = ds.row_number_column 
148   limit, offset = limit_and_offset
149   ds = ds.unordered.select_append{|o| o.row_number.function.over(:partition=>predicate_key, :order=>ds.opts[:order]).as(rn)}.from_self
150   ds = ds.order(rn) if ds.db.database_type == :mysql
151   ds = if !returns_array?
152     ds.where(rn => offset ? offset+1 : 1)
153   elsif offset
154     offset += 1
155     if limit
156       ds.where(rn => (offset...(offset+limit))) 
157     else
158       ds.where{SQL::Identifier.new(rn) >= offset} 
159     end
160   else
161     ds.where{SQL::Identifier.new(rn) <= limit} 
162   end
163 end
assign_singular?()

Whether the associations cache should use an array when storing the associated records during eager loading.

[show source]
    # File lib/sequel/model/associations.rb
180 def assign_singular?
181   !returns_array?
182 end
associated_class()

The class associated to the current model class via this association

[show source]
   # File lib/sequel/model/associations.rb
66 def associated_class
67   cached_fetch(:class) do
68     begin
69       constantize(self[:class_name])
70     rescue NameError => e
71       raise NameError, "#{e.message} (this happened when attempting to find the associated class for #{inspect})", e.backtrace
72     end
73   end
74 end
associated_dataset()

The dataset associated via this association, with the non-instance specific changes already applied. This will be a joined dataset if the association requires joining tables.

[show source]
   # File lib/sequel/model/associations.rb
79 def associated_dataset
80   cached_fetch(:_dataset){apply_dataset_changes(_associated_dataset)}
81 end
association_dataset_for(object)

Return an dataset that will load the appropriate associated objects for the given object using this association.

[show source]
    # File lib/sequel/model/associations.rb
217 def association_dataset_for(object)
218   condition = if can_have_associated_objects?(object)
219     predicate_keys.zip(predicate_key_values(object))
220   else
221     false
222   end
223 
224   associated_dataset.where(condition)
225 end
association_dataset_proc()

Proc used to create the association dataset method.

[show source]
    # File lib/sequel/model/associations.rb
229 def association_dataset_proc
230   ASSOCIATION_DATASET_PROC
231 end
association_method()

Name symbol for association method, the same as the name of the association.

[show source]
   # File lib/sequel/model/associations.rb
61 def association_method
62   self[:name]
63 end
can_have_associated_objects?(obj)

Whether this association can have associated objects, given the current object. Should be false if obj cannot have associated objects because the necessary key columns are NULL.

[show source]
    # File lib/sequel/model/associations.rb
187 def can_have_associated_objects?(obj)
188   true
189 end
cloneable?(ref)

Whether you are able to clone from the given association type to the current association type, true by default only if the types match.

[show source]
    # File lib/sequel/model/associations.rb
193 def cloneable?(ref)
194   ref[:type] == self[:type]
195 end
dataset_method()

Name symbol for the dataset association method

[show source]
    # File lib/sequel/model/associations.rb
198 def dataset_method
199   self[:dataset_method]
200 end
dataset_need_primary_key?()

Whether the dataset needs a primary key to function, true by default.

[show source]
    # File lib/sequel/model/associations.rb
203 def dataset_need_primary_key?
204   true
205 end
delete_row_number_column(ds=associated_dataset)

Return the symbol used for the row number column if the window function eager limit strategy is being used, or nil otherwise.

[show source]
    # File lib/sequel/model/associations.rb
209 def delete_row_number_column(ds=associated_dataset)
210   if eager_limit_strategy == :window_function
211     ds.row_number_column 
212   end
213 end
eager_graph_lazy_dataset?()

Whether to eagerly graph a lazy dataset, true by default. If this is false, the association won’t respect the :eager_graph option when loading the association for a single record.

[show source]
    # File lib/sequel/model/associations.rb
361 def eager_graph_lazy_dataset?
362   true
363 end
eager_graph_limit_strategy(strategy)

The eager_graph limit strategy to use for this dataset

[show source]
    # File lib/sequel/model/associations.rb
234 def eager_graph_limit_strategy(strategy)
235   if self[:limit] || !returns_array?
236     strategy = strategy[self[:name]] if strategy.is_a?(Hash)
237     case strategy
238     when true
239       true_eager_graph_limit_strategy
240     when Symbol
241       strategy
242     else
243       if returns_array? || offset
244         :ruby
245       end
246     end
247   end
248 end
eager_limit_strategy()

The eager limit strategy to use for this dataset.

[show source]
    # File lib/sequel/model/associations.rb
251 def eager_limit_strategy
252   cached_fetch(:_eager_limit_strategy) do
253     if self[:limit] || !returns_array?
254       case s = cached_fetch(:eager_limit_strategy){default_eager_limit_strategy}
255       when true
256         true_eager_limit_strategy
257       else
258         s
259       end
260     end
261   end
262 end
eager_load_results(eo, &block)

Eager load the associated objects using the hash of eager options, yielding each row to the block.

[show source]
    # File lib/sequel/model/associations.rb
266 def eager_load_results(eo, &block)
267   rows = eo[:rows]
268   unless eo[:initialize_rows] == false
269     Sequel.synchronize_with(eo[:mutex]){initialize_association_cache(rows)}
270   end
271   if eo[:id_map]
272     ids = eo[:id_map].keys
273     return ids if ids.empty?
274   end
275   strategy = eager_limit_strategy
276   cascade = eo[:associations]
277   eager_limit = nil
278 
279   if eo[:no_results]
280     no_results = true
281   elsif eo[:eager_block] || eo[:loader] == false || !use_placeholder_loader?
282     ds = eager_loading_dataset(eo)
283 
284     strategy = ds.opts[:eager_limit_strategy] || strategy
285 
286     eager_limit =
287       if el = ds.opts[:eager_limit]
288         raise Error, "The :eager_limit dataset option is not supported for associations returning a single record" unless returns_array?
289         strategy ||= true_eager_graph_limit_strategy
290         if el.is_a?(Array)
291           el
292         else
293           [el, nil]
294         end
295       else
296         limit_and_offset
297       end
298 
299     strategy = true_eager_graph_limit_strategy if strategy == :union
300     # Correlated subqueries are not supported for regular eager loading
301     strategy = :ruby if strategy == :correlated_subquery
302     strategy = nil if strategy == :ruby && assign_singular?
303 
304     objects = if strategy == :lateral_subquery
305       apply_lateral_subquery_eager_limit_strategy(ds, ids, eager_limit).all
306     else
307       apply_eager_limit_strategy(ds, strategy, eager_limit).all
308     end
309 
310     if strategy == :window_function
311       delete_rn = ds.row_number_column 
312       objects.each{|obj| obj.remove_key!(delete_rn)}
313     end
314   elsif strategy == :union
315     objects = []
316     ds = associated_dataset
317     loader = union_eager_loader
318     joiner = " UNION ALL "
319     ids.each_slice(subqueries_per_union).each do |slice|
320       sql = loader.send(:sql_origin)
321       join = false
322       slice.each do |k|
323         if join
324           sql << joiner
325         else
326           join = true
327         end
328         loader.append_sql(sql, *k)
329       end
330       objects.concat(ds.with_sql(sql.freeze).to_a)
331     end
332     ds = ds.eager(cascade) if cascade
333     ds.send(:post_load, objects)
334   else
335     loader = placeholder_eager_loader
336     loader = loader.with_dataset{|dataset| dataset.eager(cascade)} if cascade
337     objects = loader.all(ids)
338   end
339 
340   Sequel.synchronize_with(eo[:mutex]){objects.each(&block)} unless no_results
341 
342   if strategy == :ruby
343     apply_ruby_eager_limit_strategy(rows, eager_limit || limit_and_offset)
344   end
345 end
eager_loader_key()

The key to use for the key hash when eager loading

[show source]
    # File lib/sequel/model/associations.rb
348 def eager_loader_key
349   self[:eager_loader_key]
350 end
eager_loading_use_associated_key?()

By default associations do not need to select a key in an associated table to eagerly load.

[show source]
    # File lib/sequel/model/associations.rb
354 def eager_loading_use_associated_key?
355   false
356 end
filter_by_associations_add_conditions?()

Whether additional conditions should be added when using the filter by associations support.

[show source]
    # File lib/sequel/model/associations.rb
367 def filter_by_associations_add_conditions?
368   self[:conditions] || self[:eager_block] || self[:limit]
369 end
filter_by_associations_conditions_expression(obj)

The expression to use for the additional conditions to be added for the filter by association support, when the association itself is filtered. Works by using a subquery to test that the objects passed also meet the association filter criteria.

[show source]
    # File lib/sequel/model/associations.rb
375 def filter_by_associations_conditions_expression(obj)
376   ds = if filter_by_associations_limit_strategy == :lateral_subquery
377     apply_lateral_subquery_filter_limit_strategy(associated_eager_dataset, obj)
378   else
379     filter_by_associations_conditions_dataset.where(filter_by_associations_conditions_subquery_conditions(obj))
380   end
381 
382   {filter_by_associations_conditions_key=>ds}
383 end
finalize()

Finalize the association by first attempting to populate the thread-safe cache, and then transfering the thread-safe cache value to the association itself, so that a mutex is not needed to get the value.

[show source]
    # File lib/sequel/model/associations.rb
388 def finalize
389   return unless cache = self[:cache]
390 
391   finalizer = proc do |meth, key|
392     next if has_key?(key)
393 
394     # Allow calling private methods to make sure caching is done appropriately
395     send(meth)
396     self[key] = cache.delete(key) if cache.has_key?(key)
397   end
398 
399   finalize_settings.each(&finalizer)
400 
401   unless self[:instance_specific]
402     finalizer.call(:associated_eager_dataset, :associated_eager_dataset)
403     finalizer.call(:filter_by_associations_conditions_dataset, :filter_by_associations_conditions_dataset)
404   end
405 
406   nil
407 end
finalize_settings()
[show source]
    # File lib/sequel/model/associations.rb
419 def finalize_settings
420   FINALIZE_SETTINGS
421 end
handle_silent_modification_failure?()

Whether to handle silent modification failure when adding/removing associated records, false by default.

[show source]
    # File lib/sequel/model/associations.rb
425 def handle_silent_modification_failure?
426   false
427 end
hash()

Hash value for the association reflection. This is precomputed to avoid concurrency issues at runtime.

[show source]
    # File lib/sequel/model/associations.rb
431 def hash
432   self[:_hash]
433 end
initialize_association_cache(objects)

Initialize the associations cache for the current association for the given objects.

[show source]
    # File lib/sequel/model/associations.rb
436 def initialize_association_cache(objects)
437   name = self[:name]
438   if assign_singular?
439     objects.each{|object| object.associations[name] = nil}
440   else
441     objects.each{|object| object.associations[name] = []}
442   end
443 end
inspect()

Show which type of reflection this is, and a guess at what code was used to create the association.

[show source]
    # File lib/sequel/model/associations.rb
447 def inspect
448   o = self[:orig_opts].dup
449   o.delete(:class)
450   o.delete(:class_name)
451   o.delete(:block) unless o[:block]
452   o[:class] = self[:orig_class] if self[:orig_class]
453 
454   "#<#{self.class} #{self[:model]}.#{self[:type]} #{self[:name].inspect}#{", #{o.inspect[1...-1]}" unless o.empty?}>"
455 end
limit_and_offset()

The limit and offset for this association (returned as a two element array).

[show source]
    # File lib/sequel/model/associations.rb
458 def limit_and_offset
459   if (v = self[:limit]).is_a?(Array)
460     v
461   else
462     [v, nil]
463   end
464 end
need_associated_primary_key?()

Whether the associated object needs a primary key to be added/removed, false by default.

[show source]
    # File lib/sequel/model/associations.rb
468 def need_associated_primary_key?
469   false
470 end
placeholder_loader()

A placeholder literalizer that can be used to lazily load the association. If one can’t be used, returns nil.

[show source]
    # File lib/sequel/model/associations.rb
474 def placeholder_loader
475   if use_placeholder_loader?
476     cached_fetch(:placeholder_loader) do
477       associated_dataset.placeholder_literalizer_loader do |pl, ds|
478         ds = ds.where(Sequel.&(*predicate_keys.map{|k| SQL::BooleanExpression.new(:'=', k, pl.arg)}))
479         if self[:block]
480           ds = self[:block].call(ds)
481         end
482         ds
483       end
484     end
485   end
486 end
predicate_key_values(object)

The values that predicate_keys should match for objects to be associated.

[show source]
    # File lib/sequel/model/associations.rb
494 def predicate_key_values(object)
495   predicate_key_methods.map{|k| object.get_column_value(k)}
496 end
predicate_keys()

The keys to use for loading of the regular dataset, as an array.

[show source]
    # File lib/sequel/model/associations.rb
489 def predicate_keys
490   cached_fetch(:predicate_keys){Array(predicate_key)}
491 end
qualify(table, col)

Qualify col with the given table name.

[show source]
    # File lib/sequel/model/associations.rb
499 def qualify(table, col)
500   transform(col) do |k|
501     case k
502     when Symbol, SQL::Identifier
503       SQL::QualifiedIdentifier.new(table, k)
504     else
505       Sequel::Qualifier.new(table).transform(k)
506     end
507   end
508 end
qualify_assoc(col)

Qualify col with the associated model’s table name.

[show source]
    # File lib/sequel/model/associations.rb
511 def qualify_assoc(col)
512   qualify(associated_class.table_name, col)
513 end
qualify_cur(col)

Qualify col with the current model’s table name.

[show source]
    # File lib/sequel/model/associations.rb
516 def qualify_cur(col)
517   qualify(self[:model].table_name, col)
518 end
reciprocal()

Returns the reciprocal association variable, if one exists. The reciprocal association is the association in the associated class that is the opposite of the current association. For example, Album.many_to_one :artist and Artist.one_to_many :albums are reciprocal associations. This information is to populate reciprocal associations. For example, when you do this_artist.add_album(album) it sets album.artist to this_artist.

[show source]
    # File lib/sequel/model/associations.rb
526 def reciprocal
527   cached_fetch(:reciprocal) do
528     possible_recips = []
529 
530     associated_class.all_association_reflections.each do |assoc_reflect|
531       if reciprocal_association?(assoc_reflect)
532         possible_recips << assoc_reflect
533       end
534     end
535 
536     if possible_recips.length == 1
537       cached_set(:reciprocal_type, possible_recips.first[:type]) if ambiguous_reciprocal_type?
538       possible_recips.first[:name]
539     end
540   end
541 end
reciprocal_array?()

Whether the reciprocal of this association returns an array of objects instead of a single object, true by default.

[show source]
    # File lib/sequel/model/associations.rb
545 def reciprocal_array?
546   true
547 end
remove_all_method()

Name symbol for the remove_all_ association method

[show source]
    # File lib/sequel/model/associations.rb
550 def remove_all_method
551   self[:remove_all_method]
552 end
remove_before_destroy?()

Whether associated objects need to be removed from the association before being destroyed in order to preserve referential integrity.

[show source]
    # File lib/sequel/model/associations.rb
556 def remove_before_destroy?
557   true
558 end
remove_method()

Name symbol for the remove_ association method

[show source]
    # File lib/sequel/model/associations.rb
561 def remove_method
562   self[:remove_method]
563 end
remove_should_check_existing?()

Whether to check that an object to be disassociated is already associated to this object, false by default.

[show source]
    # File lib/sequel/model/associations.rb
566 def remove_should_check_existing?
567   false
568 end
returns_array?()

Whether this association returns an array of objects instead of a single object, true by default.

[show source]
    # File lib/sequel/model/associations.rb
572 def returns_array?
573   true
574 end
select()

The columns to select when loading the association.

[show source]
    # File lib/sequel/model/associations.rb
577 def select
578   self[:select]
579 end
set_reciprocal_to_self?()

Whether to set the reciprocal association to self when loading associated records, false by default.

[show source]
    # File lib/sequel/model/associations.rb
583 def set_reciprocal_to_self?
584   false
585 end
setter_method()

Name symbol for the setter association method

[show source]
    # File lib/sequel/model/associations.rb
588 def setter_method
589   self[:setter_method]
590 end
slice_range(limit_and_offset = limit_and_offset())

The range used for slicing when using the :ruby eager limit strategy.

[show source]
    # File lib/sequel/model/associations.rb
593 def slice_range(limit_and_offset = limit_and_offset())
594   limit, offset = limit_and_offset
595   if limit || offset
596     (offset||0)..(limit ? (offset||0)+limit-1 : -1)
597   end
598 end