sequel 3.8.0 → 3.9.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 (65) hide show
  1. data/CHANGELOG +48 -0
  2. data/Rakefile +6 -28
  3. data/bin/sequel +7 -2
  4. data/doc/release_notes/3.9.0.txt +233 -0
  5. data/lib/sequel/adapters/ado.rb +4 -8
  6. data/lib/sequel/adapters/amalgalite.rb +1 -1
  7. data/lib/sequel/adapters/dbi.rb +3 -3
  8. data/lib/sequel/adapters/do.rb +7 -13
  9. data/lib/sequel/adapters/jdbc.rb +10 -16
  10. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  11. data/lib/sequel/adapters/mysql.rb +10 -23
  12. data/lib/sequel/adapters/odbc.rb +6 -10
  13. data/lib/sequel/adapters/postgres.rb +0 -5
  14. data/lib/sequel/adapters/shared/mssql.rb +17 -9
  15. data/lib/sequel/adapters/shared/mysql.rb +16 -7
  16. data/lib/sequel/adapters/shared/sqlite.rb +5 -0
  17. data/lib/sequel/adapters/sqlite.rb +2 -1
  18. data/lib/sequel/connection_pool.rb +67 -349
  19. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  20. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  21. data/lib/sequel/connection_pool/single.rb +29 -0
  22. data/lib/sequel/connection_pool/threaded.rb +150 -0
  23. data/lib/sequel/core.rb +46 -15
  24. data/lib/sequel/database.rb +11 -9
  25. data/lib/sequel/dataset/convenience.rb +23 -0
  26. data/lib/sequel/dataset/graph.rb +2 -2
  27. data/lib/sequel/dataset/query.rb +9 -5
  28. data/lib/sequel/dataset/sql.rb +87 -12
  29. data/lib/sequel/extensions/inflector.rb +8 -1
  30. data/lib/sequel/extensions/schema_dumper.rb +3 -4
  31. data/lib/sequel/model/associations.rb +5 -43
  32. data/lib/sequel/model/base.rb +9 -2
  33. data/lib/sequel/model/default_inflections.rb +1 -1
  34. data/lib/sequel/model/exceptions.rb +11 -1
  35. data/lib/sequel/model/inflections.rb +8 -1
  36. data/lib/sequel/model/plugins.rb +2 -12
  37. data/lib/sequel/plugins/active_model.rb +5 -0
  38. data/lib/sequel/plugins/association_dependencies.rb +1 -1
  39. data/lib/sequel/plugins/many_through_many.rb +1 -1
  40. data/lib/sequel/plugins/optimistic_locking.rb +65 -0
  41. data/lib/sequel/plugins/single_table_inheritance.rb +14 -3
  42. data/lib/sequel/plugins/validation_helpers.rb +2 -2
  43. data/lib/sequel/sql.rb +2 -2
  44. data/lib/sequel/timezones.rb +2 -2
  45. data/lib/sequel/version.rb +1 -1
  46. data/spec/adapters/mssql_spec.rb +19 -0
  47. data/spec/adapters/mysql_spec.rb +4 -0
  48. data/spec/adapters/postgres_spec.rb +180 -0
  49. data/spec/adapters/spec_helper.rb +15 -1
  50. data/spec/core/connection_pool_spec.rb +119 -78
  51. data/spec/core/database_spec.rb +41 -50
  52. data/spec/core/dataset_spec.rb +115 -4
  53. data/spec/extensions/active_model_spec.rb +40 -34
  54. data/spec/extensions/boolean_readers_spec.rb +1 -1
  55. data/spec/extensions/migration_spec.rb +43 -38
  56. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  57. data/spec/extensions/schema_dumper_spec.rb +4 -4
  58. data/spec/extensions/single_table_inheritance_spec.rb +19 -11
  59. data/spec/integration/dataset_test.rb +44 -1
  60. data/spec/integration/plugin_test.rb +39 -0
  61. data/spec/integration/prepared_statement_test.rb +58 -7
  62. data/spec/integration/spec_helper.rb +4 -0
  63. data/spec/model/eager_loading_spec.rb +24 -0
  64. data/spec/model/validations_spec.rb +5 -1
  65. metadata +114 -106
@@ -720,7 +720,7 @@ module Sequel
720
720
  def save(*columns)
