sequel 3.25.0 → 3.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. data/CHANGELOG +28 -0
  2. data/README.rdoc +3 -3
  3. data/Rakefile +17 -11
  4. data/doc/release_notes/3.26.0.txt +88 -0
  5. data/lib/sequel/adapters/ado.rb +10 -0
  6. data/lib/sequel/adapters/do.rb +12 -0
  7. data/lib/sequel/adapters/jdbc.rb +6 -6
  8. data/lib/sequel/adapters/mysql.rb +8 -2
  9. data/lib/sequel/adapters/mysql2.rb +5 -1
  10. data/lib/sequel/adapters/odbc.rb +10 -2
  11. data/lib/sequel/adapters/oracle.rb +5 -1
  12. data/lib/sequel/adapters/postgres.rb +10 -4
  13. data/lib/sequel/adapters/shared/access.rb +11 -0
  14. data/lib/sequel/adapters/shared/oracle.rb +0 -4
  15. data/lib/sequel/adapters/shared/postgres.rb +0 -12
  16. data/lib/sequel/adapters/tinytds.rb +9 -0
  17. data/lib/sequel/connection_pool.rb +1 -1
  18. data/lib/sequel/connection_pool/threaded.rb +3 -2
  19. data/lib/sequel/core.rb +1 -1
  20. data/lib/sequel/database/connecting.rb +3 -3
  21. data/lib/sequel/database/dataset.rb +1 -1
  22. data/lib/sequel/database/dataset_defaults.rb +1 -1
  23. data/lib/sequel/database/logging.rb +1 -1
  24. data/lib/sequel/database/misc.rb +23 -6
  25. data/lib/sequel/database/query.rb +16 -15
  26. data/lib/sequel/database/schema_methods.rb +21 -16
  27. data/lib/sequel/dataset/actions.rb +19 -16
  28. data/lib/sequel/dataset/features.rb +8 -2
  29. data/lib/sequel/dataset/graph.rb +1 -1
  30. data/lib/sequel/dataset/misc.rb +29 -9
  31. data/lib/sequel/dataset/mutation.rb +3 -3
  32. data/lib/sequel/dataset/prepared_statements.rb +11 -11
  33. data/lib/sequel/dataset/query.rb +28 -7
  34. data/lib/sequel/dataset/sql.rb +2 -2
  35. data/lib/sequel/extensions/migration.rb +1 -0
  36. data/lib/sequel/model.rb +5 -4
  37. data/lib/sequel/model/associations.rb +487 -328
  38. data/lib/sequel/model/base.rb +43 -26
  39. data/lib/sequel/model/exceptions.rb +2 -0
  40. data/lib/sequel/plugins/identity_map.rb +111 -4
  41. data/lib/sequel/plugins/sharding.rb +12 -20
  42. data/lib/sequel/plugins/xml_serializer.rb +2 -2
  43. data/lib/sequel/version.rb +1 -1
  44. data/spec/adapters/postgres_spec.rb +0 -6
  45. data/spec/core/connection_pool_spec.rb +6 -0
  46. data/spec/core/database_spec.rb +12 -0
  47. data/spec/core/schema_spec.rb +9 -2
  48. data/spec/extensions/identity_map_spec.rb +162 -0
  49. data/spec/extensions/many_through_many_spec.rb +3 -19
  50. data/spec/extensions/xml_serializer_spec.rb +4 -4
  51. data/spec/model/eager_loading_spec.rb +7 -21
  52. data/spec/model/record_spec.rb +23 -0
  53. metadata +36 -34
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Dataset
3
3
  # ---------------------
4
- # :section: Methods that describe what the dataset supports
4
+ # :section: 4 - Methods that describe what the dataset supports
5
5
  # These methods all return booleans, with most describing whether or not the
6
6
  # dataset supports a feature.
7
7
  # ---------------------
@@ -11,7 +11,13 @@ module Sequel
11
11
 
12
12
  # Whether this dataset quotes identifiers.
13
13
  def quote_identifiers?
14
- @quote_identifiers
14
+ if defined?(@quote_identifiers)
15
+ @quote_identifiers
16
+ elsif db.respond_to?(:quote_identifiers?)
17
+ @quote_identifiers = db.quote_identifiers?
18
+ else
19
+ @quote_identifiers = false
20
+ end
15
21
  end
16
22
 
17
23
  # Whether this dataset will provide accurate number of rows matched for
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Dataset
3
3
  # ---------------------
4
- # :section: Methods related to dataset graphing
4
+ # :section: 5 - Methods related to dataset graphing
5
5
  # Dataset graphing changes the dataset to yield hashes where keys are table
6
6
  # name symbols and values are hashes representing the columns related to
7
7
  # that table. All of these methods return modified copies of the receiver.
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Dataset
3
3
  # ---------------------
4
- # :section: Miscellaneous methods
4
+ # :section: 6 - Miscellaneous methods
5
5
  # These methods don't fit cleanly into another section.
6
6
  # ---------------------
7
7
 
@@ -23,17 +23,13 @@ module Sequel
23
23
  # DB[:posts]
24
24
  #
25
25
  # Sequel::Dataset is an abstract class that is not useful by itself. Each
26
- # database adaptor provides a subclass of Sequel::Dataset, and has
26
+ # database adapter provides a subclass of Sequel::Dataset, and has
27
27
  # the Database#dataset method return an instance of that subclass.
28
28
  def initialize(db, opts = nil)
29
29
  @db = db
30
- @quote_identifiers = db.quote_identifiers? if db.respond_to?(:quote_identifiers?)
31
- @identifier_input_method = db.identifier_input_method if db.respond_to?(:identifier_input_method)
32
- @identifier_output_method = db.identifier_output_method if db.respond_to?(:identifier_output_method)
33
30
  @opts = opts || {}
34
- @row_proc = nil
35
31
  end
36
-
32
+
37
33
  # Define a hash value such that datasets with the same DB, opts, and SQL
38
34
  # will be consider equal.
39
35
  def ==(o)
@@ -87,10 +83,10 @@ module Sequel
87
83
  # have a table, raises an error. If the table is aliased, returns the original
88
84
  # table, not the alias
89
85
  #
90
- # DB[:table].first_source_alias
86
+ # DB[:table].first_source_table
91
87
  # # => :table
92
88
  #
93
- # DB[:table___t].first_source_alias
89
+ # DB[:table___t].first_source_table
94
90
  # # => :table
95
91
  def first_source_table
96
92
  source = @opts[:from]
@@ -114,6 +110,30 @@ module Sequel
114
110
  [db, opts.sort_by{|k| k.to_s}, sql].hash
115
111
  end
116
112
 
113
+ # The String instance method to call on identifiers before sending them to
114
+ # the database.
115
+ def identifier_input_method
116
+ if defined?(@identifier_input_method)
117
+ @identifier_input_method
118
+ elsif db.respond_to?(:identifier_input_method)
119
+ @identifier_input_method = db.identifier_input_method
120
+ else
121
+ @identifier_input_method = nil
122
+ end
123
+ end
124
+
125
+ # The String instance method to call on identifiers before sending them to
126
+ # the database.
127
+ def identifier_output_method
128
+ if defined?(@identifier_output_method)
129
+ @identifier_output_method
130
+ elsif db.respond_to?(:identifier_output_method)
131
+ @identifier_output_method = db.identifier_output_method
132
+ else
133
+ @identifier_output_method = nil
134
+ end
135
+ end
136
+
117
137
  # Returns a string representation of the dataset including the class name
118
138
  # and the corresponding SQL select statement.
119
139
  def inspect
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Dataset
3
3
  # ---------------------
4
- # :section: Mutation methods
4
+ # :section: 7 - Mutation methods
5
5
  # These methods modify the receiving dataset and should be used with care.
6
6
  # ---------------------
7
7
 
@@ -21,10 +21,10 @@ module Sequel
21
21
  def_mutation_method(*MUTATION_METHODS)
22
22
 
23
23
  # Set the method to call on identifiers going into the database for this dataset
24
- attr_accessor :identifier_input_method
24
+ attr_writer :identifier_input_method
25
25
 
26
26
  # Set the method to call on identifiers coming the database for this dataset
27
- attr_accessor :identifier_output_method
27
+ attr_writer :identifier_output_method
28
28
 
29
29
  # Whether to quote identifiers for this dataset
