sequel 3.25.0 → 3.26.0

Sign up to get free protection for your applications and to get access to all the features.
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