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)} |
Name symbol for the _add internal association method
# 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
# 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
# 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
# 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
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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
# 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.
# 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.
# 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.
# 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
# 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.
# 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.
# 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
# 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.
# File lib/sequel/model/associations.rb, line 308 308: def eager_loading_predicate_key 309: predicate_key 310: end
Whether additional conditions should be added when using the filter by associations support.
# 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.
# 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.
# 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.
# 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).
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# File lib/sequel/model/associations.rb, line 436 436: def reciprocal_array? 437: true 438: end
Name symbol for the remove_all_ association method
# 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.
# File lib/sequel/model/associations.rb, line 447 447: def remove_before_destroy? 448: true 449: end
Name symbol for the remove_ association method
# 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.
# 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.
# File lib/sequel/model/associations.rb, line 463 463: def returns_array? 464: true 465: end
Whether to set the reciprocal association to self when loading associated records, false by default.
# 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
# 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.
# 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