30
30
  attr_writer :quote_identifiers
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Dataset
3
3
  # ---------------------
4
- # :section: Methods related to prepared statements or bound variables
4
+ # :section: 8 - Methods related to prepared statements or bound variables
5
5
  # On some adapters, these use native prepared statements and bound variables, on others
6
6
  # support is emulated. For details, see the {"Prepared Statements/Bound Variables" guide}[link:files/doc/prepared_statements_rdoc.html].
7
7
  # ---------------------
@@ -199,11 +199,9 @@ module Sequel
199
199
  clone(:bind_vars=>@opts[:bind_vars] ? @opts[:bind_vars].merge(bind_vars) : bind_vars)
200
200
  end
201
201
 
202
- # For the given type (:select, :insert, :update, or :delete),
203
- # run the sql with the bind variables
204
- # specified in the hash. values is a hash of passed to
205
- # insert or update (if one of those types is used),
206
- # which may contain placeholders.
202
+ # For the given type (:select, :first, :insert, :insert_select, :update, or :delete),
203
+ # run the sql with the bind variables specified in the hash. +values+ is a hash passed to
204
+ # insert or update (if one of those types is used), which may contain placeholders.
207
205
  #
208
206
  # DB[:table].filter(:id=>:$id).call(:first, :id=>1)
209
207
  # # SELECT * FROM table WHERE id = ? LIMIT 1 -- (1)
@@ -212,11 +210,13 @@ module Sequel
212
210
  prepare(type, nil, *values).call(bind_variables, &block)
213
211
  end
214
212
 
215
- # Prepare an SQL statement for later execution. This returns
216
- # a clone of the dataset extended with PreparedStatementMethods,
217
- # on which you can call call with the hash of bind variables to
218
- # do substitution. The prepared statement is also stored in
219
- # the associated database. The following usage is identical:
213
+ # Prepare an SQL statement for later execution. Takes a type similar to #call,
214
+ # and the name symbol of the prepared statement.
215
+ # This returns a clone of the dataset extended with PreparedStatementMethods,
216
+ # which you can +call+ with the hash of bind variables to use.
217
+ # The prepared statement is also stored in
218
+ # the associated database, where it can be called by name.
219
+ # The following usage is identical:
220
220
  #
221
221
  # ps = DB[:table].filter(:name=>:$name).prepare(:first, :select_by_name)
222
222
  #
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Dataset
3
3
  # ---------------------
4
- # :section: Methods that return modified datasets
4
+ # :section: 1 - Methods that return modified datasets
5
5
  # These methods all return modified copies of the receiver.
6
6
  # ---------------------
7
7
 
@@ -80,7 +80,7 @@ module Sequel
80
80
  # :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
81
81
  #
82
82
  # DB[:items].except(DB[:other_items])
83
- # # SELECT * FROM items EXCEPT SELECT * FROM other_items
83
+ # # SELECT * FROM (SELECT * FROM items EXCEPT SELECT * FROM other_items) AS t1
84
84
  #
85
85
  # DB[:items].except(DB[:other_items], :all=>true, :from_self=>false)
86
86
  # # SELECT * FROM items EXCEPT ALL SELECT * FROM other_items
@@ -381,7 +381,11 @@ module Sequel
381
381
  inner_join(*args, &block)
382
382
  end
383
383
 
384
- # Returns a joined dataset. Uses the following arguments:
384
+ # Returns a joined dataset. Not usually called directly, users should use the
385
+ # appropriate join method (e.g. join, left_join, natural_join, cross_join) which fills
386
+ # in the +type+ argument.
387
+ #
388
+ # Takes the following arguments:
385
389
  #
386
390
  # * type - The type of join to do (e.g. :inner)
387
391
  # * table - Depends on type:
@@ -411,6 +415,23 @@ module Sequel
411
415
  # in which case it yields the table alias/name for the table currently being joined,
412
416
  # the table alias/name for the last joined (or first table), and an array of previous
413
417
  # SQL::JoinClause. Unlike +filter+, this block is not treated as a virtual row block.
418
+ #
419
+ # Examples:
420
+ #
421
+ # DB[:a].join_table(:cross, :b)
422
+ # # SELECT * FROM a CROSS JOIN b
423
+ #
424
+ # DB[:a].join_table(:inner, DB[:b], :c=>d)
425
+ # # SELECT * FROM a INNER JOIN (SELECT * FROM b) AS t1 ON (t1.c = a.d)
426
+ #
427
+ # DB[:a].join_table(:left, :b___c, [:d])
428
+ # # SELECT * FROM a LEFT JOIN b AS c USING (d)
429
+ #
430
+ # DB[:a].natural_join(:b).join_table(:inner, :c) do |ta, jta, js|
431
+ # (:d.qualify(ta) > :e.qualify(jta)) & {:f.qualify(ta)=>DB.from(js.first.table).select(:g)}
432
+ # end
433
+ # # SELECT * FROM a NATURAL JOIN b INNER JOIN c
434
+ # # ON ((c.d > b.e) AND (c.f IN (SELECT g FROM b)))
414
435
  def join_table(type, table, expr=nil, options={}, &block)
415
436
  using_join = expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)}
416
437
  if using_join && !supports_join_using?
@@ -591,7 +612,7 @@ module Sequel
591
612
  @opts[:order] ? ds.order_more(*@opts[:order]) : ds
592
613
  end
593
614
 
594
- # Qualify to the given table, or first source if not table is given.
615
+ # Qualify to the given table, or first source if no table is given.
595
616
  #
596
617
  # DB[:items].filter(:id=>1).qualify
597
618
  # # SELECT items.* FROM items WHERE (items.id = 1)
@@ -718,7 +739,7 @@ module Sequel
718
739
  end
719
740
 
720
741
  # Set the server for this dataset to use. Used to pick a specific database
721
- # shard to run a query against, or to override the default (which is SELECT uses
742
+ # shard to run a query against, or to override the default (where SELECT uses
722
743
  # :read_only database and all other queries use the :default database). This
723
744
  # method is always available but is only useful when database sharding is being
724
745
  # used.
@@ -790,8 +811,8 @@ module Sequel
790
811
  # :all :: Set to true to use UNION ALL instead of UNION, so duplicate rows can occur
791
812
  # :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
792
813
  #
793
- # DB[:items].union(DB[:other_items]).sql
794
- # #=> "SELECT * FROM items UNION SELECT * FROM other_items"
814
+ # DB[:items].union(DB[:other_items])
815
+ # # SELECT * FROM (SELECT * FROM items UNION SELECT * FROM other_items) AS t1
795
816
  #
796
817
  # DB[:items].union(DB[:other_items], :all=>true, :from_self=>false)
797
818
  # # SELECT * FROM items UNION ALL SELECT * FROM other_items
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Dataset
3
3
  # ---------------------
4
- # :section: User Methods relating to SQL Creation
4
+ # :section: 3 - User Methods relating to SQL Creation
5
5
  # These are methods you can call to see what SQL will be generated by the dataset.
6
6
  # ---------------------
7
7
 
@@ -163,7 +163,7 @@ module Sequel
163
163
  end
164
164
 
165
165
  # ---------------------
166
- # :section: Internal Methods relating to SQL Creation
166
+ # :section: 9 - Internal Methods relating to SQL Creation
167
167
  # These methods, while public, are not designed to be used directly by the end user.
168
168
  # ---------------------
169
169
 
@@ -2,6 +2,7 @@
2
2
  # the user to easily group schema changes and migrate the database
3
3
  # to a newer version or revert to a previous version.
4
4
 
5
+ #
5
6
  module Sequel
6
7
  # Sequel's older migration class, available for backward compatibility.
7
8
  # Uses subclasses with up and down instance methods for each migration:
data/lib/sequel/model.rb CHANGED
@@ -49,10 +49,11 @@ module Sequel
49
49
  # called directly on the class. Model datasets return rows as model instances,
50
50
  # which have fairly standard ORM instance behavior.
51
51
  #
