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)}

Public Instance methods

Name symbol for the _add internal association method

[Source]

    # File lib/sequel/model/associations.rb, line 28
28:         def _add_method
29: 
30:           "_add_#{singularize(self[:name])}"
31:         end

Name symbol for the _remove_all internal association method

[Source]

    # File lib/sequel/model/associations.rb, line 33
33:         def _remove_all_method
34: 
35:           "_remove_all_#{self[:name]}"
36:         end

Name symbol for the _remove internal association method

[Source]

    # File lib/sequel/model/associations.rb, line 38
38:         def _remove_method
39: 
40:           "_remove_#{singularize(self[:name])}"
41:         end

Name symbol for the _setter association method

[Source]

    # File lib/sequel/model/associations.rb, line 43
43:         def _setter_method
44: 
45:           "_#{self[:name]}="
46:         end

Name symbol for the add association method

[Source]

    # File lib/sequel/model/associations.rb, line 48
48:         def add_method
49: 
50:           "add_#{singularize(self[:name])}"
51:         end

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

[Source]

    # File lib/sequel/model/associations.rb, line 70
70:         def apply_dataset_changes(ds)
71:           ds.extend(AssociationDatasetMethods)
72:           ds.association_reflection = self
73:           self[:extend].each{|m| ds.extend(m)}
74:           ds = ds.select(*select) if select
75:           if c = self[:conditions]
76:             ds = (c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.where(*c) : ds.where(c)
77:           end
78:           ds = ds.order(*self[:order]) if self[:order]
79:           ds = ds.limit(*self[:limit]) if self[:limit]
80:           ds = ds.limit(1) if limit_to_single_row?
81:           ds = ds.eager(self[:eager]) if self[:eager]
82:           ds = ds.distinct if self[:distinct]
83:           ds
84:         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 123
123:         def apply_distinct_on_eager_limit_strategy(ds)
124:           keys = predicate_key
125:           ds.distinct(*keys).order_prepend(*keys)
126:         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 88
88:         def apply_eager_dataset_changes(ds)
89:           ds = apply_dataset_changes(ds)
90:           if block = self[:eager_block]
91:             ds = block.call(ds)
92:           end
93:           ds
94:         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 98
 98:         def apply_eager_graph_limit_strategy(strategy, ds)
 99:           case strategy
100:           when :distinct_on
101:             apply_distinct_on_eager_limit_strategy(ds.order_prepend(*self[:order]))
102:           when :window_function
103:             apply_window_function_eager_limit_strategy(ds.order_prepend(*self[:order])).select(*ds.columns)
104:           else
105:             ds
106:           end
107:         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 111
111:         def apply_eager_limit_strategy(ds, strategy=eager_limit_strategy, limit_and_offset=limit_and_offset())
112:           case strategy
113:           when :distinct_on
114:             apply_distinct_on_eager_limit_strategy(ds)
115:           when :window_function
116:             apply_window_function_eager_limit_strategy(ds, limit_and_offset)
117:           else
118:             ds
119:           end
120:         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 149
149:         def apply_ruby_eager_limit_strategy(rows, limit_and_offset = limit_and_offset())
150:           name = self[:name]
151:           if returns_array?
152:             range = slice_range(limit_and_offset)
153:             rows.each{|o| o.associations[name] = o.associations[name][range] || []}
154:           elsif sr = slice_range(limit_and_offset)
155:             offset = sr.begin
156:             rows.each{|o| o.associations[name] = o.associations[name][offset]}
157:           end
158:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 129
129:         def apply_window_function_eager_limit_strategy(ds, limit_and_offset=limit_and_offset())
130:           rn = ds.row_number_column 
131:           limit, offset = limit_and_offset
132:           ds = ds.unordered.select_append{|o| o.row_number{}.over(:partition=>predicate_key, :order=>ds.opts[:order]).as(rn)}.from_self
133:           ds = if !returns_array?
134:             ds.where(rn => offset ? offset+1 : 1)
135:           elsif offset
136:             offset += 1
137:             if limit
138:               ds.where(rn => (offset...(offset+limit))) 
139:             else
140:               ds.where{SQL::Identifier.new(rn) >= offset} 
141:             end
142:           else
143:             ds.where{SQL::Identifier.new(rn) <= limit} 
144:           end
145:         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 162
162:         def assign_singular?
163:           !returns_array?
164:         end

The class associated to the current model class via this association

[Source]

    # File lib/sequel/model/associations.rb, line 58
58:         def associated_class
59:           cached_fetch(:class){constantize(self[:class_name])}
60:         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 65
65:         def associated_dataset
66:           cached_fetch(:_dataset){apply_dataset_changes(_associated_dataset)}
67:         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 199
199:         def association_dataset_for(object)
200:           associated_dataset.where(predicate_keys.zip(predicate_key_values(object)))
201:         end

Proc used to create the association dataset method.

[Source]

     # File lib/sequel/model/associations.rb, line 205
205:         def association_dataset_proc
206:           ASSOCIATION_DATASET_PROC
207:         end

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

[Source]

    # File lib/sequel/model/associations.rb, line 53
53:         def association_method
54:           self[:name]
55:         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 169
169:         def can_have_associated_objects?(obj)
170:           true
171:         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 175
175:         def cloneable?(ref)
176:           ref[:type] == self[:type]
177:         end

Name symbol for the dataset association method

[Source]

     # File lib/sequel/model/associations.rb, line 180
180:         def dataset_method
181: 
182:           "#{self[:name]}_dataset"
183:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 185
185:         def dataset_need_primary_key?
186:           true
187:         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 191
191:         def delete_row_number_column(ds=associated_dataset)
192:           if eager_limit_strategy == :window_function
193:             ds.row_number_column 
194:           end
195:         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 315
315:         def eager_graph_lazy_dataset?
316:           true
317:         end

The eager_graph limit strategy to use for this dataset

[Source]

     # File lib/sequel/model/associations.rb, line 210
210:         def eager_graph_limit_strategy(strategy)
211:           if self[:limit] || !returns_array?
212:             strategy = strategy[self[:name]] if strategy.is_a?(Hash)
213:             case strategy
214:             when true
215:               true_eager_graph_limit_strategy
216:             when Symbol
217:               strategy
218:             else
219:               if returns_array? || offset
220:                 :ruby
221:               end
222:             end
223:           end
224:         end

The eager limit strategy to use for this dataset.

[Source]

     # File lib/sequel/model/associations.rb, line 227
227:         def eager_limit_strategy
228:           cached_fetch(:_eager_limit_strategy) do
229:             if self[:limit] || !returns_array?
230:               case s = cached_fetch(:eager_limit_strategy){default_eager_limit_strategy}
231:               when true
232:                 true_eager_limit_strategy
233:               else
234:                 s
235:               end
236:             end
237:           end
238:         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 242
242:         def eager_load_results(eo, &block)
243:           rows = eo[:rows]
244:           initialize_association_cache(rows) unless eo[:initialize_rows] == false
245:           if eo[:id_map]
246:             ids = eo[:id_map].keys
247:             return ids if ids.empty?
248:           end
249:           strategy = eager_limit_strategy
250:           cascade = eo[:associations]
251:           eager_limit = nil
252: 
253:           if eo[:eager_block] || eo[:loader] == false
254:             ds = eager_loading_dataset(eo)
255: 
256:             strategy = ds.opts[:eager_limit_strategy] || strategy
257: 
258:             eager_limit =
259:               if el = ds.opts[:eager_limit]
260:                 strategy ||= true_eager_graph_limit_strategy
261:                 if el.is_a?(Array)
262:                   el
263:                 else
264:                   [el, nil]
265:                 end
266:               else
267:                 limit_and_offset
268:               end
269: 
270:             strategy = true_eager_graph_limit_strategy if strategy == :union
271:             # Correlated subqueries are not supported for regular eager loading
272:             strategy = :ruby if strategy == :correlated_subquery
273:             objects = apply_eager_limit_strategy(ds, strategy, eager_limit).all
274:           elsif strategy == :union
275:             objects = []
276:             ds = associated_dataset
277:             loader = union_eager_loader
278:             joiner = " UNION ALL "
279:             ids.each_slice(subqueries_per_union).each do |slice|
280:               objects.concat(ds.with_sql(slice.map{|k| loader.sql(*k)}.join(joiner)).to_a)
281:             end
282:             ds = ds.eager(cascade) if cascade
283:             ds.send(:post_load, objects)
284:           else
285:             loader = placeholder_eager_loader
286:             loader = loader.with_dataset{|dataset| dataset.eager(cascade)} if cascade
287:             objects = loader.all(ids)
288:           end
289: 
290:           objects.each(&block)
291:           if strategy == :ruby
292:             apply_ruby_eager_limit_strategy(rows, eager_limit || limit_and_offset)
293:           end
294:         end

The key to use for the key hash when eager loading

[Source]

     # File lib/sequel/model/associations.rb, line 297
297:         def eager_loader_key
298:           self[:eager_loader_key]
299:         end

Alias of predicate_key, only for backwards compatibility.

[Source]

     # File lib/sequel/model/associations.rb, line 308
308:         def eager_loading_predicate_key
309:           predicate_key
310:         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 303
303:         def eager_loading_use_associated_key?
304:           false
305:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 321
321:         def filter_by_associations_add_conditions?
322:           self[:conditions] || self[:eager_block] || self[:limit]
323:         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 329
329:         def filter_by_associations_conditions_expression(obj)
330:           ds = filter_by_associations_conditions_dataset.where(filter_by_associations_conditions_subquery_conditions(obj))
331:           {filter_by_associations_conditions_key=>ds}
332:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 336
336:         def handle_silent_modification_failure?
337:           false
338:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 341
341:         def initialize_association_cache(objects)
342:           name = self[:name]
343:           if assign_singular?
344:             objects.each{|object| object.associations[name] = nil}
345:           else
346:             objects.each{|object| object.associations[name] = []}
347:           end
348:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 351
351:         def limit_and_offset
352:           if (v = self[:limit]).is_a?(Array)
353:             v
354:           else
355:             [v, nil]
356:           end
357:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 361
361:         def need_associated_primary_key?
362:           false
363:         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 367
367:         def placeholder_loader
368:           if use_placeholder_loader?
369:             cached_fetch(:placeholder_loader) do
370:               Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
371:                 ds.where(*predicate_keys.map{|k| SQL::BooleanExpression.new('=''=', k, pl.arg)})
372:               end
373:             end
374:           end
375:         end

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

[Source]

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

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

[Source]

     # File lib/sequel/model/associations.rb, line 378
378:         def predicate_keys
379:           cached_fetch(:predicate_keys){Array(predicate_key)}
380:         end

Qualify col with the given table name. If col is an array of columns, return an array of qualified columns. Only qualifies Symbols and SQL::Identifier values, other values are not modified.

[Source]

     # File lib/sequel/model/associations.rb, line 390
390:         def qualify(table, col)
391:           transform(col) do |k|
392:             case k
393:             when Symbol, SQL::Identifier
394:               SQL::QualifiedIdentifier.new(table, k)
395:             else
396:               Sequel::Qualifier.new(self[:model].dataset, table).transform(k)
397:             end
398:           end
399:         end

Qualify col with the associated model‘s table name.

[Source]

     # File lib/sequel/model/associations.rb, line 402
402:         def qualify_assoc(col)
403:           qualify(associated_class.table_name, col)
404:         end

Qualify col with the current model‘s table name.

[Source]

     # File lib/sequel/model/associations.rb, line 407
407:         def qualify_cur(col)
408:           qualify(self[:model].table_name, col)
409:         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 417
417:         def reciprocal
418:           cached_fetch(:reciprocal) do
419:             possible_recips = []
420: 
421:             associated_class.all_association_reflections.each do |assoc_reflect|
422:               if reciprocal_association?(assoc_reflect)
423:                 possible_recips << assoc_reflect
424:               end
425:             end
426: 
427:             if possible_recips.length == 1
428:               cached_set(:reciprocal_type, possible_recips.first[:type]) if ambiguous_reciprocal_type?
429:               possible_recips.first[:name]
430:             end
431:           end
432:         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 436
436:         def reciprocal_array?
437:           true
438:         end

Name symbol for the remove_all_ association method

[Source]

     # File lib/sequel/model/associations.rb, line 441
441:         def remove_all_method
442: 
443:           "remove_all_#{self[:name]}"
444:         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 447
447:         def remove_before_destroy?
448:           true
449:         end

Name symbol for the remove_ association method

[Source]

     # File lib/sequel/model/associations.rb, line 452
452:         def remove_method
453: 
454:           "remove_#{singularize(self[:name])}"
455:         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 457
457:         def remove_should_check_existing?
458:           false
459:         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 463
463:         def returns_array?
464:           true
465:         end

The columns to select when loading the association.

[Source]

     # File lib/sequel/model/associations.rb, line 468
468:         def select
469:           self[:select]
470:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 474
474:         def set_reciprocal_to_self?
475:           false
476:         end

Name symbol for the setter association method

[Source]

     # File lib/sequel/model/associations.rb, line 479
479:         def setter_method
480: 
481:           "#{self[:name]}="
482:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 484
484:         def slice_range(limit_and_offset = limit_and_offset())
485:           limit, offset = limit_and_offset
486:           if limit || offset
487:             (offset||0)..(limit ? (offset||0)+limit-1 : -1)
488:           end
489:         end

[Validate]