Class Sequel::Model::Associations::AssociationReflection
In: lib/sequel/model/associations.rb
Parent: 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.

Methods

Included Modules

Sequel::Inflections

Constants

ASSOCIATION_DATASET_PROC = proc{|r| r.association_dataset_for(self)}
FINALIZE_SETTINGS = { :associated_class=>:class, :associated_dataset=>:_dataset, :associated_eager_dataset=>:associated_eager_dataset, :eager_limit_strategy=>:_eager_limit_strategy, :filter_by_associations_conditions_dataset=>:filter_by_associations_conditions_dataset, :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

Name symbol for the _add internal association method

[Source]

    # File lib/sequel/model/associations.rb, line 36
36:         def _add_method
37:           self[:_add_method]
38:         end

Name symbol for the _remove_all internal association method

[Source]

    # File lib/sequel/model/associations.rb, line 41
41:         def _remove_all_method
42:           self[:_remove_all_method]
43:         end

Name symbol for the _remove internal association method

[Source]

    # File lib/sequel/model/associations.rb, line 46
46:         def _remove_method
47:           self[:_remove_method]
48:         end

Name symbol for the _setter association method

[Source]

    # File lib/sequel/model/associations.rb, line 51
51:         def _setter_method
52:           self[:_setter_method]
53:         end

Name symbol for the add association method

[Source]

    # File lib/sequel/model/associations.rb, line 56
56:         def add_method
57:           self[:add_method]
58:         end

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

[Source]

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

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

[Source]

     # File lib/sequel/model/associations.rb, line 138
138:         def apply_distinct_on_eager_limit_strategy(ds)
139:           keys = predicate_key
140:           ds.distinct(*keys).order_prepend(*keys)
141:         end

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

[Source]

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

[Source]

     # File lib/sequel/model/associations.rb, line 113
113:         def apply_eager_graph_limit_strategy(strategy, ds)
114:           case strategy
115:           when :distinct_on
116:             apply_distinct_on_eager_limit_strategy(ds.order_prepend(*self[:order]))
117:           when :window_function
118:             apply_window_function_eager_limit_strategy(ds.order_prepend(*self[:order])).select(*ds.columns)
119:           else
120:             ds
121:           end
122:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 126
126:         def apply_eager_limit_strategy(ds, strategy=eager_limit_strategy, limit_and_offset=limit_and_offset())
127:           case strategy
128:           when :distinct_on
129:             apply_distinct_on_eager_limit_strategy(ds)
130:           when :window_function
131:             apply_window_function_eager_limit_strategy(ds, limit_and_offset)
132:           else
133:             ds
134:           end
135:         end

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.

[Source]

     # File lib/sequel/model/associations.rb, line 165
165:         def apply_ruby_eager_limit_strategy(rows, limit_and_offset = limit_and_offset())
166:           name = self[:name]
167:           if returns_array?
168:             range = slice_range(limit_and_offset)
169:             rows.each{|o| o.associations[name] = o.associations[name][range] || []}
170:           elsif sr = slice_range(limit_and_offset)
171:             offset = sr.begin
172:             rows.each{|o| o.associations[name] = o.associations[name][offset]}
173:           end
174:         end

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

[Source]

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

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

[Source]

     # File lib/sequel/model/associations.rb, line 178
178:         def assign_singular?
179:           !returns_array?
180:         end

The class associated to the current model class via this association

[Source]

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

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.

[Source]

    # File lib/sequel/model/associations.rb, line 79
79:         def associated_dataset
80:           cached_fetch(:_dataset){apply_dataset_changes(_associated_dataset)}
81:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 215
215:         def association_dataset_for(object)
216:           condition = if can_have_associated_objects?(object)
217:             predicate_keys.zip(predicate_key_values(object))
218:           else
219:             false
220:           end
221: 
222:           associated_dataset.where(condition)
223:         end

Proc used to create the association dataset method.

[Source]

     # File lib/sequel/model/associations.rb, line 227
227:         def association_dataset_proc
228:           ASSOCIATION_DATASET_PROC
229:         end

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

[Source]

    # File lib/sequel/model/associations.rb, line 61
61:         def association_method
62:           self[:name]
63:         end

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.

[Source]

     # File lib/sequel/model/associations.rb, line 185
185:         def can_have_associated_objects?(obj)
186:           true
187:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 191
191:         def cloneable?(ref)
192:           ref[:type] == self[:type]
193:         end

Name symbol for the dataset association method

[Source]

     # File lib/sequel/model/associations.rb, line 196
196:         def dataset_method
197:           self[:dataset_method]
198:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 201
201:         def dataset_need_primary_key?
202:           true
203:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 207
207:         def delete_row_number_column(ds=associated_dataset)
208:           if eager_limit_strategy == :window_function
209:             ds.row_number_column 
210:           end
211:         end

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.

[Source]

     # File lib/sequel/model/associations.rb, line 334
334:         def eager_graph_lazy_dataset?
335:           true
336:         end

The eager_graph limit strategy to use for this dataset

[Source]

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

The eager limit strategy to use for this dataset.

[Source]

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

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

[Source]

     # File lib/sequel/model/associations.rb, line 264
264:         def eager_load_results(eo, &block)
265:           rows = eo[:rows]
266:           initialize_association_cache(rows) unless eo[:initialize_rows] == false
267:           if eo[:id_map]
268:             ids = eo[:id_map].keys
269:             return ids if ids.empty?
270:           end
271:           strategy = eager_limit_strategy
272:           cascade = eo[:associations]
273:           eager_limit = nil
274: 
275:           if eo[:eager_block] || eo[:loader] == false
276:             ds = eager_loading_dataset(eo)
277: 
278:             strategy = ds.opts[:eager_limit_strategy] || strategy
279: 
280:             eager_limit =
281:               if el = ds.opts[:eager_limit]
282:                 raise Error, "The :eager_limit dataset option is not supported for associations returning a single record" unless returns_array?
283:                 strategy ||= true_eager_graph_limit_strategy
284:                 if el.is_a?(Array)
285:                   el
286:                 else
287:                   [el, nil]
288:                 end
289:               else
290:                 limit_and_offset
291:               end
292: 
293:             strategy = true_eager_graph_limit_strategy if strategy == :union
294:             # Correlated subqueries are not supported for regular eager loading
295:             strategy = :ruby if strategy == :correlated_subquery
296:             strategy = nil if strategy == :ruby && assign_singular?
297:             objects = apply_eager_limit_strategy(ds, strategy, eager_limit).all
298:           elsif strategy == :union
299:             objects = []
300:             ds = associated_dataset
301:             loader = union_eager_loader
302:             joiner = " UNION ALL "
303:             ids.each_slice(subqueries_per_union).each do |slice|
304:               objects.concat(ds.with_sql(slice.map{|k| loader.sql(*k)}.join(joiner)).to_a)
305:             end
306:             ds = ds.eager(cascade) if cascade
307:             ds.send(:post_load, objects)
308:           else
309:             loader = placeholder_eager_loader
310:             loader = loader.with_dataset{|dataset| dataset.eager(cascade)} if cascade
311:             objects = loader.all(ids)
312:           end
313: 
314:           objects.each(&block)
315:           if strategy == :ruby
316:             apply_ruby_eager_limit_strategy(rows, eager_limit || limit_and_offset)
317:           end
318:         end

The key to use for the key hash when eager loading

[Source]

     # File lib/sequel/model/associations.rb, line 321
321:         def eager_loader_key
322:           self[:eager_loader_key]
323:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 327
327:         def eager_loading_use_associated_key?
328:           false
329:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 340
340:         def filter_by_associations_add_conditions?
341:           self[:conditions] || self[:eager_block] || self[:limit]
342:         end

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.

[Source]

     # File lib/sequel/model/associations.rb, line 348
348:         def filter_by_associations_conditions_expression(obj)
349:           ds = filter_by_associations_conditions_dataset.where(filter_by_associations_conditions_subquery_conditions(obj))
350:           {filter_by_associations_conditions_key=>ds}
351:         end

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.

[Source]

     # File lib/sequel/model/associations.rb, line 356
356:         def finalize
357:           return unless cache = self[:cache]
358: 
359:           finalize_settings.each do |meth, key|
360:             next if has_key?(key)
361: 
362:             # Allow calling private methods to make sure caching is done appropriately
363:             send(meth)
364:             self[key] = cache.delete(key) if cache.has_key?(key)
365:           end
366: 
367:           nil
368:         end

[Source]

     # File lib/sequel/model/associations.rb, line 382
382:         def finalize_settings
383:           FINALIZE_SETTINGS
384:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 388
388:         def handle_silent_modification_failure?
389:           false
390:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 393
393:         def initialize_association_cache(objects)
394:           name = self[:name]
395:           if assign_singular?
396:             objects.each{|object| object.associations[name] = nil}
397:           else
398:             objects.each{|object| object.associations[name] = []}
399:           end
400:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 404
404:         def inspect
405:           o = self[:orig_opts].dup
406:           o.delete(:class)
407:           o.delete(:class_name)
408:           o.delete(:block) unless o[:block]
409:           o[:class] = self[:orig_class] if self[:orig_class]
410: 
411:           "#<#{self.class} #{self[:model]}.#{self[:type]} #{self[:name].inspect}#{", #{o.inspect[1...-1]}" unless o.empty?}>"
412:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 415
415:         def limit_and_offset
416:           if (v = self[:limit]).is_a?(Array)
417:             v
418:           else
419:             [v, nil]
420:           end
421:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 425
425:         def need_associated_primary_key?
426:           false
427:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 431
431:         def placeholder_loader
432:           if use_placeholder_loader?
433:             cached_fetch(:placeholder_loader) do
434:               Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
435:                 ds.where(Sequel.&(*predicate_keys.map{|k| SQL::BooleanExpression.new('=''=', k, pl.arg)}))
436:               end
437:             end
438:           end
439:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 447
447:         def predicate_key_values(object)
448:           predicate_key_methods.map{|k| object.get_column_value(k)}
449:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 442
442:         def predicate_keys
443:           cached_fetch(:predicate_keys){Array(predicate_key)}
444:         end

Qualify col with the given table name.

[Source]

     # File lib/sequel/model/associations.rb, line 452
452:         def qualify(table, col)
453:           transform(col) do |k|
454:             case k
455:             when Symbol, SQL::Identifier
456:               SQL::QualifiedIdentifier.new(table, k)
457:             else
458:               Sequel::Qualifier.new(table).transform(k)
459:             end
460:           end
461:         end

Qualify col with the associated model‘s table name.

[Source]

     # File lib/sequel/model/associations.rb, line 464
464:         def qualify_assoc(col)
465:           qualify(associated_class.table_name, col)
466:         end

Qualify col with the current model‘s table name.

[Source]

     # File lib/sequel/model/associations.rb, line 469
469:         def qualify_cur(col)
470:           qualify(self[:model].table_name, col)
471:         end

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.

[Source]

     # File lib/sequel/model/associations.rb, line 479
479:         def reciprocal
480:           cached_fetch(:reciprocal) do
481:             possible_recips = []
482: 
483:             associated_class.all_association_reflections.each do |assoc_reflect|
484:               if reciprocal_association?(assoc_reflect)
485:                 possible_recips << assoc_reflect
486:               end
487:             end
488: 
489:             if possible_recips.length == 1
490:               cached_set(:reciprocal_type, possible_recips.first[:type]) if ambiguous_reciprocal_type?
491:               possible_recips.first[:name]
492:             end
493:           end
494:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 498
498:         def reciprocal_array?
499:           true
500:         end

Name symbol for the remove_all_ association method

[Source]

     # File lib/sequel/model/associations.rb, line 503
503:         def remove_all_method
504:           self[:remove_all_method]
505:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 509
509:         def remove_before_destroy?
510:           true
511:         end

Name symbol for the remove_ association method

[Source]

     # File lib/sequel/model/associations.rb, line 514
514:         def remove_method
515:           self[:remove_method]
516:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 519
519:         def remove_should_check_existing?
520:           false
521:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 525
525:         def returns_array?
526:           true
527:         end

The columns to select when loading the association.

[Source]

     # File lib/sequel/model/associations.rb, line 530
530:         def select
531:           self[:select]
532:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 536
536:         def set_reciprocal_to_self?
537:           false
538:         end

Name symbol for the setter association method

[Source]

     # File lib/sequel/model/associations.rb, line 541
541:         def setter_method
542:           self[:setter_method]
543:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 546
546:         def slice_range(limit_and_offset = limit_and_offset())
547:           limit, offset = limit_and_offset
548:           if limit || offset
549:             (offset||0)..(limit ? (offset||0)+limit-1 : -1)
550:           end
551:         end

[Validate]