52
- # <tt>Sequel::Model</tt> is built completely out of plugins, the only method not part of a
53
- # plugin is the plugin method itself. Plugins can override any class, instance, or
54
- # dataset method defined by a previous plugin and call super to get the default
55
- # behavior.
52
+ # <tt>Sequel::Model</tt> is built completely out of plugins. Plugins can override any class,
53
+ # instance, or dataset method defined by a previous plugin and call super to get the default
54
+ # behavior. By default, <tt>Sequel::Model</tt> loads two plugins, <tt>Sequel::Model</tt>
55
+ # (which is itself a plugin) for the base support, and <tt>Sequel::Model::Associations</tt>
56
+ # for the associations support.
56
57
  #
57
58
  # You can set the +SEQUEL_NO_ASSOCIATIONS+ constant or environment variable to
58
59
  # make Sequel not load the associations plugin by default.
@@ -472,161 +472,165 @@ module Sequel
472
472
  # Associates a related model with the current model. The following types are
473
473
  # supported:
474
474
  #
475
- # * :many_to_one - Foreign key in current model's table points to
476
- # associated model's primary key. Each associated model object can
477
- # be associated with more than one current model objects. Each current
478
- # model object can be associated with only one associated model object.
479
- # * :one_to_many - Foreign key in associated model's table points to this
480
- # model's primary key. Each current model object can be associated with
481
- # more than one associated model objects. Each associated model object
482
- # can be associated with only one current model object.
483
- # * :one_to_one - Similar to one_to_many in terms of foreign keys, but
484
- # only one object is associated to the current object through the
485
- # association. The methods created are similar to many_to_one, except
486
- # that the one_to_one setter method saves the passed object.
487
- # * :many_to_many - A join table is used that has a foreign key that points
488
- # to this model's primary key and a foreign key that points to the
489
- # associated model's primary key. Each current model object can be
490
- # associated with many associated model objects, and each associated
491
- # model object can be associated with many current model objects.
475
+ # :many_to_one :: Foreign key in current model's table points to
476
+ # associated model's primary key. Each associated model object can
477
+ # be associated with more than one current model objects. Each current
478
+ # model object can be associated with only one associated model object.
479
+ # :one_to_many :: Foreign key in associated model's table points to this
480
+ # model's primary key. Each current model object can be associated with
481
+ # more than one associated model objects. Each associated model object
482
+ # can be associated with only one current model object.
483
+ # :one_to_one :: Similar to one_to_many in terms of foreign keys, but
484
+ # only one object is associated to the current object through the
485
+ # association. The methods created are similar to many_to_one, except
486
+ # that the one_to_one setter method saves the passed object.
487
+ # :many_to_many :: A join table is used that has a foreign key that points
488
+ # to this model's primary key and a foreign key that points to the
489
+ # associated model's primary key. Each current model object can be
490
+ # associated with many associated model objects, and each associated
491
+ # model object can be associated with many current model objects.
492
492
  #
493
493
  # The following options can be supplied:
494
- # * *ALL types*:
495
- # - :after_add - Symbol, Proc, or array of both/either specifying a callback to call
496
- # after a new item is added to the association.
497
- # - :after_load - Symbol, Proc, or array of both/either specifying a callback to call
498
- # after the associated record(s) have been retrieved from the database. Not called
499
- # when eager loading via eager_graph, but called when eager loading via eager.
500
- # - :after_remove - Symbol, Proc, or array of both/either specifying a callback to call
501
- # after an item is removed from the association.
502
- # - :after_set - Symbol, Proc, or array of both/either specifying a callback to call
503
- # after an item is set using the association setter method.
504
- # - :allow_eager - If set to false, you cannot load the association eagerly
505
- # via eager or eager_graph
506
- # - :before_add - Symbol, Proc, or array of both/either specifying a callback to call
507
- # before a new item is added to the association.
508
- # - :before_remove - Symbol, Proc, or array of both/either specifying a callback to call
509
- # before an item is removed from the association.
510
- # - :before_set - Symbol, Proc, or array of both/either specifying a callback to call
511
- # before an item is set using the association setter method.
512
- # - :cartesian_product_number - the number of joins completed by this association that could cause more
513
- # than one row for each row in the current table (default: 0 for many_to_one and one_to_one associations,
514
- # 1 for one_to_many and many_to_many associations).
515
- # - :class - The associated class or its name. If not
516
- # given, uses the association's name, which is camelized (and
517
- # singularized unless the type is :many_to_one or :one_to_one)
518
- # - :clone - Merge the current options and block into the options and block used in defining
519
- # the given association. Can be used to DRY up a bunch of similar associations that
520
- # all share the same options such as :class and :key, while changing the order and block used.
521
- # - :conditions - The conditions to use to filter the association, can be any argument passed to filter.
522
- # - :dataset - A proc that is instance_evaled to get the base dataset
523
- # to use for the _dataset method (before the other options are applied).
524
- # - :distinct - Use the DISTINCT clause when selecting associating object, both when
525
- # lazy loading and eager loading via .eager (but not when using .eager_graph).
526
- # - :eager - The associations to eagerly load via +eager+ when loading the associated object(s).
527
- # - :eager_block - If given, use the block instead of the default block when
528
- # eagerly loading. To not use a block when eager loading (when one is used normally),
529
- # set to nil.
530
- # - :eager_graph - The associations to eagerly load via +eager_graph+ when loading the associated object(s).
531
- # - :eager_grapher - A proc to use to implement eager loading via +eager_graph+, overriding the default.
532
- # Takes one or three arguments. If three arguments, they are a dataset, an alias to use for
533
- # the table to graph for this association, and the alias that was used for the current table
534
- # (since you can cascade associations). If one argument, is passed a hash with keys :self,
535
- # :table_alias, and :implicit_qualifier, corresponding to the three arguments, and an optional
536
- # additional key :eager_block, a callback accepting one argument, the associated dataset. This
537
- # is used to customize the association at query time.
538
- # Should return a copy of the dataset with the association graphed into it.
539
- # - :eager_loader - A proc to use to implement eager loading, overriding the default. Takes one or three arguments.
540
- # If three arguments, the first should be a key hash (used solely to enhance performance), the second an array of records,
541
- # and the third a hash of dependent associations. If one argument, is passed a hash with keys :key_hash,
542
- # :rows, and :associations, corresponding to the three arguments, and an additional key :self, which is
543
- # the dataset doing the eager loading. In the proc, the associated records should
544
- # be queried from the database and the associations cache for each
545
- # record should be populated.
546
- # - :eager_loader_key - A symbol for the key column to use to populate the key hash
547
- # for the eager loader.
548
- # - :extend - A module or array of modules to extend the dataset with.
549
- # - :graph_block - The block to pass to join_table when eagerly loading
550
- # the association via +eager_graph+.
551
- # - :graph_conditions - The additional conditions to use on the SQL join when eagerly loading
552
- # the association via +eager_graph+. Should be a hash or an array of two element arrays. If not
553
- # specified, the :conditions option is used if it is a hash or array of two element arrays.
554
- # - :graph_join_type - The type of SQL join to use when eagerly loading the association via
555
- # eager_graph. Defaults to :left_outer.
556
- # - :graph_only_conditions - The conditions to use on the SQL join when eagerly loading
557
- # the association via +eager_graph+, instead of the default conditions specified by the
558
- # foreign/primary keys. This option causes the :graph_conditions option to be ignored.
559
- # - :graph_select - A column or array of columns to select from the associated table
560
- # when eagerly loading the association via +eager_graph+. Defaults to all
561
- # columns in the associated table.
562
- # - :limit - Limit the number of records to the provided value. Use
563
- # an array with two elements for the value to specify a limit (first element) and an offset (second element).
564
- # - :methods_module - The module that methods the association creates will be placed into. Defaults
565
- # to the module containing the model's columns.
566
- # - :order - the column(s) by which to order the association dataset. Can be a
567
- # singular column symbol or an array of column symbols.
568
- # - :order_eager_graph - Whether to add the association's order to the graphed dataset's order when graphing
569
- # via +eager_graph+. Defaults to true, so set to false to disable.
570
- # - :read_only - Do not add a setter method (for many_to_one or one_to_one associations),
571
- # or add_/remove_/remove_all_ methods (for one_to_many and many_to_many associations).
572
- # - :reciprocal - the symbol name of the reciprocal association,
573
- # if it exists. By default, Sequel will try to determine it by looking at the
574
- # associated model's assocations for a association that matches
575
- # the current association's key(s). Set to nil to not use a reciprocal.
576
- # - :select - the columns to select. Defaults to the associated class's
577
- # table_name.* in a many_to_many association, which means it doesn't include the attributes from the
578
- # join table. If you want to include the join table attributes, you can
579
- # use this option, but beware that the join table attributes can clash with
580
- # attributes from the model table, so you should alias any attributes that have
581
- # the same name in both the join table and the associated table.
582
- # - :validate - Set to false to not validate when implicitly saving any associated object.
583
- # * :many_to_one:
584
- # - :key - foreign_key in current model's table that references
585
- # associated model's primary key, as a symbol. Defaults to :"#{name}_id". Can use an
586
- # array of symbols for a composite key association.
587
- # - :primary_key - column in the associated table that :key option references, as a symbol.
588
- # Defaults to the primary key of the associated table. Can use an
589
- # array of symbols for a composite key association.
590
- # * :one_to_many and :one_to_one:
591
- # - :key - foreign key in associated model's table that references
592
- # current model's primary key, as a symbol. Defaults to
593
- # :"#{self.name.underscore}_id". Can use an
594
- # array of symbols for a composite key association.
595
- # - :primary_key - column in the current table that :key option references, as a symbol.
596
- # Defaults to primary key of the current table. Can use an
597
- # array of symbols for a composite key association.
598
- # * :many_to_many:
599
- # - :graph_join_table_block - The block to pass to +join_table+ for
600
- # the join table when eagerly loading the association via +eager_graph+.
601
- # - :graph_join_table_conditions - The additional conditions to use on the SQL join for
602
- # the join table when eagerly loading the association via +eager_graph+. Should be a hash
603
- # or an array of two element arrays.
604
- # - :graph_join_table_join_type - The type of SQL join to use for the join table when eagerly
605
- # loading the association via +eager_graph+. Defaults to the :graph_join_type option or
606
- # :left_outer.
607
- # - :graph_join_table_only_conditions - The conditions to use on the SQL join for the join
608
- # table when eagerly loading the association via +eager_graph+, instead of the default
609
- # conditions specified by the foreign/primary keys. This option causes the
610
- # :graph_join_table_conditions option to be ignored.
611
- # - :join_table - name of table that includes the foreign keys to both
612
- # the current model and the associated model, as a symbol. Defaults to the name
613
- # of current model and name of associated model, pluralized,
614
- # underscored, sorted, and joined with '_'.
615
- # - :join_table_block - proc that can be used to modify the dataset used in the add/remove/remove_all
616
- # methods. Should accept a dataset argument and return a modified dataset if present.
617
- # - :left_key - foreign key in join table that points to current model's
618
- # primary key, as a symbol. Defaults to :"#{self.name.underscore}_id".
619
- # Can use an array of symbols for a composite key association.
620
- # - :left_primary_key - column in current table that :left_key points to, as a symbol.
621
- # Defaults to primary key of current table. Can use an
622
- # array of symbols for a composite key association.
623
- # - :right_key - foreign key in join table that points to associated
624
- # model's primary key, as a symbol. Defaults to Defaults to :"#{name.to_s.singularize}_id".
625
- # Can use an array of symbols for a composite key association.
626
- # - :right_primary_key - column in associated table that :right_key points to, as a symbol.
627
- # Defaults to primary key of the associated table. Can use an
628
- # array of symbols for a composite key association.
629
- # - :uniq - Adds a after_load callback that makes the array of objects unique.
494
+ # === All Types
495
+ # :after_add :: Symbol, Proc, or array of both/either specifying a callback to call
496
+ # after a new item is added to the association.
497
+ # :after_load :: Symbol, Proc, or array of both/either specifying a callback to call
498
+ # after the associated record(s) have been retrieved from the database. Not called
499
+ # when eager loading via eager_graph, but called when eager loading via eager.
500
+ # :after_remove :: Symbol, Proc, or array of both/either specifying a callback to call
501
+ # after an item is removed from the association.
502
+ # :after_set :: Symbol, Proc, or array of both/either specifying a callback to call
503
+ # after an item is set using the association setter method.
504
+ # :allow_eager :: If set to false, you cannot load the association eagerly
505
+ # via eager or eager_graph
506
+ # :before_add :: Symbol, Proc, or array of both/either specifying a callback to call
507
+ # before a new item is added to the association.
508
+ # :before_remove :: Symbol, Proc, or array of both/either specifying a callback to call
509
+ # before an item is removed from the association.
510
+ # :before_set :: Symbol, Proc, or array of both/either specifying a callback to call
511
+ # before an item is set using the association setter method.
512
+ # :cartesian_product_number :: the number of joins completed by this association that could cause more
513
+ # than one row for each row in the current table (default: 0 for
514
+ # many_to_one and one_to_one associations, 1 for one_to_many and
515
+ # many_to_many associations).
516
+ # :class :: The associated class or its name. If not
517
+ # given, uses the association's name, which is camelized (and
518
+ # singularized unless the type is :many_to_one or :one_to_one)
519
+ # :clone :: Merge the current options and block into the options and block used in defining
520
+ # the given association. Can be used to DRY up a bunch of similar associations that
521
+ # all share the same options such as :class and :key, while changing the order and block used.
522
+ # :conditions :: The conditions to use to filter the association, can be any argument passed to filter.
523
+ # :dataset :: A proc that is instance_evaled to get the base dataset
524
+ # to use for the _dataset method (before the other options are applied).
525
+ # :distinct :: Use the DISTINCT clause when selecting associating object, both when
526
+ # lazy loading and eager loading via .eager (but not when using .eager_graph).
527
+ # :eager :: The associations to eagerly load via +eager+ when loading the associated object(s).
528
+ # :eager_block :: If given, use the block instead of the default block when
529
+ # eagerly loading. To not use a block when eager loading (when one is used normally),
530
+ # set to nil.
531
+ # :eager_graph :: The associations to eagerly load via +eager_graph+ when loading the associated object(s).
532
+ # many_to_many associations with this option cannot be eagerly loaded via +eager+.
533
+ # :eager_grapher :: A proc to use to implement eager loading via +eager_graph+, overriding the default.
534
+ # Takes one or three arguments. If three arguments, they are a dataset, an alias to use for
535
+ # the table to graph for this association, and the alias that was used for the current table
536
+ # (since you can cascade associations). If one argument, is passed a hash with keys :self,
537
+ # :table_alias, and :implicit_qualifier, corresponding to the three arguments, and an optional
538
+ # additional key :eager_block, a callback accepting one argument, the associated dataset. This
539
+ # is used to customize the association at query time.
540
+ # Should return a copy of the dataset with the association graphed into it.
541
+ # :eager_loader :: A proc to use to implement eager loading, overriding the default. Takes one or three arguments.
542
+ # If three arguments, the first should be a key hash (used solely to enhance performance), the
543
+ # second an array of records, and the third a hash of dependent associations. If one argument, is
544
+ # passed a hash with keys :key_hash, :rows, and :associations, corresponding to the three
545
+ # arguments, and an additional key :self, which is the dataset doing the eager loading. In the
546
+ # proc, the associated records should be queried from the database and the associations cache for
547
+ # each record should be populated.
548
+ # :eager_loader_key :: A symbol for the key column to use to populate the key hash
549
+ # for the eager loader.
550
+ # :extend :: A module or array of modules to extend the dataset with.
551
+ # :graph_block :: The block to pass to join_table when eagerly loading
552
+ # the association via +eager_graph+.
553
+ # :graph_conditions :: The additional conditions to use on the SQL join when eagerly loading
554
+ # the association via +eager_graph+. Should be a hash or an array of two element arrays. If not
555
+ # specified, the :conditions option is used if it is a hash or array of two element arrays.
556
+ # :graph_join_type :: The type of SQL join to use when eagerly loading the association via
557
+ # eager_graph. Defaults to :left_outer.
558
+ # :graph_only_conditions :: The conditions to use on the SQL join when eagerly loading
559
+ # the association via +eager_graph+, instead of the default conditions specified by the
560
+ # foreign/primary keys. This option causes the :graph_conditions option to be ignored.
561
+ # :graph_select :: A column or array of columns to select from the associated table
562
+ # when eagerly loading the association via +eager_graph+. Defaults to all
563
+ # columns in the associated table.
564
+ # :limit :: Limit the number of records to the provided value. Use
565
+ # an array with two elements for the value to specify a
566
+ # limit (first element) and an offset (second element).
567
+ # :methods_module :: The module that methods the association creates will be placed into. Defaults
568
+ # to the module containing the model's columns.
569
+ # :order :: the column(s) by which to order the association dataset. Can be a
570
+ # singular column symbol or an array of column symbols.
571
+ # :order_eager_graph :: Whether to add the association's order to the graphed dataset's order when graphing
572
+ # via +eager_graph+. Defaults to true, so set to false to disable.
573
+ # :read_only :: Do not add a setter method (for many_to_one or one_to_one associations),
574
+ # or add_/remove_/remove_all_ methods (for one_to_many and many_to_many associations).
575
+ # :reciprocal :: the symbol name of the reciprocal association,
576
+ # if it exists. By default, Sequel will try to determine it by looking at the
577
+ # associated model's assocations for a association that matches
578
+ # the current association's key(s). Set to nil to not use a reciprocal.
579
+ # :select :: the columns to select. Defaults to the associated class's
580
+ # table_name.* in a many_to_many association, which means it doesn't include the attributes from the
581
+ # join table. If you want to include the join table attributes, you can
582
+ # use this option, but beware that the join table attributes can clash with
583
+ # attributes from the model table, so you should alias any attributes that have
584
+ # the same name in both the join table and the associated table.
585
+ # :validate :: Set to false to not validate when implicitly saving any associated object.
586
+ # === :many_to_one
587
+ # :key :: foreign key in current model's table that references
588
+ # associated model's primary key, as a symbol. Defaults to :"#{name}_id". Can use an
589
+ # array of symbols for a composite key association.
590
+ # :primary_key :: column in the associated table that :key option references, as a symbol.
591
+ # Defaults to the primary key of the associated table. Can use an
592
+ # array of symbols for a composite key association.
593
+ # === :one_to_many and :one_to_one
594
+ # :key :: foreign key in associated model's table that references
595
+ # current model's primary key, as a symbol. Defaults to
596
+ # :"#{self.name.underscore}_id". Can use an
597
+ # array of symbols for a composite key association.
598
+ # :primary_key :: column in the current table that :key option references, as a symbol.
599
+ # Defaults to primary key of the current table. Can use an
600
+ # array of symbols for a composite key association.
601
+ # === :many_to_many
602
+ # :graph_join_table_block :: The block to pass to +join_table+ for
603
+ # the join table when eagerly loading the association via +eager_graph+.
604
+ # :graph_join_table_conditions :: The additional conditions to use on the SQL join for
605
+ # the join table when eagerly loading the association via +eager_graph+.
606
+ # Should be a hash or an array of two element arrays.
607
+ # :graph_join_table_join_type :: The type of SQL join to use for the join table when eagerly
608
+ # loading the association via +eager_graph+. Defaults to the
609
+ # :graph_join_type option or :left_outer.
610
+ # :graph_join_table_only_conditions :: The conditions to use on the SQL join for the join
611
+ # table when eagerly loading the association via +eager_graph+,
612
+ # instead of the default conditions specified by the
613
+ # foreign/primary keys. This option causes the
614
+ # :graph_join_table_conditions option to be ignored.
615
+ # :join_table :: name of table that includes the foreign keys to both
616
+ # the current model and the associated model, as a symbol. Defaults to the name
617
+ # of current model and name of associated model, pluralized,
618
+ # underscored, sorted, and joined with '_'.
619
+ # :join_table_block :: proc that can be used to modify the dataset used in the add/remove/remove_all
620
+ # methods. Should accept a dataset argument and return a modified dataset if present.
621
+ # :left_key :: foreign key in join table that points to current model's
622
+ # primary key, as a symbol. Defaults to :"#{self.name.underscore}_id".
623
+ # Can use an array of symbols for a composite key association.
624
+ # :left_primary_key :: column in current table that :left_key points to, as a symbol.
625
+ # Defaults to primary key of current table. Can use an
626
+ # array of symbols for a composite key association.
627
+ # :right_key :: foreign key in join table that points to associated
628
+ # model's primary key, as a symbol. Defaults to Defaults to :"#{name.to_s.singularize}_id".
629
+ # Can use an array of symbols for a composite key association.
630
+ # :right_primary_key :: column in associated table that :right_key points to, as a symbol.
631
+ # Defaults to primary key of the associated table. Can use an
632
+ # array of symbols for a composite key association.
633
+ # :uniq :: Adds a after_load callback that makes the array of objects unique.
630
634
  def associate(type, name, opts = {}, &block)