721
721
  opts = columns.last.is_a?(Hash) ? columns.pop : {}
722
722
  if opts[:validate] != false and !valid?
723
- raise(ValidationFailed, errors.full_messages.join(', ')) if raise_on_save_failure
723
+ raise(ValidationFailed.new(errors)) if raise_on_save_failure
724
724
  return
725
725
  end
726
726
  checked_save_failure{checked_transaction(opts){_save(columns, opts)}}
@@ -811,11 +811,18 @@ module Sequel
811
811
  # allow running inside a transaction
812
812
  def _destroy(opts)
813
813
  return save_failure(:destroy) if before_destroy == false
814
- delete
814
+ _destroy_delete
815
815
  after_destroy
816
816
  self
817
817
  end
818
818
 
819
+ # Internal delete method to call when destroying an object,
820
+ # separated from delete to allow you to override destroy's version
821
+ # without affecting delete.
822
+ def _destroy_delete
823
+ delete
824
+ end
825
+
819
826
  def _insert
820
827
  ds = model.dataset
821
828
  if ds.respond_to?(:insert_select) and h = ds.insert_select(@values)
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  # Proc that is instance evaled to create the default inflections for both the
3
3
  # model inflector and the inflector extension.
4
- DEFAULT_INFLECTIONS_PROC = lambda do
4
+ DEFAULT_INFLECTIONS_PROC = proc do
5
5
  plural(/$/, 's')
6
6
  plural(/s$/i, 's')
7
7
  plural(/(alias|(?:stat|octop|vir|b)us)$/i, '\1es')
@@ -1,6 +1,16 @@
1
1
  module Sequel
2
2
  # This exception will be raised when raise_on_save_failure is set and validation fails
3
- class ValidationFailed < Error; end
3
+ class ValidationFailed < Error
4
+ def initialize(errors)
5
+ if errors.respond_to?(:full_messages)
6
+ @errors = errors
7
+ super(errors.full_messages.join(', '))
8
+ else
9
+ super
10
+ end
11
+ end
12
+ attr_reader :errors
13
+ end
4
14
 
5
15
  # This exception will be raised when raise_on_save_failure is set and a before hook returns false
6
16
  class BeforeHookFailed < Error; end
@@ -39,7 +39,14 @@ module Sequel
39
39
  @plurals, @singulars, @uncountables = [], [], []
40
40
 
41
41
  class << self
42
- attr_reader :plurals, :singulars, :uncountables
42
+ # Array of 2 element arrays, first containing a regex, and the second containing a substitution pattern, used for plurization.
43
+ attr_reader :plurals
44
+
45
+ # Array of 2 element arrays, first containing a regex, and the second containing a substitution pattern, used for singularization.
46
+ attr_reader :singulars
47
+
48
+ # Array of strings for words were the singular form is the same as the plural form
49
+ attr_reader :uncountables
43
50
  end
44
51
 
45
52
  # Clears the loaded inflections within a given scope (default is :all). Give the scope as a symbol of the inflection type,
@@ -60,25 +60,15 @@ module Sequel
60
60
 
61
61
  private
62
62
 
63
- # Returns the new style location for the plugin name.
64
- def plugin_gem_location(plugin)
65
- "sequel/plugins/#{plugin}"
66
- end
67
-
68
- # Returns the old style location for the plugin name.
69
- def plugin_gem_location_old(plugin)
70
- "sequel_#{plugin}"
71
- end
72
-
73
63
  # Returns the module for the specified plugin. If the module is not
74
64
  # defined, the corresponding plugin gem is automatically loaded.
75
65
  def plugin_module(plugin)
76
66
  module_name = plugin.to_s.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
77
67
  if not Sequel::Plugins.const_defined?(module_name)
78
68
  begin
79
- require plugin_gem_location(plugin)
69
+ Sequel.tsk_require "sequel/plugins/#{plugin}"
80
70
  rescue LoadError
81
- require plugin_gem_location_old(plugin)
71
+ Sequel.tsk_require "sequel_#{plugin}"
82
72
  end
83
73
  end
84
74
  Sequel::Plugins.const_get(module_name)
@@ -1,10 +1,15 @@
1
+ require 'active_model'
1
2
  module Sequel
2
3
  module Plugins
3
4
  # The ActiveModel plugin makes Sequel::Model objects the
