sequel 3.8.0 → 3.9.0

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