631
635
  raise(Error, 'one_to_many association type with :one_to_one option removed, used one_to_one association type') if opts[:one_to_one] && type == :one_to_many
632
636
  raise(Error, 'invalid association type') unless assoc_class = ASSOCIATION_TYPES[type]
@@ -681,13 +685,13 @@ module Sequel
681
685
  ds = ds.eager(opts[:eager]) if opts[:eager]
682
686
  ds = ds.distinct if opts[:distinct]
683
687
  if opts[:eager_graph]
688
+ raise(Error, "cannot eagerly load a #{opts[:type]} association that uses :eager_graph") if opts.eager_loading_use_associated_key?
684
689
  ds = ds.eager_graph(opts[:eager_graph])
685
- ds = ds.add_graph_aliases(opts.associated_key_alias=>[opts.associated_class.table_name, opts.associated_key_alias, SQL::QualifiedIdentifier.new(opts.associated_key_table, opts.associated_key_column)]) if opts.eager_loading_use_associated_key?
686
690
  end
687
691
  ds = ds.eager(associations) unless Array(associations).empty?
688
692
  ds = opts[:eager_block].call(ds) if opts[:eager_block]
689
693
  ds = eager_options[:eager_block].call(ds) if eager_options[:eager_block]
690
- if !opts[:eager_graph] && opts.eager_loading_use_associated_key?
694
+ if opts.eager_loading_use_associated_key?
691
695
  ds = if opts[:uses_left_composite_keys]
692
696
  t = opts.associated_key_table
693
697
  ds.select_append(*opts.associated_key_alias.zip(opts.associated_key_column).map{|a, c| SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(t, c), a)})
@@ -704,22 +708,22 @@ module Sequel
704
708
  subclass.instance_variable_set(:@association_reflections, @association_reflections.dup)
705
709
  end
706
710
 
707
- # Shortcut for adding a many_to_many association, see associate
711
+ # Shortcut for adding a many_to_many association, see #associate
708
712
  def many_to_many(name, opts={}, &block)
709
713
  associate(:many_to_many, name, opts, &block)
710
714
  end
711
715
 
712
- # Shortcut for adding a many_to_one association, see associate
716
+ # Shortcut for adding a many_to_one association, see #associate
713
717
  def many_to_one(name, opts={}, &block)
714
718
  associate(:many_to_one, name, opts, &block)
715
719
  end
716
720
 
717
- # Shortcut for adding a one_to_many association, see associate
721
+ # Shortcut for adding a one_to_many association, see #associate
718
722
  def one_to_many(name, opts={}, &block)
719
723
  associate(:one_to_many, name, opts, &block)
720
724
  end
721
725
 
722
- # Shortcut for adding a one_to_one association, see associate.
726
+ # Shortcut for adding a one_to_one association, see #associate.
723
727
  def one_to_one(name, opts={}, &block)
724
728
  associate(:one_to_one, name, opts, &block)
725
729
  end
@@ -1285,7 +1289,7 @@ module Sequel
1285
1289
  # time, as it loads associated records using one query per association. However,
1286
1290
  # it does not allow you the ability to filter or order based on columns in associated tables. +eager_graph+ loads
1287
1291
  # all records in a single query using JOINs, allowing you to filter or order based on columns in associated
1288
- # tables. However, +eager_graph+ can be slower than +eager+, especially if multiple
1292
+ # tables. However, +eager_graph+ is usually slower than +eager+, especially if multiple
1289
1293
  # one_to_many or many_to_many associations are joined.
1290
1294
  #
1291
1295
  # You can cascade the eager loading (loading associations on associated objects)
@@ -1330,6 +1334,56 @@ module Sequel
1330
1334
  obj.def_mutation_method(:eager, :eager_graph)
1331
1335
  end
1332
1336
 