4
5
  # pass the ActiveModel::Lint tests, which should
5
6
  # hopefully mean full ActiveModel compliance. This should
6
7
  # allow the full support of Sequel::Model objects in Rails 3.
8
+ # This plugin requires active_model in order to use
9
+ # ActiveModel::Naming.
7
10
  module ActiveModel
11
+ ClassMethods = ::ActiveModel::Naming
12
+
8
13
  module InstanceMethods
9
14
  # Record that an object was destroyed, for later use by
10
15
  # destroyed?
@@ -16,7 +16,7 @@ module Sequel
16
16
  # and dependency action values. You can provide the hash to the plugin call itself or
17
17
  # to the add_association_dependencies method:
18
18
  #
19
- # Business.plugin :association_dependencies, :address=>delete
19
+ # Business.plugin :association_dependencies, :address=>:delete
20
20
  # # or:
21
21
  # Artist.plugin :association_dependencies
22
22
  # Artist.add_association_dependencies :albums=>:destroy, :reviews=>:delete, :tags=>:nullify
@@ -193,7 +193,7 @@ module Sequel
193
193
  opts[:eager_grapher] ||= proc do |ds, assoc_alias, table_alias|
194
194
  iq = table_alias
195
195
  opts.edges.each do |t|
196
- ds = ds.graph(t[:table], t.include?(:only_conditions) ? t[:only_conditions] : (Array(t[:right]).zip(Array(t[:left])) + t[:conditions]), :select=>false, :table_alias=>ds.send(:eager_unique_table_alias, ds, t[:table]), :join_type=>t[:join_type], :implicit_qualifier=>iq, &t[:block])
196
+ ds = ds.graph(t[:table], t.include?(:only_conditions) ? t[:only_conditions] : (Array(t[:right]).zip(Array(t[:left])) + t[:conditions]), :select=>false, :table_alias=>ds.unused_table_alias(t[:table]), :join_type=>t[:join_type], :implicit_qualifier=>iq, &t[:block])
197
197
  iq = nil
198
198
  end
199
199
  fe = opts[:final_edge]
@@ -0,0 +1,65 @@
1
+ module Sequel
2
+ module Plugins
3
+ # This plugin implements a simple database-independent locking mechanism
4
+ # to ensure that concurrent updates do not override changes. This is
5
+ # best implemented by a code example:
6
+ #
7
+ # class Person < Sequel::Model
8
+ # plugin :optimistic_locking
9
+ # end
10
+ # p1 = Person[1]
11
+ # p2 = Person[1]
12
+ # p1.update(:name=>'Jim') # works
13
+ # p2.update(:name=>'Bob') # raises Sequel::Plugins::OptimisticLocking::Error
14
+ #
15
+ # In order for this plugin to work, you need to make sure that the database
16
+ # table has a lock_version column (or other column you name via the lock_column
17
+ # class level accessor) that defaults to 0.
18
+ #
19
+ # This plugin does not work with the class_table_inheritance plugin.
20
+ module OptimisticLocking
21
+ # Exception class raised when trying to update or destroy a stale object.
22
+ class Error < Sequel::Error
23
+ end
24
+
25
+ # Set the lock_column to the :lock_column option, or :lock_version if
26
+ # that option is not given.
27
+ def self.configure(model, opts={})
28
+ model.lock_column = opts[:lock_column] || :lock_version
29
+ end
30
+
31
+ module ClassMethods
32
+ # The column holding the version of the lock
33
+ attr_accessor :lock_column
34
+
35
+ # Copy the lock_column value into the subclass
36
+ def inherited(subclass)
37
+ super
38
+ subclass.lock_column = lock_column
39
+ end
40
+ end
41
+
42
+ module InstanceMethods
43
+ private
44
+
45
+ # Only delete the object when destroying if it has the same lock version. If the row
46
+ # doesn't have the same lock version, raise an error.
47
+ def _destroy_delete
48
+ lc = model.lock_column
49
+ raise(Error, "Attempt to destroy a stale object") if this.filter(lc=>send(lc)).delete != 1
50
+ end
51
+
52
+ # Only update the row if it has the same lock version, and increment the
53
+ # lock version. If the row doesn't have the same lock version, raise
54
+ # an Error.
55
+ def _update(columns)
56
+ lc = model.lock_column
57
+ lcv = send(lc)
58
+ columns[lc] = lcv + 1
59
+ raise(Error, "Attempt to update a stale object") if this.filter(lc=>lcv).update(columns) != 1
60
+ send("#{lc}=", lcv + 1)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -19,11 +19,10 @@ module Sequel
19
19
  # dataset's row_proc so that the dataset yields objects of varying classes,
