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.
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. |
Name symbol for the _add internal association method
# 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
# 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
# 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
# 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
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# File lib/sequel/model/associations.rb, line 164 164: def apply_ruby_eager_limit_strategy(rows, limit_and_offset = limit_and_offset()) 165: name = self[:name] 166: if returns_array? 167: range = slice_range(limit_and_offset) 168: rows.each{|o| o.associations[name] = o.associations[name][range] || []} 169: elsif sr = slice_range(limit_and_offset) 170: offset = sr.begin 171: rows.each{|o| o.associations[name] = o.associations[name][offset]} 172: end 173: end
Use a window function to limit the results of the eager loading dataset.
# 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 = if !returns_array? 149: ds.where(rn => offset ? offset+1 : 1) 150: elsif offset 151: offset += 1 152: if limit 153: ds.where(rn => (offset...(offset+limit))) 154: else 155: ds.where{SQL::Identifier.new(rn) >= offset} 156: end 157: else 158: ds.where{SQL::Identifier.new(rn) <= limit} 159: end 160: end
Whether the associations cache should use an array when storing the associated records during eager loading.
# File lib/sequel/model/associations.rb, line 177 177: def assign_singular? 178: !returns_array? 179: end
The class associated to the current model class via this association
# 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.
# 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.
# File lib/sequel/model/associations.rb, line 214 214: def association_dataset_for(object) 215: condition = if can_have_associated_objects?(object) 216: predicate_keys.zip(predicate_key_values(object)) 217: else 218: false 219: end 220: 221: associated_dataset.where(condition) 222: end
Proc used to create the association dataset method.
# File lib/sequel/model/associations.rb, line 226 226: def association_dataset_proc 227: ASSOCIATION_DATASET_PROC 228: end
Name symbol for association method, the same as the name of the association.
# 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.
# File lib/sequel/model/associations.rb, line 184 184: def can_have_associated_objects?(obj) 185: true 186: 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.
# File lib/sequel/model/associations.rb, line 190 190: def cloneable?(ref) 191: ref[:type] == self[:type] 192: end
Name symbol for the dataset association method
# File lib/sequel/model/associations.rb, line 195 195: def dataset_method 196: self[:dataset_method] 197: end
Whether the dataset needs a primary key to function, true by default.
# File lib/sequel/model/associations.rb, line 200 200: def dataset_need_primary_key? 201: true 202: end
Return the symbol used for the row number column if the window function eager limit strategy is being used, or nil otherwise.
# File lib/sequel/model/associations.rb, line 206 206: def delete_row_number_column(ds=associated_dataset) 207: if eager_limit_strategy == :window_function 208: ds.row_number_column 209: end 210: 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.
# File lib/sequel/model/associations.rb, line 333 333: def eager_graph_lazy_dataset? 334: true 335: end
The eager_graph limit strategy to use for this dataset
# File lib/sequel/model/associations.rb, line 231 231: def eager_graph_limit_strategy(strategy) 232: if self[:limit] || !returns_array? 233: strategy = strategy[self[:name]] if strategy.is_a?(Hash) 234: case strategy 235: when true 236: true_eager_graph_limit_strategy 237: when Symbol 238: strategy 239: else 240: if returns_array? || offset 241: :ruby 242: end 243: end 244: end 245: end
The eager limit strategy to use for this dataset.
# File lib/sequel/model/associations.rb, line 248 248: def eager_limit_strategy 249: cached_fetch(:_eager_limit_strategy) do 250: if self[:limit] || !returns_array? 251: case s = cached_fetch(:eager_limit_strategy){default_eager_limit_strategy} 252: when true 253: true_eager_limit_strategy 254: else 255: s 256: end 257: end 258: end 259: end
Eager load the associated objects using the hash of eager options, yielding each row to the block.
# File lib/sequel/model/associations.rb, line 263 263: def eager_load_results(eo, &block) 264: rows = eo[:rows] 265: initialize_association_cache(rows) unless eo[:initialize_rows] == false 266: if eo[:id_map] 267: ids = eo[:id_map].keys 268: return ids if ids.empty? 269: end 270: strategy = eager_limit_strategy 271: cascade = eo[:associations] 272: eager_limit = nil 273: 274: if eo[:eager_block] || eo[:loader] == false 275: ds = eager_loading_dataset(eo) 276: 277: strategy = ds.opts[:eager_limit_strategy] || strategy 278: 279: eager_limit = 280: if el = ds.opts[:eager_limit] 281: raise Error, "The :eager_limit dataset option is not supported for associations returning a single record" unless returns_array? 282: strategy ||= true_eager_graph_limit_strategy 283: if el.is_a?(Array) 284: el 285: else 286: [el, nil] 287: end 288: else 289: limit_and_offset 290: end 291: 292: strategy = true_eager_graph_limit_strategy if strategy == :union 293: # Correlated subqueries are not supported for regular eager loading 294: strategy = :ruby if strategy == :correlated_subquery 295: strategy = nil if strategy == :ruby && assign_singular? 296: objects = apply_eager_limit_strategy(ds, strategy, eager_limit).all 297: elsif strategy == :union 298: objects = [] 299: ds = associated_dataset 300: loader = union_eager_loader 301: joiner = " UNION ALL " 302: ids.each_slice(subqueries_per_union).each do |slice| 303: objects.concat(ds.with_sql(slice.map{|k| loader.sql(*k)}.join(joiner)).to_a) 304: end 305: ds = ds.eager(cascade) if cascade 306: ds.send(:post_load, objects) 307: else 308: loader = placeholder_eager_loader 309: loader = loader.with_dataset{|dataset| dataset.eager(cascade)} if cascade 310: objects = loader.all(ids) 311: end 312: 313: objects.each(&block) 314: if strategy == :ruby 315: apply_ruby_eager_limit_strategy(rows, eager_limit || limit_and_offset) 316: end 317: end
The key to use for the key hash when eager loading
# File lib/sequel/model/associations.rb, line 320 320: def eager_loader_key 321: self[:eager_loader_key] 322: end
Whether additional conditions should be added when using the filter by associations support.
# File lib/sequel/model/associations.rb, line 339 339: def filter_by_associations_add_conditions? 340: self[:conditions] || self[:eager_block] || self[:limit] 341: 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.
# File lib/sequel/model/associations.rb, line 347 347: def filter_by_associations_conditions_expression(obj) 348: ds = filter_by_associations_conditions_dataset.where(filter_by_associations_conditions_subquery_conditions(obj)) 349: {filter_by_associations_conditions_key=>ds} 350: 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.
# File lib/sequel/model/associations.rb, line 355 355: def finalize 356: return unless cache = self[:cache] 357: 358: finalize_settings.each do |meth, key| 359: next if has_key?(key) 360: 361: # Allow calling private methods to make sure caching is done appropriately 362: send(meth) 363: self[key] = cache.delete(key) if cache.has_key?(key) 364: end 365: 366: nil 367: end
# File lib/sequel/model/associations.rb, line 381 381: def finalize_settings 382: FINALIZE_SETTINGS 383: end
Whether to handle silent modification failure when adding/removing associated records, false by default.
# File lib/sequel/model/associations.rb, line 387 387: def handle_silent_modification_failure? 388: false 389: end
Initialize the associations cache for the current association for the given objects.
# File lib/sequel/model/associations.rb, line 392 392: def initialize_association_cache(objects) 393: name = self[:name] 394: if assign_singular? 395: objects.each{|object| object.associations[name] = nil} 396: else 397: objects.each{|object| object.associations[name] = []} 398: end 399: end
Show which type of reflection this is, and a guess at what code was used to create the association.
# File lib/sequel/model/associations.rb, line 403 403: def inspect 404: o = self[:orig_opts].dup 405: o.delete(:class) 406: o.delete(:class_name) 407: o.delete(:block) unless o[:block] 408: o[:class] = self[:orig_class] if self[:orig_class] 409: 410: "#<#{self.class} #{self[:model]}.#{self[:type]} #{self[:name].inspect}#{", #{o.inspect[1...-1]}" unless o.empty?}>" 411: end
The limit and offset for this association (returned as a two element array).
# File lib/sequel/model/associations.rb, line 414 414: def limit_and_offset 415: if (v = self[:limit]).is_a?(Array) 416: v 417: else 418: [v, nil] 419: end 420: end
Whether the associated object needs a primary key to be added/removed, false by default.
# File lib/sequel/model/associations.rb, line 424 424: def need_associated_primary_key? 425: false 426: end
A placeholder literalizer that can be used to lazily load the association. If one can‘t be used, returns nil.
# File lib/sequel/model/associations.rb, line 430 430: def placeholder_loader 431: if use_placeholder_loader? 432: cached_fetch(:placeholder_loader) do 433: Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds| 434: ds.where(Sequel.&(*predicate_keys.map{|k| SQL::BooleanExpression.new('=''=', k, pl.arg)})) 435: end 436: end 437: end 438: end
The values that predicate_keys should match for objects to be associated.
# File lib/sequel/model/associations.rb, line 446 446: def predicate_key_values(object) 447: predicate_key_methods.map{|k| object.get_column_value(k)} 448: end
The keys to use for loading of the regular dataset, as an array.
# File lib/sequel/model/associations.rb, line 441 441: def predicate_keys 442: cached_fetch(:predicate_keys){Array(predicate_key)} 443: end
Qualify col with the given table name.
# File lib/sequel/model/associations.rb, line 451 451: def qualify(table, col) 452: transform(col) do |k| 453: case k 454: when Symbol, SQL::Identifier 455: SQL::QualifiedIdentifier.new(table, k) 456: else 457: Sequel::Qualifier.new(table).transform(k) 458: end 459: end 460: end
Qualify col with the associated model‘s table name.
# File lib/sequel/model/associations.rb, line 463 463: def qualify_assoc(col) 464: qualify(associated_class.table_name, col) 465: end
Qualify col with the current model‘s table name.
# File lib/sequel/model/associations.rb, line 468 468: def qualify_cur(col) 469: qualify(self[:model].table_name, col) 470: 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.
# File lib/sequel/model/associations.rb, line 478 478: def reciprocal 479: cached_fetch(:reciprocal) do 480: possible_recips = [] 481: 482: associated_class.all_association_reflections.each do |assoc_reflect| 483: if reciprocal_association?(assoc_reflect) 484: possible_recips << assoc_reflect 485: end 486: end 487: 488: if possible_recips.length == 1 489: cached_set(:reciprocal_type, possible_recips.first[:type]) if ambiguous_reciprocal_type? 490: possible_recips.first[:name] 491: end 492: end 493: end
Whether the reciprocal of this association returns an array of objects instead of a single object, true by default.
# File lib/sequel/model/associations.rb, line 497 497: def reciprocal_array? 498: true 499: end
Name symbol for the remove_all_ association method
# File lib/sequel/model/associations.rb, line 502 502: def remove_all_method 503: self[:remove_all_method] 504: end
Whether associated objects need to be removed from the association before being destroyed in order to preserve referential integrity.
# File lib/sequel/model/associations.rb, line 508 508: def remove_before_destroy? 509: true 510: end
Name symbol for the remove_ association method
# File lib/sequel/model/associations.rb, line 513 513: def remove_method 514: self[:remove_method] 515: end
Whether to check that an object to be disassociated is already associated to this object, false by default.
# File lib/sequel/model/associations.rb, line 518 518: def remove_should_check_existing? 519: false 520: end
Whether this association returns an array of objects instead of a single object, true by default.
# File lib/sequel/model/associations.rb, line 524 524: def returns_array? 525: true 526: end
Whether to set the reciprocal association to self when loading associated records, false by default.
# File lib/sequel/model/associations.rb, line 535 535: def set_reciprocal_to_self? 536: false 537: end
Name symbol for the setter association method
# File lib/sequel/model/associations.rb, line 540 540: def setter_method 541: self[:setter_method] 542: end
The range used for slicing when using the :ruby eager limit strategy.
# File lib/sequel/model/associations.rb, line 545 545: def slice_range(limit_and_offset = limit_and_offset()) 546: limit, offset = limit_and_offset 547: if limit || offset 548: (offset||0)..(limit ? (offset||0)+limit-1 : -1) 549: end 550: end