1337
+ # If the expression is in the form <tt>x = y</tt> where +y+ is a <tt>Sequel::Model</tt>
1338
+ # instance, array of <tt>Sequel::Model</tt> instances, or a <tt>Sequel::Model</tt> dataset,
1339
+ # assume +x+ is an association symbol and look up the association reflection
1340
+ # via the dataset's model. From there, return the appropriate SQL based on the type of
1341
+ # association and the values of the foreign/primary keys of +y+. For most association
1342
+ # types, this is a simple transformation, but for +many_to_many+ associations this
1343
+ # creates a subquery to the join table.
1344
+ def complex_expression_sql(op, args)
1345
+ r = args.at(1)
1346
+ if (((op == :'=' || op == :'!=') and r.is_a?(Sequel::Model)) ||
1347
+ (multiple = ((op == :IN || op == :'NOT IN') and ((is_ds = r.is_a?(Sequel::Dataset)) or r.all?{|x| x.is_a?(Sequel::Model)}))))
1348
+ l = args.at(0)
1349
+ if ar = model.association_reflections[l]
1350
+ if multiple
1351
+ klass = ar.associated_class
1352
+ if is_ds
1353
+ if r.respond_to?(:model)
1354
+ unless r.model <= klass
1355
+ # A dataset for a different model class, could be a valid regular query
1356
+ return super
1357
+ end
1358
+ else
1359
+ # Not a model dataset, could be a valid regular query
1360
+ return super
1361
+ end
1362
+ else
1363
+ unless r.all?{|x| x.is_a?(klass)}
1364
+ raise Sequel::Error, "invalid association class for one object for association #{l.inspect} used in dataset filter for model #{model.inspect}, expected class #{klass.inspect}"
1365
+ end
1366
+ end
1367
+ elsif !r.is_a?(ar.associated_class)
1368
+ raise Sequel::Error, "invalid association class #{r.class.inspect} for association #{l.inspect} used in dataset filter for model #{model.inspect}, expected class #{ar.associated_class.inspect}"
1369
+ end
1370
+
1371
+ if exp = association_filter_expression(op, ar, r)
1372
+ literal(exp)
1373
+ else
1374
+ raise Sequel::Error, "invalid association type #{ar[:type].inspect} for association #{l.inspect} used in dataset filter for model #{model.inspect}"
1375
+ end
1376
+ elsif multiple && (is_ds || r.empty?)
1377
+ # Not a query designed for this support, could be a valid regular query
1378
+ super
1379
+ else
1380
+ raise Sequel::Error, "invalid association #{l.inspect} used in dataset filter for model #{model.inspect}"
1381
+ end
1382
+ else
1383
+ super
1384
+ end
1385
+ end
1386
+
1333
1387
  # The preferred eager loading method. Loads all associated records using one
1334
1388
  # query for each association.
1335
1389
  #
@@ -1376,9 +1430,9 @@ module Sequel
1376
1430
  # The secondary eager loading method. Loads all associations in a single query. This
1377
1431
  # method should only be used if you need to filter or order based on columns in associated tables.
1378
1432
  #
1379
- # This method builds an object graph using <tt>Dataset#graph</tt>. Then it uses the graph
1380
- # to build the associations, and finally replaces the graph with a simple array
1381
- # of model objects.
1433
+ # This method uses <tt>Dataset#graph</tt> to create appropriate aliases for columns in all the
1434
+ # tables. Then it uses the graph's metadata to build the associations from the single hash, and
1435
+ # finally replaces the array of hashes with an array model objects inside all.
1382
1436
  #
1383
1437
  # Be very careful when using this with multiple one_to_many or many_to_many associations, as you can
1384
1438
  # create large cartesian products. If you must graph multiple one_to_many and many_to_many associations,
@@ -1392,8 +1446,7 @@ module Sequel
1392
1446
  # all objects. You can use the :graph_* association options to modify the SQL query.
1393
1447
  #
1394
1448
  # Like +eager+, you need to call +all+ on the dataset for the eager loading to work. If you just
1395
- # call +each+, you will get a normal graphed result back (a hash with table alias symbol keys and
1396
- # model object values).
1449
+ # call +each+, it will yield plain hashes, each containing all columns from all the tables.
1397
1450
  def eager_graph(*associations)
1398
1451
  ds = if @opts[:eager_graph]
1399
1452
  self
@@ -1408,55 +1461,6 @@ module Sequel
1408
1461
  ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations)
1409
1462
  end
1410
1463
 
1411
- # If the expression is in the form <tt>x = y</tt> where +y+ is a <tt>Sequel::Model</tt>
1412
- # instance, assume +x+ is an association symbol and look up the association reflection
1413
- # via the dataset's model. From there, return the appropriate SQL based on the type of
1414
- # association and the values of the foreign/primary keys of +y+. For most association
1415
- # types, this is a simple transformation, but for +many_to_many+ associations this
1416
- # creates a subquery to the join table.
1417
- def complex_expression_sql(op, args)
1418
- r = args.at(1)
1419
- if (((op == :'=' || op == :'!=') and r.is_a?(Sequel::Model)) ||
1420
- (multiple = ((op == :IN || op == :'NOT IN') and ((is_ds = r.is_a?(Sequel::Dataset)) or r.all?{|x| x.is_a?(Sequel::Model)}))))
1421
- l = args.at(0)
1422
- if ar = model.association_reflections[l]
1423
- if multiple
1424
- klass = ar.associated_class
1425
- if is_ds
1426
- if r.respond_to?(:model)
1427
- unless r.model <= klass
1428
- # A dataset for a different model class, could be a valid regular query
1429
- return super
1430
- end
1431
- else
1432
- # Not a model dataset, could be a valid regular query
1433
- return super
1434
- end
1435
- else
1436
- unless r.all?{|x| x.is_a?(klass)}
1437
- raise Sequel::Error, "invalid association class for one object for association #{l.inspect} used in dataset filter for model #{model.inspect}, expected class #{klass.inspect}"
1438
- end
1439
- end
1440
- elsif !r.is_a?(ar.associated_class)
1441
- raise Sequel::Error, "invalid association class #{r.class.inspect} for association #{l.inspect} used in dataset filter for model #{model.inspect}, expected class #{ar.associated_class.inspect}"
1442
- end
1443
-
1444
- if exp = association_filter_expression(op, ar, r)
1445
- literal(exp)
1446
- else
1447
- raise Sequel::Error, "invalid association type #{ar[:type].inspect} for association #{l.inspect} used in dataset filter for model #{model.inspect}"
1448
- end
1449
- elsif multiple && (is_ds || r.empty?)
1450
- # Not a query designed for this support, could be a valid regular query
1451
- super
1452
- else
1453
- raise Sequel::Error, "invalid association #{l.inspect} used in dataset filter for model #{model.inspect}"
1454
- end
1455
- else
1456
- super
1457
- end
1458
- end
1459
-
1460
1464
  # Do not attempt to split the result set into associations,
1461
1465
  # just return results as simple objects. This is useful if you
1462
1466
  # want to use eager_graph as a shortcut to have all of the joins
@@ -1506,7 +1510,7 @@ module Sequel
1506
1510
  ds = ds.eager_graph_associations(ds, r.associated_class, assoc_table_alias, requirements + [assoc_table_alias], *associations) unless associations.empty?
1507
1511
  ds
1508
1512
  end
1509
-
1513
+
1510
1514
  # Check the associations are valid for the given model.
1511
1515
  # Call eager_graph_association on each association.
1512
1516
  #
@@ -1533,67 +1537,10 @@ module Sequel
1533
1537
  ds
1534
1538
  end
1535
1539
 