20
20
  # where the class used has the same name as the key field.
21
21
  def self.configure(model, key)
22
- m = model.method(:constantize)
23
22
  model.instance_eval do
24
23
  @sti_key = key
25
24
  @sti_dataset = dataset
26
- dataset.row_proc = lambda{|r| (m.call(r[key]) rescue model).load(r)}
25
+ dataset.row_proc = lambda{|r| model.sti_load(r)}
27
26
  end
28
27
  end
29
28
 
@@ -42,13 +41,25 @@ module Sequel
42
41
  super
43
42
  sk = sti_key
44
43
  sd = sti_dataset
45
- subclass.set_dataset(sd.filter(sk=>subclass.name.to_s), :inherited=>true)
44
+ subclass.set_dataset(sd.filter(SQL::QualifiedIdentifier.new(table_name, sk)=>subclass.name.to_s), :inherited=>true)
46
45
  subclass.instance_eval do
47
46
  @sti_key = sk
48
47
  @sti_dataset = sd
49
48
  @simple_table = nil
50
49
  end
51
50
  end
51
+
52
+ # Return an instance of the class specified by sti_key,
53
+ # used by the row_proc.
54
+ def sti_load(r)
55
+ v = r[sti_key]
56
+ model = if (v && v != '')
57
+ constantize(v) rescue self
58
+ else
59
+ self
60
+ end
61
+ model.load(r)
62
+ end
52
63
  end
53
64
 
54
65
  module InstanceMethods
@@ -18,8 +18,8 @@ module Sequel
18
18
  # attribute(s) to validate.
19
19
  # Options:
20
20
  # * :allow_blank - Whether to skip the validation if the value is blank. You should
21
- # make sure all objects respond to blank if you use this option, which you can do by
22
- # requiring 'sequel/extensions/blank'
21
+ # make sure all objects respond to blank if you use this option, which you can do by:
22
+ # Sequel.extension :blank
23
23
  # * :allow_missing - Whether to skip the validation if the attribute isn't a key in the
24
24
  # values hash. This is different from allow_nil, because Sequel only sends the attributes
25
25
  # in the values when doing an insert or update. If the attribute is not present, Sequel
data/lib/sequel/sql.rb CHANGED
@@ -360,7 +360,7 @@ module Sequel
360
360
  # and SQL::StringExpression).
361
361
  #
362
362
  # This defines the like (LIKE) and ilike methods, used for pattern matching.
363
- # like is case sensitive, ilike is case insensitive.
363
+ # like is case sensitive (if the database supports it), ilike is case insensitive.
364
364
  module StringMethods
365
365
  # Create a BooleanExpression case insensitive pattern match of self
366
366
  # with the given patterns. See StringExpression.like.
@@ -368,7 +368,7 @@ module Sequel
368
368
  StringExpression.like(self, *(ces << {:case_insensitive=>true}))
369
369
  end
370
370
 
371
- # Create a BooleanExpression case sensitive pattern match of self with
371
+ # Create a BooleanExpression case sensitive (if the database supports it) pattern match of self with
372
372
  # the given patterns. See StringExpression.like.
373
373
  def like(*ces)
374
374
  StringExpression.like(self, *ces)
@@ -49,8 +49,8 @@ module Sequel
49
49
  # same time and just modifying the timezone.
50
50
  def convert_input_datetime_no_offset(v, input_timezone)
51
51
  case input_timezone
52
- when :utc
53
- v# DateTime assumes UTC if no offset is given
52
+ when :utc, nil
53
+ v # DateTime assumes UTC if no offset is given
54
54
  when :local
55
55
  v.new_offset(LOCAL_DATETIME_OFFSET) - LOCAL_DATETIME_OFFSET
56
56
  else
@@ -1,6 +1,6 @@
1
1
  module Sequel
2
2
  MAJOR = 3
3
- MINOR = 8
3
+ MINOR = 9
4
4
  TINY = 0
