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 25
25:         def _add_method
26: 
27:           "_add_#{singularize(self[:name])}"
28:         end

Name symbol for the _remove_all internal association method

[Source]

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

Name symbol for the _remove internal association method

[Source]

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

Name symbol for the _setter association method

[Source]

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

Name symbol for the add association method

[Source]

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

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

[Source]

    # File lib/sequel/model/associations.rb, line 67
67:         def apply_dataset_changes(ds)
68:           ds.extend(AssociationDatasetMethods)
69:           ds.association_reflection = self
70:           self[:extend].each{|m| ds.extend(m)}
71:           ds = ds.select(*select) if select
72:           if c = self[:conditions]
73:             ds = (c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.where(*c) : ds.where(c)
74:           end
75:           ds = ds.order(*self[:order]) if self[:order]
76:           ds = ds.limit(*self[:limit]) if self[:limit]
77:           ds = ds.limit(1) if limit_to_single_row?
78:           ds = ds.eager(self[:eager]) if self[:eager]
79:           ds = ds.distinct if self[:distinct]
80:           ds
81:         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 120
120:         def apply_distinct_on_eager_limit_strategy(ds)
121:           keys = predicate_key
122:           ds.distinct(*keys).order_prepend(*keys)
123:         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 85
85:         def apply_eager_dataset_changes(ds)
86:           ds = apply_dataset_changes(ds)
87:           if block = self[:eager_block]
88:             ds = block.call(ds)
89:           end
90:           ds
91:         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 95
 95:         def apply_eager_graph_limit_strategy(strategy, ds)
 96:           case strategy
 97:           when :distinct_on
 98:             apply_distinct_on_eager_limit_strategy(ds.order_prepend(*self[:order]))
 99:           when :window_function
100:             apply_window_function_eager_limit_strategy(ds.order_prepend(*self[:order])).select(*ds.columns)
101:           else
102:             ds
103:           end
104:         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 108
108:         def apply_eager_limit_strategy(ds, strategy=eager_limit_strategy)
109:           case strategy
110:           when :distinct_on
111:             apply_distinct_on_eager_limit_strategy(ds)
112:           when :window_function
113:             apply_window_function_eager_limit_strategy(ds)
114:           else
115:             ds
116:           end
117:         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 146
146:         def apply_ruby_eager_limit_strategy(rows)
147:           name = self[:name]
148:           if returns_array?
149:             range = slice_range
150:             rows.each{|o| o.associations[name] = o.associations[name][range] || []}
151:           elsif slice_range
152:             offset = slice_range.begin
153:             rows.each{|o| o.associations[name] = o.associations[name][offset]}
154:           end
155:         end

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

[Source]

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

The class associated to the current model class via this association

[Source]

    # File lib/sequel/model/associations.rb, line 55
55:         def associated_class
56:           cached_fetch(:class){constantize(self[:class_name])}
57:         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 62
62:         def associated_dataset
63:           cached_fetch(:_dataset){apply_dataset_changes(_associated_dataset)}
64:         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 196
196:         def association_dataset_for(object)
197:           associated_dataset.where(predicate_keys.zip(predicate_key_values(object)))
198:         end

Proc used to create the association dataset method.

[Source]

     # File lib/sequel/model/associations.rb, line 202
202:         def association_dataset_proc
203:           ASSOCIATION_DATASET_PROC
204:         end

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

[Source]

    # File lib/sequel/model/associations.rb, line 50
50:         def association_method
51:           self[:name]
52:         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 166
166:         def can_have_associated_objects?(obj)
167:           true
168:         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 172
172:         def cloneable?(ref)
173:           ref[:type] == self[:type]
174:         end

Name symbol for the dataset association method

[Source]

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

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

[Source]

     # File lib/sequel/model/associations.rb, line 182
182:         def dataset_need_primary_key?
183:           true
184:         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 188
188:         def delete_row_number_column(ds=associated_dataset)
189:           if eager_limit_strategy == :window_function
190:             ds.row_number_column 
191:           end
192:         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 295
295:         def eager_graph_lazy_dataset?
296:           true
297:         end

The eager_graph limit strategy to use for this dataset

[Source]

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

The eager limit strategy to use for this dataset.

[Source]

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

The key to use for the key hash when eager loading

[Source]

     # File lib/sequel/model/associations.rb, line 277
277:         def eager_loader_key
278:           self[:eager_loader_key]
279:         end

Alias of predicate_key, only for backwards compatibility.

[Source]

     # File lib/sequel/model/associations.rb, line 288
288:         def eager_loading_predicate_key
289:           predicate_key
290:         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 283
283:         def eager_loading_use_associated_key?
284:           false
285:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 301
301:         def filter_by_associations_add_conditions?
302:           self[:conditions] || self[:eager_block] || self[:limit]
303:         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 309
309:         def filter_by_associations_conditions_expression(obj)
310:           ds = filter_by_associations_conditions_dataset.where(filter_by_associations_conditions_subquery_conditions(obj))
311:           {filter_by_associations_conditions_key=>ds}
312:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 316
316:         def handle_silent_modification_failure?
317:           false
318:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 321
321:         def initialize_association_cache(objects)
322:           name = self[:name]
323:           if assign_singular?
324:             objects.each{|object| object.associations[name] = nil}
325:           else
326:             objects.each{|object| object.associations[name] = []}
327:           end
328:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 331
331:         def limit_and_offset
332:           if (v = self[:limit]).is_a?(Array)
333:             v
334:           else
335:             [v, nil]
336:           end
337:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 341
341:         def need_associated_primary_key?
342:           false
343:         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 347
347:         def placeholder_loader
348:           if use_placeholder_loader?
349:             cached_fetch(:placeholder_loader) do
350:               Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
351:                 ds.where(*predicate_keys.map{|k| SQL::BooleanExpression.new('=''=', k, pl.arg)})
352:               end
353:             end
354:           end
355:         end

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

[Source]

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

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

[Source]

     # File lib/sequel/model/associations.rb, line 358
358:         def predicate_keys
359:           cached_fetch(:predicate_keys){Array(predicate_key)}
360:         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 370
370:         def qualify(table, col)
371:           transform(col) do |k|
372:             case k
373:             when Symbol, SQL::Identifier
374:               SQL::QualifiedIdentifier.new(table, k)
375:             else
376:               Sequel::Qualifier.new(self[:model].dataset, table).transform(k)
377:             end
378:           end
379:         end

Qualify col with the associated model‘s table name.

[Source]

     # File lib/sequel/model/associations.rb, line 382
382:         def qualify_assoc(col)
383:           qualify(associated_class.table_name, col)
384:         end

Qualify col with the current model‘s table name.

[Source]

     # File lib/sequel/model/associations.rb, line 387
387:         def qualify_cur(col)
388:           qualify(self[:model].table_name, col)
389:         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 397
397:         def reciprocal
398:           cached_fetch(:reciprocal) do
399:             possible_recips = []
400: 
401:             associated_class.all_association_reflections.each do |assoc_reflect|
402:               if reciprocal_association?(assoc_reflect)
403:                 possible_recips << assoc_reflect
404:               end
405:             end
406: 
407:             if possible_recips.length == 1
408:               cached_set(:reciprocal_type, possible_recips.first[:type]) if ambiguous_reciprocal_type?
409:               possible_recips.first[:name]
410:             end
411:           end
412:         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 416
416:         def reciprocal_array?
417:           true
418:         end

Name symbol for the remove_all_ association method

[Source]

     # File lib/sequel/model/associations.rb, line 421
421:         def remove_all_method
422: 
423:           "remove_all_#{self[:name]}"
424:         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 427
427:         def remove_before_destroy?
428:           true
429:         end

Name symbol for the remove_ association method

[Source]

     # File lib/sequel/model/associations.rb, line 432
432:         def remove_method
433: 
434:           "remove_#{singularize(self[:name])}"
435:         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 437
437:         def remove_should_check_existing?
438:           false
439:         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 443
443:         def returns_array?
444:           true
445:         end

The columns to select when loading the association.

[Source]

     # File lib/sequel/model/associations.rb, line 448
448:         def select
449:           self[:select]
450:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 454
454:         def set_reciprocal_to_self?
455:           false
456:         end

Name symbol for the setter association method

[Source]

     # File lib/sequel/model/associations.rb, line 459
459:         def setter_method
460: 
461:           "#{self[:name]}="
462:         end

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

[Source]

     # File lib/sequel/model/associations.rb, line 464
464:         def slice_range
465:           limit, offset = limit_and_offset
466:           if limit || offset
467:             (offset||0)..(limit ? (offset||0)+limit-1 : -1)
468:           end
469:         end

[Validate]