1536
- # Build associations out of the array of returned object graphs.
1537
- def eager_graph_build_associations(record_graphs)
1538
- eager_graph = @opts[:eager_graph]
1539
- master = eager_graph[:master]
1540
- requirements = eager_graph[:requirements]
1541
- alias_map = eager_graph[:alias_association_name_map]
1542
- type_map = eager_graph[:alias_association_type_map]
1543
- reciprocal_map = eager_graph[:reciprocals]
1544
-
1545
- # Make dependency map hash out of requirements array for each association.
1546
- # This builds a tree of dependencies that will be used for recursion
1547
- # to ensure that all parts of the object graph are loaded into the
1548
- # appropriate subordinate association.
1549
- dependency_map = {}
1550
- # Sort the associations by requirements length, so that
1551
- # requirements are added to the dependency hash before their
1552
- # dependencies.
1553
- requirements.sort_by{|a| a[1].length}.each do |ta, deps|
1554
- if deps.empty?
1555
- dependency_map[ta] = {}
1556
- else
1557
- deps = deps.dup
1558
- hash = dependency_map[deps.shift]
1559
- deps.each do |dep|
1560
- hash = hash[dep]
1561
- end
1562
- hash[ta] = {}
1563
- end
1564
- end
1565
-
1566
- # This mapping is used to make sure that duplicate entries in the
1567
- # result set are mapped to a single record. For example, using a
1568
- # single one_to_many association with 10 associated records,
1569
- # the main object will appear in the object graph 10 times.
1570
- # We map by primary key, if available, or by the object's entire values,
1571
- # if not. The mapping must be per table, so create sub maps for each table
1572
- # alias.
1573
- records_map = {master=>{}}
1574
- alias_map.keys.each{|ta| records_map[ta] = {}}
1575
-
1576
- # This will hold the final record set that we will be replacing the object graph with.
1577
- records = []
1578
- record_graphs.each do |record_graph|
1579
- primary_record = record_graph[master]
1580
- key = primary_record.pk_or_nil || primary_record.values.sort_by{|x| x[0].to_s}
1581
- if cached_pr = records_map[master][key]
1582
- primary_record = cached_pr
1583
- else
1584
- records_map[master][key] = primary_record
1585
- # Only add it to the list of records to return if it is a new record
1586
- records.push(primary_record)
1587
- end
1588
- # Build all associations for the current object and it's dependencies
1589
- eager_graph_build_associations_graph(dependency_map, alias_map, type_map, reciprocal_map, records_map, primary_record, record_graph)
1590
- end
1591
-
1592
- # Remove duplicate records from all associations if this graph could possibly be a cartesian product
1593
- eager_graph_make_associations_unique(records, dependency_map, alias_map, type_map) if eager_graph[:cartesian_product_number] > 1
1594
-
1595
- # Replace the array of object graphs with an array of model objects
1596
- record_graphs.replace(records)
1540
+ # Replace the array of plain hashes with an array of model objects will all eager_graphed
1541
+ # associations set in the associations cache for each object.
1542
+ def eager_graph_build_associations(hashes)
1543
+ hashes.replace(EagerGraphLoader.new(self).load(hashes))
1597
1544
  end
1598
1545
 
1599
1546
  private
@@ -1648,57 +1595,6 @@ module Sequel
1648
1595
  reflection
1649
1596
  end
1650
1597
 
1651
- # Build associations for the current object. This is called recursively
1652
- # to build all dependencies.
1653
- def eager_graph_build_associations_graph(dependency_map, alias_map, type_map, reciprocal_map, records_map, current, record_graph)
1654
- return if dependency_map.empty?
1655
- # Don't clobber the cached association value for one_to_many and many_to_many associations if it has already been setup
1656
- dependency_map.keys.each do |ta|
1657
- assoc_name = alias_map[ta]
1658
- current.associations[assoc_name] = type_map[ta] ? [] : nil unless current.associations.include?(assoc_name)
1659
- end
1660
- dependency_map.each do |ta, deps|
1661
- next unless rec = record_graph[ta]
1662
- key = rec.pk_or_nil || rec.values.sort_by{|x| x[0].to_s}
1663
- if cached_rec = records_map[ta][key]
1664
- rec = cached_rec
1665
- else
1666
- records_map[ta][key] = rec
1667
- end
1668
- assoc_name = alias_map[ta]
1669
- if type_map[ta]
1670
- current.associations[assoc_name].push(rec)
1671
- if reciprocal = reciprocal_map[ta]
1672
- rec.associations[reciprocal] = current
1673
- end
1674
- else
1675
- current.associations[assoc_name] = rec
1676
- end
1677
- # Recurse into dependencies of the current object
1678
- eager_graph_build_associations_graph(deps, alias_map, type_map, reciprocal_map, records_map, rec, record_graph)
1679
- end
1680
- end
1681
-
1682
- # If the result set is the result of a cartesian product, then it is possible that
1683
- # there are multiple records for each association when there should only be one.
1684
- # In that case, for each object in all associations loaded via +eager_graph+, run
1685
- # uniq! on the association to make sure no duplicate records show up.
1686
- # Note that this can cause legitimate duplicate records to be removed.
1687
- def eager_graph_make_associations_unique(records, dependency_map, alias_map, type_map)
1688
- records.each do |record|
1689
- dependency_map.each do |ta, deps|
1690
- list = record.send(alias_map[ta])
1691
- list = if type_map[ta]
1692
- list.uniq!
1693
- else
1694
- [list] if list
1695
- end
1696
- # Recurse into dependencies
1697
- eager_graph_make_associations_unique(list, deps, alias_map, type_map) if list
1698
- end
1699
- end
1700
- end
1701
-
1702
1598
  # Eagerly load all specified associations
1703
1599
  def eager_load(a, eager_assoc=@opts[:eager])
1704
1600
  return if a.empty?
@@ -1742,7 +1638,12 @@ module Sequel
1742
1638
  a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} unless r[:after_load].empty?
1743
1639
  end
1744
1640
  end
1745
-
1641
+
1642
+ # Return plain hashes instead of calling the row_proc if eager_graph is being used.
1643
+ def graph_each(&block)
1644
+ @opts[:eager_graph] ? fetch_rows(select_sql, &block) : super
1645
+ end
1646
+
1746
1647
  # Return a subquery expression for filering by a many_to_many association
1747
1648
  def many_to_many_association_filter_expression(op, ref, obj)
1748
1649
  lpks, lks, rks = ref.values_at(:left_primary_keys, :left_keys, :right_keys)
@@ -1776,6 +1677,264 @@ module Sequel
1776
1677
  super
1777
1678
  end
1778
1679
  end