5
5
 
6
6
  VERSION = [MAJOR, MINOR, TINY].join('.')
@@ -352,3 +352,22 @@ context "MSSSQL::Dataset#disable_insert_output" do
352
352
  MSSQL_DB[:test].disable_insert_output.send(:simple_select_all?).should == true
353
353
  end
354
354
  end
355
+
356
+ context "MSSSQL::Dataset#into" do
357
+ before do
358
+ @db = MSSQL_DB
359
+ end
360
+
361
+ specify "should format SELECT statement" do
362
+ @db[:t].into(:new).select_sql.should == "SELECT * INTO NEW FROM T"
363
+ end
364
+
365
+ specify "should select rows into a new table" do
366
+ @db.create_table!(:t) {Integer :id; String :value}
367
+ @db[:t].insert(:id => 1, :value => "test")
368
+ @db << @db[:t].into(:new).select_sql
369
+ @db[:new].all.should == [{:id => 1, :value => "test"}]
370
+ @db.drop_table(:t)
371
+ @db.drop_table(:new)
372
+ end
373
+ end
@@ -79,6 +79,10 @@ context "A MySQL database" do
79
79
  specify "should provide the server version" do
80
80
  MYSQL_DB.server_version.should >= 40000
81
81
  end
82
+
83
+ specify "should handle the creation and dropping of an InnoDB table with foreign keys" do
84
+ proc{MYSQL_DB.create_table!(:test_innodb, :engine=>:InnoDB){primary_key :id; foreign_key :fk, :test_innodb, :key=>:id}}.should_not raise_error
85
+ end
82
86
  end
83
87
 
84
88
  if MYSQL_DB.class.adapter_scheme == :mysql
@@ -543,6 +543,186 @@ context "Postgres::Database schema qualified tables" do
543
543
  end
544
544
  end
545
545
 
