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
Public Instance
- _add_method
- _remove_all_method
- _remove_method
- _setter_method
- add_method
- apply_dataset_changes
- apply_distinct_on_eager_limit_strategy
- apply_eager_dataset_changes
- apply_eager_graph_limit_strategy
- apply_eager_limit_strategy
- apply_ruby_eager_limit_strategy
- apply_window_function_eager_limit_strategy
- assign_singular?
- associated_class
- associated_dataset
- association_dataset_for
- association_dataset_proc
- association_method
- can_have_associated_objects?
- cloneable?
- dataset_method
- dataset_need_primary_key?
- delete_row_number_column
- eager_graph_lazy_dataset?
- eager_graph_limit_strategy
- eager_limit_strategy
- eager_load_results
- eager_loader_key
- eager_loading_use_associated_key?
- filter_by_associations_add_conditions?
- filter_by_associations_conditions_expression
- finalize
- finalize_settings
- handle_silent_modification_failure?
- hash
- initialize_association_cache
- inspect
- limit_and_offset
- need_associated_primary_key?
- placeholder_loader
- predicate_key_values
- predicate_keys
- qualify
- qualify_assoc
- qualify_cur
- reciprocal
- reciprocal_array?
- remove_all_method
- remove_before_destroy?
- remove_method
- remove_should_check_existing?
- returns_array?
- select
- set_reciprocal_to_self?
- setter_method
- slice_range
Included modules
Constants
| ASSOCIATION_DATASET_PROC | = | proc{|r| r.association_dataset_for(self)} | ||
| FINALIZE_SETTINGS | = | { :associated_class=>:class, :associated_dataset=>:_dataset, :eager_limit_strategy=>:_eager_limit_strategy, :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
# File lib/sequel/model/associations.rb 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 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 46 def _remove_method 47 self[:_remove_method] 48 end
Name symbol for the _setter association method
# File lib/sequel/model/associations.rb 51 def _setter_method 52 self[:_setter_method] 53 end
Name symbol for the add association method
# File lib/sequel/model/associations.rb 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 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 140 def apply_distinct_on_eager_limit_strategy(ds) 141 keys = predicate_key 142 ds.distinct(*keys).order_prepend(*keys) 143 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 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 113 def apply_eager_graph_limit_strategy(strategy, ds) 114 case strategy 115 when :lateral_subquery 116 apply_lateral_subquery_eager_graph_limit_strategy(ds) 117 when :distinct_on 118 apply_distinct_on_eager_limit_strategy(ds.order_prepend(*self[:order])) 119 when :window_function 120 apply_window_function_eager_limit_strategy(ds.order_prepend(*self[:order])).select(*ds.columns) 121 else 122 ds 123 end 124 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 128 def apply_eager_limit_strategy(ds, strategy=eager_limit_strategy, limit_and_offset=limit_and_offset()) 129 case strategy 130 when :distinct_on 131 apply_distinct_on_eager_limit_strategy(ds) 132 when :window_function 133 apply_window_function_eager_limit_strategy(ds, limit_and_offset) 134 else 135 ds 136 end 137 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 167 def apply_ruby_eager_limit_strategy(rows, limit_and_offset = limit_and_offset()) 168 name = self[:name] 169 return unless range = slice_range(limit_and_offset) 170 if returns_array? 171 rows.each{|o| o.associations[name] = o.associations[name][range] || []} 172 else 173 offset = range.begin 174 rows.each{|o| o.associations[name] = o.associations[name][offset]} 175 end 176 end
Use a window function to limit the results of the eager loading dataset.
# File lib/sequel/model/associations.rb 146 def apply_window_function_eager_limit_strategy(ds, limit_and_offset=limit_and_offset()) 147 rn = ds.row_number_column 148 limit, offset = limit_and_offset 149 ds = ds.unordered.select_append{|o| o.row_number.function.over(:partition=>predicate_key, :order=>ds.opts[:order]).as(rn)}.from_self 150 ds = ds.order(rn) if ds.db.database_type == :mysql 151 ds = if !returns_array? 152 ds.where(rn => offset ? offset+1 : 1) 153 elsif offset 154 offset += 1 155 if limit 156 ds.where(rn => (offset...(offset+limit))) 157 else 158 ds.where{SQL::Identifier.new(rn) >= offset} 159 end 160 else 161 ds.where{SQL::Identifier.new(rn) <= limit} 162 end 163 end
Whether the associations cache should use an array when storing the associated records during eager loading.
# File lib/sequel/model/associations.rb 180 def assign_singular? 181 !returns_array? 182 end
The class associated to the current model class via this association
# File lib/sequel/model/associations.rb 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 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 217 def association_dataset_for(object) 218 condition = if can_have_associated_objects?(object) 219 predicate_keys.zip(predicate_key_values(object)) 220 else 221 false 222 end 223 224 associated_dataset.where(condition) 225 end
Proc used to create the association dataset method.
# File lib/sequel/model/associations.rb 229 def association_dataset_proc 230 ASSOCIATION_DATASET_PROC 231 end
Name symbol for association method, the same as the name of the association.
# File lib/sequel/model/associations.rb 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 187 def can_have_associated_objects?(obj) 188 true 189 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 193 def cloneable?(ref) 194 ref[:type] == self[:type] 195 end
Name symbol for the dataset association method
# File lib/sequel/model/associations.rb 198 def dataset_method 199 self[:dataset_method] 200 end
Whether the dataset needs a primary key to function, true by default.
# File lib/sequel/model/associations.rb 203 def dataset_need_primary_key? 204 true 205 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 209 def delete_row_number_column(ds=associated_dataset) 210 if eager_limit_strategy == :window_function 211 ds.row_number_column 212 end 213 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 361 def eager_graph_lazy_dataset? 362 true 363 end
The eager_graph limit strategy to use for this dataset
# File lib/sequel/model/associations.rb 234 def eager_graph_limit_strategy(strategy) 235 if self[:limit] || !returns_array? 236 strategy = strategy[self[:name]] if strategy.is_a?(Hash) 237 case strategy 238 when true 239 true_eager_graph_limit_strategy 240 when Symbol 241 strategy 242 else 243 if returns_array? || offset 244 :ruby 245 end 246 end 247 end 248 end
The eager limit strategy to use for this dataset.
# File lib/sequel/model/associations.rb 251 def eager_limit_strategy 252 cached_fetch(:_eager_limit_strategy) do 253 if self[:limit] || !returns_array? 254 case s = cached_fetch(:eager_limit_strategy){default_eager_limit_strategy} 255 when true 256 true_eager_limit_strategy 257 else 258 s 259 end 260 end 261 end 262 end
Eager load the associated objects using the hash of eager options, yielding each row to the block.
# File lib/sequel/model/associations.rb 266 def eager_load_results(eo, &block) 267 rows = eo[:rows] 268 unless eo[:initialize_rows] == false 269 Sequel.synchronize_with(eo[:mutex]){initialize_association_cache(rows)} 270 end 271 if eo[:id_map] 272 ids = eo[:id_map].keys 273 return ids if ids.empty? 274 end 275 strategy = eager_limit_strategy 276 cascade = eo[:associations] 277 eager_limit = nil 278 279 if eo[:no_results] 280 no_results = true 281 elsif eo[:eager_block] || eo[:loader] == false || !use_placeholder_loader? 282 ds = eager_loading_dataset(eo) 283 284 strategy = ds.opts[:eager_limit_strategy] || strategy 285 286 eager_limit = 287 if el = ds.opts[:eager_limit] 288 raise Error, "The :eager_limit dataset option is not supported for associations returning a single record" unless returns_array? 289 strategy ||= true_eager_graph_limit_strategy 290 if el.is_a?(Array) 291 el 292 else 293 [el, nil] 294 end 295 else 296 limit_and_offset 297 end 298 299 strategy = true_eager_graph_limit_strategy if strategy == :union 300 # Correlated subqueries are not supported for regular eager loading 301 strategy = :ruby if strategy == :correlated_subquery 302 strategy = nil if strategy == :ruby && assign_singular? 303 304 objects = if strategy == :lateral_subquery 305 apply_lateral_subquery_eager_limit_strategy(ds, ids, eager_limit).all 306 else 307 apply_eager_limit_strategy(ds, strategy, eager_limit).all 308 end 309 310 if strategy == :window_function 311 delete_rn = ds.row_number_column 312 objects.each{|obj| obj.remove_key!(delete_rn)} 313 end 314 elsif strategy == :union 315 objects = [] 316 ds = associated_dataset 317 loader = union_eager_loader 318 joiner = " UNION ALL " 319 ids.each_slice(subqueries_per_union).each do |slice| 320 sql = loader.send(:sql_origin) 321 join = false 322 slice.each do |k| 323 if join 324 sql << joiner 325 else 326 join = true 327 end 328 loader.append_sql(sql, *k) 329 end 330 objects.concat(ds.with_sql(sql.freeze).to_a) 331 end 332 ds = ds.eager(cascade) if cascade 333 ds.send(:post_load, objects) 334 else 335 loader = placeholder_eager_loader 336 loader = loader.with_dataset{|dataset| dataset.eager(cascade)} if cascade 337 objects = loader.all(ids) 338 end 339 340 Sequel.synchronize_with(eo[:mutex]){objects.each(&block)} unless no_results 341 342 if strategy == :ruby 343 apply_ruby_eager_limit_strategy(rows, eager_limit || limit_and_offset) 344 end 345 end
The key to use for the key hash when eager loading
# File lib/sequel/model/associations.rb 348 def eager_loader_key 349 self[:eager_loader_key] 350 end
By default associations do not need to select a key in an associated table to eagerly load.
# File lib/sequel/model/associations.rb 354 def eager_loading_use_associated_key? 355 false 356 end
Whether additional conditions should be added when using the filter by associations support.
# File lib/sequel/model/associations.rb 367 def filter_by_associations_add_conditions? 368 self[:conditions] || self[:eager_block] || self[:limit] 369 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 375 def filter_by_associations_conditions_expression(obj) 376 ds = if filter_by_associations_limit_strategy == :lateral_subquery 377 apply_lateral_subquery_filter_limit_strategy(associated_eager_dataset, obj) 378 else 379 filter_by_associations_conditions_dataset.where(filter_by_associations_conditions_subquery_conditions(obj)) 380 end 381 382 {filter_by_associations_conditions_key=>ds} 383 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 388 def finalize 389 return unless cache = self[:cache] 390 391 finalizer = proc do |meth, key| 392 next if has_key?(key) 393 394 # Allow calling private methods to make sure caching is done appropriately 395 send(meth) 396 self[key] = cache.delete(key) if cache.has_key?(key) 397 end 398 399 finalize_settings.each(&finalizer) 400 401 unless self[:instance_specific] 402 finalizer.call(:associated_eager_dataset, :associated_eager_dataset) 403 finalizer.call(:filter_by_associations_conditions_dataset, :filter_by_associations_conditions_dataset) 404 end 405 406 nil 407 end
# File lib/sequel/model/associations.rb 419 def finalize_settings 420 FINALIZE_SETTINGS 421 end
Whether to handle silent modification failure when adding/removing associated records, false by default.
# File lib/sequel/model/associations.rb 425 def handle_silent_modification_failure? 426 false 427 end
Hash value for the association reflection. This is precomputed to avoid concurrency issues at runtime.
# File lib/sequel/model/associations.rb 431 def hash 432 self[:_hash] 433 end
Initialize the associations cache for the current association for the given objects.
# File lib/sequel/model/associations.rb 436 def initialize_association_cache(objects) 437 name = self[:name] 438 if assign_singular? 439 objects.each{|object| object.associations[name] = nil} 440 else 441 objects.each{|object| object.associations[name] = []} 442 end 443 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 447 def inspect 448 o = self[:orig_opts].dup 449 o.delete(:class) 450 o.delete(:class_name) 451 o.delete(:block) unless o[:block] 452 o[:class] = self[:orig_class] if self[:orig_class] 453 454 "#<#{self.class} #{self[:model]}.#{self[:type]} #{self[:name].inspect}#{", #{o.inspect[1...-1]}" unless o.empty?}>" 455 end
The limit and offset for this association (returned as a two element array).
# File lib/sequel/model/associations.rb 458 def limit_and_offset 459 if (v = self[:limit]).is_a?(Array) 460 v 461 else 462 [v, nil] 463 end 464 end
Whether the associated object needs a primary key to be added/removed, false by default.
# File lib/sequel/model/associations.rb 468 def need_associated_primary_key? 469 false 470 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 474 def placeholder_loader 475 if use_placeholder_loader? 476 cached_fetch(:placeholder_loader) do 477 associated_dataset.placeholder_literalizer_loader do |pl, ds| 478 ds = ds.where(Sequel.&(*predicate_keys.map{|k| SQL::BooleanExpression.new(:'=', k, pl.arg)})) 479 if self[:block] 480 ds = self[:block].call(ds) 481 end 482 ds 483 end 484 end 485 end 486 end
The values that predicate_keys should match for objects to be associated.
# File lib/sequel/model/associations.rb 494 def predicate_key_values(object) 495 predicate_key_methods.map{|k| object.get_column_value(k)} 496 end
The keys to use for loading of the regular dataset, as an array.
# File lib/sequel/model/associations.rb 489 def predicate_keys 490 cached_fetch(:predicate_keys){Array(predicate_key)} 491 end
Qualify col with the given table name.
# File lib/sequel/model/associations.rb 499 def qualify(table, col) 500 transform(col) do |k| 501 case k 502 when Symbol, SQL::Identifier 503 SQL::QualifiedIdentifier.new(table, k) 504 else 505 Sequel::Qualifier.new(table).transform(k) 506 end 507 end 508 end
Qualify col with the associated model’s table name.
# File lib/sequel/model/associations.rb 511 def qualify_assoc(col) 512 qualify(associated_class.table_name, col) 513 end
Qualify col with the current model’s table name.
# File lib/sequel/model/associations.rb 516 def qualify_cur(col) 517 qualify(self[:model].table_name, col) 518 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 526 def reciprocal 527 cached_fetch(:reciprocal) do 528 possible_recips = [] 529 530 associated_class.all_association_reflections.each do |assoc_reflect| 531 if reciprocal_association?(assoc_reflect) 532 possible_recips << assoc_reflect 533 end 534 end 535 536 if possible_recips.length == 1 537 cached_set(:reciprocal_type, possible_recips.first[:type]) if ambiguous_reciprocal_type? 538 possible_recips.first[:name] 539 end 540 end 541 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 545 def reciprocal_array? 546 true 547 end
Name symbol for the remove_all_ association method
# File lib/sequel/model/associations.rb 550 def remove_all_method 551 self[:remove_all_method] 552 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 556 def remove_before_destroy? 557 true 558 end
Name symbol for the remove_ association method
# File lib/sequel/model/associations.rb 561 def remove_method 562 self[:remove_method] 563 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 566 def remove_should_check_existing? 567 false 568 end
Whether this association returns an array of objects instead of a single object, true by default.
# File lib/sequel/model/associations.rb 572 def returns_array? 573 true 574 end
The columns to select when loading the association.
# File lib/sequel/model/associations.rb 577 def select 578 self[:select] 579 end
Whether to set the reciprocal association to self when loading associated records, false by default.
# File lib/sequel/model/associations.rb 583 def set_reciprocal_to_self? 584 false 585 end
Name symbol for the setter association method
# File lib/sequel/model/associations.rb 588 def setter_method 589 self[:setter_method] 590 end
The range used for slicing when using the :ruby eager limit strategy.
# File lib/sequel/model/associations.rb 593 def slice_range(limit_and_offset = limit_and_offset()) 594 limit, offset = limit_and_offset 595 if limit || offset 596 (offset||0)..(limit ? (offset||0)+limit-1 : -1) 597 end 598 end