1680
+
1681
+ # This class is the internal implementation of eager_graph. It is responsible for taking an array of plain
1682
+ # hashes and returning an array of model objects with all eager_graphed associations already set in the
1683
+ # association cache.
1684
+ class EagerGraphLoader
1685
+ # Hash with table alias symbol keys and association name values
1686
+ attr_reader :alias_map
1687
+
1688
+ # Hash with table alias symbol keys and subhash values mapping column_alias symbols to the
1689
+ # symbol of the real name of the column
1690
+ attr_reader :column_maps
1691
+
1692
+ # Recursive hash with table alias symbol keys mapping to hashes with dependent table alias symbol keys.
1693
+ attr_reader :dependency_map
1694
+
1695
+ # The table alias symbol for the primary model
1696
+ attr_reader :master
1697
+
1698
+ # Hash with table alias symbol keys and primary key symbol values (or arrays of primary key symbols for
1699
+ # composite key tables)
1700
+ attr_reader :primary_keys
1701
+
1702
+ # Hash with table alias symbol keys and reciprocal association symbol values,
1703
+ # used for setting reciprocals for one_to_many associations.
1704
+ attr_reader :reciprocal_map
1705
+
1706
+ # Hash with table alias symbol keys and subhash values mapping primary key symbols (or array of symbols)
1707
+ # to model instances. Used so that only a single model instance is created for each object.
1708
+ attr_reader :records_map
1709
+
1710
+ # Hash with table alias symbol keys and callable values used to create model instances
1711
+ attr_reader :row_procs
1712
+
1713
+ # Hash with table alias symbol keys and true/false values, where true means the
1714
+ # association represented by the table alias uses an array of values instead of
1715
+ # a single value (i.e. true => *_many, false => *_to_one).
1716
+ attr_reader :type_map
1717
+
1718
+ # Initialize all of the data structures used during loading.
1719
+ def initialize(dataset)
1720
+ opts = dataset.opts
1721
+ eager_graph = opts[:eager_graph]
1722
+ @master = eager_graph[:master]
1723
+ requirements = eager_graph[:requirements]
1724
+ alias_map = @alias_map = eager_graph[:alias_association_name_map]
1725
+ type_map = @type_map = eager_graph[:alias_association_type_map]
1726
+ reciprocal_map = @reciprocal_map = eager_graph[:reciprocals]
1727
+ @unique = eager_graph[:cartesian_product_number] > 1
1728
+
1729
+ # Make dependency map hash out of requirements array for each association.
1730
+ # This builds a tree of dependencies that will be used for recursion
1731
+ # to ensure that all parts of the object graph are loaded into the
1732
+ # appropriate subordinate association.
1733
+ @dependency_map = {}
1734
+ # Sort the associations by requirements length, so that
1735
+ # requirements are added to the dependency hash before their
1736
+ # dependencies.
1737
+ requirements.sort_by{|a| a[1].length}.each do |ta, deps|
1738
+ if deps.empty?
1739
+ dependency_map[ta] = {}
1740
+ else
1741
+ deps = deps.dup
1742
+ hash = dependency_map[deps.shift]
1743
+ deps.each do |dep|
1744
+ hash = hash[dep]
1745
+ end
1746
+ hash[ta] = {}
1747
+ end
1748
+ end
1749
+
1750
+ # This mapping is used to make sure that duplicate entries in the
1751
+ # result set are mapped to a single record. For example, using a
1752
+ # single one_to_many association with 10 associated records,
1753
+ # the main object column values appear in the object graph 10 times.
1754
+ # We map by primary key, if available, or by the object's entire values,
1755
+ # if not. The mapping must be per table, so create sub maps for each table
1756
+ # alias.
1757
+ records_map = {@master=>{}}
1758
+ alias_map.keys.each{|ta| records_map[ta] = {}}
1759
+ @records_map = records_map
1760
+
1761
+ datasets = opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
1762
+ column_aliases = opts[:graph_aliases] || opts[:graph][:column_aliases]
1763
+ primary_keys = {}
1764
+ column_maps = {}
1765
+ models = {}
1766
+ row_procs = {}
1767
+ datasets.each do |ta, ds|
1768
+ models[ta] = ds.model
1769
+ primary_keys[ta] = []
1770
+ column_maps[ta] = {}
1771
+ row_procs[ta] = ds.row_proc
1772
+ end
1773
+ column_aliases.each do |col_alias, tc|
1774
+ ta, column = tc
1775
+ column_maps[ta][col_alias] = column
1776
+ end
1777
+ column_maps.each do |ta, h|
1778
+ pk = models[ta].primary_key
1779
+ if pk.is_a?(Array)
1780
+ primary_keys[ta] = []
1781
+ h.select{|ca, c| primary_keys[ta] << ca if pk.include?(c)}
1782
+ else
1783
+ h.select{|ca, c| primary_keys[ta] = ca if pk == c}
1784
+ end
1785
+ end
1786
+ @column_maps = column_maps
1787
+ @primary_keys = primary_keys
1788
+ @row_procs = row_procs
1789
+
1790
+ # For performance, create two special maps for the master table,
1791
+ # so you can skip a hash lookup.
1792
+ @master_column_map = column_maps[master]
1793
+ @master_primary_keys = primary_keys[master]
1794
+
1795
+ # Add a special hash mapping table alias symbols to 5 element arrays that just
1796
+ # contain the data in other data structures for that table alias. This is
1797
+ # used for performance, to get all values in one hash lookup instead of
1798
+ # separate hash lookups for each data structure.
1799
+ ta_map = {}
1800
+ alias_map.keys.each do |ta|
1801
+ ta_map[ta] = [records_map[ta], row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]]
1802
+ end
1803
+ @ta_map = ta_map
1804
+ end
1805
+
1806
+ # Return an array of primary model instances with the associations cache prepopulated
1807
+ # for all model objects (both primary and associated).
1808
+ def load(hashes)
1809
+ master = master()
1810
+
1811
+ # Assign to local variables for speed increase
1812
+ rp = row_procs[master]
1813
+ rm = records_map[master]
1814
+ dm = dependency_map
1815
+
1816
+ # This will hold the final record set that we will be replacing the object graph with.
1817
+ records = []
1818
+
1819
+ hashes.each do |h|
1820
+ unless key = master_pk(h)
1821
+ key = hkey(master_hfor(h))
1822
+ end
1823
+ unless primary_record = rm[key]
1824
+ primary_record = rm[key] = rp.call(master_hfor(h))
1825
+ # Only add it to the list of records to return if it is a new record
1826
+ records.push(primary_record)
1827
+ end
1828
+ # Build all associations for the current object and it's dependencies
1829
+ _load(dm, primary_record, h)
1830
+ end
1831
+
1832
+ # Remove duplicate records from all associations if this graph could possibly be a cartesian product
1833
+ unique(records, dm) if @unique
1834
+
1835
+ records
1836
+ end
1837
+
1838
+ private
1839
+
1840
+ # Recursive method that creates associated model objects and associates them to the current model object.
1841
+ def _load(dependency_map, current, h)
1842
+ dependency_map.each do |ta, deps|
1843
+ unless key = pk(ta, h)
1844
+ ta_h = hfor(ta, h)
1845
+ unless ta_h.values.any?
1846
+ assoc_name = alias_map[ta]
1847
+ unless (assoc = current.associations).has_key?(assoc_name)
1848
+ assoc[assoc_name] = type_map[ta] ? [] : nil
1849
+ end
1850
+ next
1851
+ end
1852
+ key = hkey(ta_h)
1853
+ end
1854
+ rm, rp, assoc_name, tm, rcm = @ta_map[ta]
1855
+ unless rec = rm[key]
1856
+ rec = rm[key] = rp.call(hfor(ta, h))
1857
+ end
1858
+
1859
+ if tm
1860
+ unless (assoc = current.associations).has_key?(assoc_name)
1861
+ assoc[assoc_name] = []
1862
+ end
1863
+ assoc[assoc_name].push(rec)
1864
+ rec.associations[rcm] = current if rcm
1865
+ else
1866
+ current.associations[assoc_name] = rec
1867
+ end
1868
+ # Recurse into dependencies of the current object
1869
+ _load(deps, rec, h) unless deps.empty?
1870
+ end
1871
+ end
1872
+
1873
+ # Return the subhash for the specific table alias +ta+ by parsing the values out of the main hash +h+
1874
+ def hfor(ta, h)
1875
+ out = {}
1876
+ @column_maps[ta].each{|ca, c| out[c] = h[ca]}
1877
+ out
1878
+ end
1879
+
1880
+ # Return a suitable hash key for any subhash +h+, which is an array of values by column order.
1881
+ # This is only used if the primary key cannot be used.
1882
+ def hkey(h)
1883
+ h.sort_by{|x| x[0].to_s}
1884
+ end
1885
+
1886
+ # Return the subhash for the master table by parsing the values out of the main hash +h+
1887
+ def master_hfor(h)
1888
+ out = {}
1889
+ @master_column_map.each{|ca, c| out[c] = h[ca]}
1890
+ out
1891
+ end
1892
+
1893
+ # Return a primary key value for the master table by parsing it out of the main hash +h+.
1894
+ def master_pk(h)
1895
+ x = @master_primary_keys
1896
+ if x.is_a?(Array)
1897
+ unless x == []
1898
+ x = x.map{|ca| h[ca]}
1899
+ x if x.all?
1900
+ end
1901
+ else
1902
+ h[x]
1903
+ end
1904
+ end
1905
+
1906
+ # Return a primary key value for the given table alias by parsing it out of the main hash +h+.
1907
+ def pk(ta, h)
1908
+ x = primary_keys[ta]
1909
+ if x.is_a?(Array)
1910
+ unless x == []
1911
+ x = x.map{|ca| h[ca]}
1912
+ x if x.all?
1913
+ end
1914
+ else
1915
+ h[x]
1916
+ end
1917
+ end
1918
+
1919
+ # If the result set is the result of a cartesian product, then it is possible that
1920
+ # there are multiple records for each association when there should only be one.
1921
+ # In that case, for each object in all associations loaded via +eager_graph+, run
1922
+ # uniq! on the association to make sure no duplicate records show up.
1923
+ # Note that this can cause legitimate duplicate records to be removed.
1924
+ def unique(records, dependency_map)
1925
+ records.each do |record|
1926
+ dependency_map.each do |ta, deps|
1927
+ list = record.send(alias_map[ta])
1928
+ list = if type_map[ta]
1929
+ list.uniq!
1930
+ unique(list, deps) if !list.empty? && !deps.empty?
1931
+ elsif list
1932
+ unique([list], deps) unless deps.empty?
1933
+ end
1934
+ end
1935
+ end
1936
+ end
1937
+ end
1779
1938
  end
1780
1939
  end
1781
1940
  end