546
+ context "Postgres::Database schema qualified tables and eager graphing" do
547
+ before(:all) do
548
+ @db = POSTGRES_DB
549
+ @db.run "DROP SCHEMA s CASCADE" rescue nil
550
+ @db.run "CREATE SCHEMA s"
551
+ @db.quote_identifiers = true
552
+
553
+ @db.create_table(:s__bands){primary_key :id; String :name}
554
+ @db.create_table(:s__albums){primary_key :id; String :name; foreign_key :band_id, :s__bands}
555
+ @db.create_table(:s__tracks){primary_key :id; String :name; foreign_key :album_id, :s__albums}
556
+ @db.create_table(:s__members){primary_key :id; String :name; foreign_key :band_id, :s__bands}
557
+
558
+ @Band = Class.new(Sequel::Model(:s__bands))
559
+ @Album = Class.new(Sequel::Model(:s__albums))
560
+ @Track = Class.new(Sequel::Model(:s__tracks))
561
+ @Member = Class.new(Sequel::Model(:s__members))
562
+ def @Band.name; :Band; end
563
+ def @Album.name; :Album; end
564
+ def @Track.name; :Track; end
565
+ def @Member.name; :Member; end
566
+
567
+ @Band.one_to_many :albums, :class=>@Album, :order=>:name
568
+ @Band.one_to_many :members, :class=>@Member, :order=>:name
569
+ @Album.many_to_one :band, :class=>@Band, :order=>:name
570
+ @Album.one_to_many :tracks, :class=>@Track, :order=>:name
571
+ @Track.many_to_one :album, :class=>@Album, :order=>:name
572
+ @Member.many_to_one :band, :class=>@Band, :order=>:name
573
+
574
+ @Member.many_to_many :members, :class=>@Member, :join_table=>:s__bands, :right_key=>:id, :left_key=>:id, :left_primary_key=>:band_id, :right_primary_key=>:band_id, :order=>:name
575
+ @Band.many_to_many :tracks, :class=>@Track, :join_table=>:s__albums, :right_key=>:id, :right_primary_key=>:album_id, :order=>:name
576
+
577
+ @b1 = @Band.create(:name=>"BM")
578
+ @b2 = @Band.create(:name=>"J")
579
+ @a1 = @Album.create(:name=>"BM1", :band=>@b1)
580
+ @a2 = @Album.create(:name=>"BM2", :band=>@b1)
581
+ @a3 = @Album.create(:name=>"GH", :band=>@b2)
582
+ @a4 = @Album.create(:name=>"GHL", :band=>@b2)
583
+ @t1 = @Track.create(:name=>"BM1-1", :album=>@a1)
584
+ @t2 = @Track.create(:name=>"BM1-2", :album=>@a1)
585
+ @t3 = @Track.create(:name=>"BM2-1", :album=>@a2)
586
+ @t4 = @Track.create(:name=>"BM2-2", :album=>@a2)
587
+ @m1 = @Member.create(:name=>"NU", :band=>@b1)
588
+ @m2 = @Member.create(:name=>"TS", :band=>@b1)
589
+ @m3 = @Member.create(:name=>"NS", :band=>@b2)
590
+ @m4 = @Member.create(:name=>"JC", :band=>@b2)
591
+ end
592
+ after(:all) do
593
+ @db.quote_identifiers = false
594
+ @db.run "DROP SCHEMA s CASCADE"
595
+ end
596
+
597
+ specify "should return all eager graphs correctly" do
598
+ bands = @Band.order(:bands__name).eager_graph(:albums).all
599
+ bands.should == [@b1, @b2]
600
+ bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
601
+
602
+ bands = @Band.order(:bands__name).eager_graph(:albums=>:tracks).all
603
+ bands.should == [@b1, @b2]
604
+ bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
605
+ bands.map{|x| x.albums.map{|y| y.tracks}}.should == [[[@t1, @t2], [@t3, @t4]], [[], []]]
606
+
607
+ bands = @Band.order(:bands__name).eager_graph({:albums=>:tracks}, :members).all
608
+ bands.should == [@b1, @b2]
609
+ bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
610
+ bands.map{|x| x.albums.map{|y| y.tracks}}.should == [[[@t1, @t2], [@t3, @t4]], [[], []]]
611
+ bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
612
+ end
613
+
614
+ specify "should have eager graphs work with previous joins" do
615
+ bands = @Band.order(:bands__name).select(:s__bands.*).join(:s__members, :band_id=>:id).from_self(:alias=>:bands0).eager_graph(:albums=>:tracks).all
616
+ bands.should == [@b1, @b2]
617
+ bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
618
+ bands.map{|x| x.albums.map{|y| y.tracks}}.should == [[[@t1, @t2], [@t3, @t4]], [[], []]]
619
+ end
620
+
621
+ specify "should have eager graphs work with joins with the same tables" do
622
+ bands = @Band.order(:bands__name).select(:s__bands.*).join(:s__members, :band_id=>:id).eager_graph({:albums=>:tracks}, :members).all
623
+ bands.should == [@b1, @b2]
624
+ bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
625
+ bands.map{|x| x.albums.map{|y| y.tracks}}.should == [[[@t1, @t2], [@t3, @t4]], [[], []]]
626
+ bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
627
+ end
628
+
629
+ specify "should have eager graphs work with self referential associations" do
630
+ bands = @Band.order(:bands__name).eager_graph(:tracks=>{:album=>:band}).all
631
+ bands.should == [@b1, @b2]
632
+ bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
633
+ bands.map{|x| x.tracks.map{|y| y.album}}.should == [[@a1, @a1, @a2, @a2], []]
634
+ bands.map{|x| x.tracks.map{|y| y.album.band}}.should == [[@b1, @b1, @b1, @b1], []]
635
+
636
+ members = @Member.order(:members__name).eager_graph(:members).all
637
+ members.should == [@m4, @m3, @m1, @m2]
638
+ members.map{|x| x.members}.should == [[@m4, @m3], [@m4, @m3], [@m1, @m2], [@m1, @m2]]
639
+
640
+ members = @Member.order(:members__name).eager_graph(:band, :members=>:band).all
641
+ members.should == [@m4, @m3, @m1, @m2]
642
+ members.map{|x| x.band}.should == [@b2, @b2, @b1, @b1]
643
+ members.map{|x| x.members}.should == [[@m4, @m3], [@m4, @m3], [@m1, @m2], [@m1, @m2]]
644
+ members.map{|x| x.members.map{|y| y.band}}.should == [[@b2, @b2], [@b2, @b2], [@b1, @b1], [@b1, @b1]]
645
+ end
646
+
647
+ specify "should have eager graphs work with a from_self dataset" do
648
+ bands = @Band.order(:bands__name).from_self.eager_graph(:tracks=>{:album=>:band}).all
649
+ bands.should == [@b1, @b2]
650
+ bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
651
+ bands.map{|x| x.tracks.map{|y| y.album}}.should == [[@a1, @a1, @a2, @a2], []]
652
+ bands.map{|x| x.tracks.map{|y| y.album.band}}.should == [[@b1, @b1, @b1, @b1], []]
653
+ end
654
+
655
+ specify "should have eager graphs work with different types of aliased from tables" do
656
+ bands = @Band.order(:tracks__name).from(:s__bands___tracks).eager_graph(:tracks).all
657
+ bands.should == [@b1, @b2]
658
+ bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
659
+
660
+ bands = @Band.order(:tracks__name).from(:s__bands.as(:tracks)).eager_graph(:tracks).all
661
+ bands.should == [@b1, @b2]
662
+ bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
663
+
664
+ bands = @Band.order(:tracks__name).from(:s__bands.as(:tracks.identifier)).eager_graph(:tracks).all
665
+ bands.should == [@b1, @b2]
666
+ bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
667
+
668
+ bands = @Band.order(:tracks__name).from(:s__bands.as('tracks')).eager_graph(:tracks).all
669
+ bands.should == [@b1, @b2]
670
+ bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
671
+ end
672
+
673
+ specify "should have eager graphs work with join tables with aliases" do
674
+ bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums___tracks, :band_id=>:id.qualify(:s__bands)).eager_graph(:albums=>:tracks).all
675
+ bands.should == [@b1, @b2]
676
+ bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
677
+ bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
678
+
679
+ bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums.as(:tracks), :band_id=>:id.qualify(:s__bands)).eager_graph(:albums=>:tracks).all
680
+ bands.should == [@b1, @b2]
681
+ bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
682
+ bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
683
+
684
+ bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums.as('tracks'), :band_id=>:id.qualify(:s__bands)).eager_graph(:albums=>:tracks).all
685
+ bands.should == [@b1, @b2]
686
+ bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
687
+ bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
688
+
689
+ bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums.as(:tracks.identifier), :band_id=>:id.qualify(:s__bands)).eager_graph(:albums=>:tracks).all
690
+ bands.should == [@b1, @b2]
691
+ bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
692
+ bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
693
+
694
+ bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums, {:band_id=>:id.qualify(:s__bands)}, :table_alias=>:tracks).eager_graph(:albums=>:tracks).all
695
+ bands.should == [@b1, @b2]
696
+ bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
697
+ bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
698
+
699
+ bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums, {:band_id=>:id.qualify(:s__bands)}, :table_alias=>'tracks').eager_graph(:albums=>:tracks).all
700
+ bands.should == [@b1, @b2]
701
+ bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
702
+ bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
703
+
704
+ bands = @Band.order(:bands__name).eager_graph(:members).join(:s__albums, {:band_id=>:id.qualify(:s__bands)}, :table_alias=>:tracks.identifier).eager_graph(:albums=>:tracks).all
705
+ bands.should == [@b1, @b2]
706
+ bands.map{|x| x.albums}.should == [[@a1, @a2], [@a3, @a4]]
707
+ bands.map{|x| x.members}.should == [[@m1, @m2], [@m4, @m3]]
708
+ end
709
+
710
+ specify "should have eager graphs work with different types of qualified from tables" do
711
+ bands = @Band.order(:bands__name).from(:bands.qualify(:s)).eager_graph(:tracks).all
712
+ bands.should == [@b1, @b2]
713
+ bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
714
+
715
+ bands = @Band.order(:bands__name).from(:bands.identifier.qualify(:s)).eager_graph(:tracks).all
716
+ bands.should == [@b1, @b2]
717
+ bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
718
+
719
+ bands = @Band.order(:bands__name).from(Sequel::SQL::QualifiedIdentifier.new(:s, 'bands')).eager_graph(:tracks).all
720
+ bands.should == [@b1, @b2]
721
+ bands.map{|x| x.tracks}.should == [[@t1, @t2, @t3, @t4], []]
722
+ end
723
+
724
+ end
725
+
546
726
  if POSTGRES_DB.server_version >= 80300
547
727
 
548
728
  POSTGRES_DB.create_table! :test6 do