sequel 4.31.0 → 4.32.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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +24 -0
  3. data/Rakefile +17 -15
  4. data/doc/association_basics.rdoc +7 -3
  5. data/doc/opening_databases.rdoc +7 -0
  6. data/doc/release_notes/4.32.0.txt +132 -0
  7. data/doc/schema_modification.rdoc +1 -1
  8. data/doc/security.rdoc +70 -26
  9. data/doc/testing.rdoc +1 -0
  10. data/lib/sequel/adapters/jdbc.rb +2 -1
  11. data/lib/sequel/adapters/postgres.rb +3 -4
  12. data/lib/sequel/adapters/shared/mysql.rb +14 -1
  13. data/lib/sequel/adapters/shared/sqlite.rb +2 -2
  14. data/lib/sequel/extensions/_pretty_table.rb +2 -0
  15. data/lib/sequel/extensions/arbitrary_servers.rb +2 -0
  16. data/lib/sequel/extensions/columns_introspection.rb +2 -0
  17. data/lib/sequel/extensions/connection_validator.rb +2 -0
  18. data/lib/sequel/extensions/constraint_validations.rb +2 -0
  19. data/lib/sequel/extensions/core_extensions.rb +1 -5
  20. data/lib/sequel/extensions/current_datetime_timestamp.rb +2 -0
  21. data/lib/sequel/extensions/dataset_source_alias.rb +2 -0
  22. data/lib/sequel/extensions/date_arithmetic.rb +2 -0
  23. data/lib/sequel/extensions/empty_array_consider_nulls.rb +3 -1
  24. data/lib/sequel/extensions/error_sql.rb +2 -0
  25. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  26. data/lib/sequel/extensions/filter_having.rb +2 -0
  27. data/lib/sequel/extensions/from_block.rb +2 -0
  28. data/lib/sequel/extensions/graph_each.rb +2 -0
  29. data/lib/sequel/extensions/hash_aliases.rb +2 -0
  30. data/lib/sequel/extensions/inflector.rb +2 -0
  31. data/lib/sequel/extensions/looser_typecasting.rb +3 -1
  32. data/lib/sequel/extensions/meta_def.rb +2 -0
  33. data/lib/sequel/extensions/migration.rb +4 -0
  34. data/lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb +2 -0
  35. data/lib/sequel/extensions/named_timezones.rb +2 -0
  36. data/lib/sequel/extensions/no_auto_literal_strings.rb +84 -0
  37. data/lib/sequel/extensions/null_dataset.rb +2 -0
  38. data/lib/sequel/extensions/pagination.rb +2 -0
  39. data/lib/sequel/extensions/pg_array.rb +2 -4
  40. data/lib/sequel/extensions/pg_array_ops.rb +2 -0
  41. data/lib/sequel/extensions/pg_enum.rb +2 -0
  42. data/lib/sequel/extensions/pg_hstore.rb +2 -4
  43. data/lib/sequel/extensions/pg_hstore_ops.rb +2 -0
  44. data/lib/sequel/extensions/pg_inet.rb +2 -4
  45. data/lib/sequel/extensions/pg_inet_ops.rb +2 -0
  46. data/lib/sequel/extensions/pg_interval.rb +2 -4
  47. data/lib/sequel/extensions/pg_json.rb +4 -4
  48. data/lib/sequel/extensions/pg_json_ops.rb +3 -0
  49. data/lib/sequel/extensions/pg_loose_count.rb +2 -0
  50. data/lib/sequel/extensions/pg_range.rb +2 -4
  51. data/lib/sequel/extensions/pg_range_ops.rb +2 -0
  52. data/lib/sequel/extensions/pg_row.rb +2 -4
  53. data/lib/sequel/extensions/pg_row_ops.rb +2 -0
  54. data/lib/sequel/extensions/pg_static_cache_updater.rb +2 -0
  55. data/lib/sequel/extensions/pretty_table.rb +2 -0
  56. data/lib/sequel/extensions/query.rb +3 -0
  57. data/lib/sequel/extensions/query_literals.rb +7 -5
  58. data/lib/sequel/extensions/round_timestamps.rb +4 -3
  59. data/lib/sequel/extensions/schema_caching.rb +2 -0
  60. data/lib/sequel/extensions/schema_dumper.rb +2 -0
  61. data/lib/sequel/extensions/select_remove.rb +2 -0
  62. data/lib/sequel/extensions/sequel_3_dataset_methods.rb +2 -0
  63. data/lib/sequel/extensions/server_block.rb +3 -0
  64. data/lib/sequel/extensions/set_overrides.rb +2 -0
  65. data/lib/sequel/extensions/split_array_nil.rb +2 -0
  66. data/lib/sequel/extensions/thread_local_timezones.rb +2 -0
  67. data/lib/sequel/extensions/to_dot.rb +2 -0
  68. data/lib/sequel/model/associations.rb +95 -55
  69. data/lib/sequel/plugins/association_pks.rb +58 -33
  70. data/lib/sequel/plugins/eager_each.rb +22 -0
  71. data/lib/sequel/plugins/pg_typecast_on_load.rb +3 -2
  72. data/lib/sequel/plugins/tactical_eager_loading.rb +44 -3
  73. data/lib/sequel/version.rb +2 -2
  74. data/spec/adapters/mysql_spec.rb +34 -6
  75. data/spec/adapters/oracle_spec.rb +1 -1
  76. data/spec/bin_spec.rb +2 -2
  77. data/spec/core/dataset_spec.rb +7 -0
  78. data/spec/extensions/association_pks_spec.rb +38 -0
  79. data/spec/extensions/class_table_inheritance_spec.rb +24 -0
  80. data/spec/extensions/eager_each_spec.rb +25 -1
  81. data/spec/extensions/no_auto_literal_strings_spec.rb +65 -0
  82. data/spec/extensions/pg_range_spec.rb +1 -0
  83. data/spec/extensions/spec_helper.rb +5 -5
  84. data/spec/extensions/tactical_eager_loading_spec.rb +71 -17
  85. data/spec/integration/associations_test.rb +77 -62
  86. data/spec/integration/dataset_test.rb +3 -3
  87. data/spec/integration/plugin_test.rb +22 -0
  88. data/spec/integration/prepared_statement_test.rb +8 -8
  89. data/spec/integration/spec_helper.rb +4 -0
  90. data/spec/model/association_reflection_spec.rb +30 -0
  91. data/spec/model/associations_spec.rb +177 -16
  92. metadata +6 -2
@@ -26,7 +26,6 @@ describe "Sequel::Plugins::EagerEach" do
26
26
  it "should make #each on an eager_graph dataset do eager loading" do
27
27
  a = []
28
28
  ds = @c.eager_graph(:children)
29
- ds._fetch = []
30
29
  ds._fetch = [{:id=>1, :parent_id=>nil, :children_id=>3, :children_parent_id=>1}, {:id=>1, :parent_id=>nil, :children_id=>4, :children_parent_id=>1}, {:id=>2, :parent_id=>nil, :children_id=>5, :children_parent_id=>2}, {:id=>2, :parent_id=>nil, :children_id=>6, :children_parent_id=>2}]
31
30
  ds.each{|c| a << c}
32
31
  a.must_equal [@c.load(:id=>1, :parent_id=>nil), @c.load(:id=>2, :parent_id=>nil)]
@@ -34,6 +33,31 @@ describe "Sequel::Plugins::EagerEach" do
34
33
  @c.db.sqls.must_equal ['SELECT items.id, items.parent_id, children.id AS children_id, children.parent_id AS children_parent_id FROM items LEFT OUTER JOIN items AS children ON (children.parent_id = items.id)']
35
34
  end
36
35
 
36
+ it "should make #first on an eager dataset do eager loading" do
37
+ ds = @c.eager(:children)
38
+ ds._fetch = [{:id=>1, :parent_id=>nil}]
39
+ @c.dataset._fetch = [{:id=>3, :parent_id=>1}, {:id=>4, :parent_id=>1}]
40
+ a = ds.first
41
+ a.values.must_equal(:id=>1, :parent_id=>nil)
42
+ a.associations[:children].must_equal [@c.load(:id=>3, :parent_id=>1), @c.load(:id=>4, :parent_id=>1)]
43
+ @c.db.sqls.must_equal ['SELECT * FROM items LIMIT 1','SELECT * FROM items WHERE (items.parent_id IN (1))']
44
+ end
45
+
46
+ it "should make #first on an eager_graph dataset do eager loading" do
47
+ ds = @c.eager_graph(:children)
48
+ ds._fetch = [[{:id=>1, :parent_id=>nil, :children_id=>3, :children_parent_id=>1}], [{:id=>1, :parent_id=>nil, :children_id=>3, :children_parent_id=>1}, {:id=>1, :parent_id=>nil, :children_id=>4, :children_parent_id=>1}]]
49
+ a = ds.first
50
+ a.values.must_equal(:id=>1, :parent_id=>nil)
51
+ a.associations[:children].must_equal [@c.load(:id=>3, :parent_id=>1), @c.load(:id=>4, :parent_id=>1)]
52
+ @c.db.sqls.must_equal ['SELECT items.id, items.parent_id, children.id AS children_id, children.parent_id AS children_parent_id FROM items LEFT OUTER JOIN items AS children ON (children.parent_id = items.id) LIMIT 1',
53
+ 'SELECT items.id, items.parent_id, children.id AS children_id, children.parent_id AS children_parent_id FROM items LEFT OUTER JOIN items AS children ON (children.parent_id = items.id) WHERE (items.id = 1)']
54
+ end
55
+
56
+ it "should make #first on a non-eager dataset work correctly" do
57
+ @c.dataset._fetch = [{:id=>1, :parent_id=>nil}]
58
+ @c.first.must_equal @c.load(:id=>1, :parent_id=>nil)
59
+ end
60
+
37
61
  it "should not attempt to eager load when getting the columns" do
38
62
  ds = @c.eager(:children)
39
63
  def ds.all; raise; end
@@ -0,0 +1,65 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe "no_auto_literal_strings extension" do
4
+ before do
5
+ @ds = Sequel.mock[:t].extension(:no_auto_literal_strings)
6
+ end
7
+
8
+ it "should raise exception for plain strings in filter methods" do
9
+ proc{@ds.where("a")}.must_raise Sequel::Error
10
+ proc{@ds.having("a")}.must_raise Sequel::Error
11
+ proc{@ds.filter("a")}.must_raise Sequel::Error
12
+ proc{@ds.exclude_where("a")}.must_raise Sequel::Error
13
+ proc{@ds.exclude_having("a")}.must_raise Sequel::Error
14
+ proc{@ds.and("a")}.must_raise Sequel::Error
15
+ proc{@ds.where(:a).or("a")}.must_raise Sequel::Error
16
+ proc{@ds.first("a")}.must_raise Sequel::Error
17
+ proc{@ds.order(:a).last("a")}.must_raise Sequel::Error
18
+ proc{@ds["a"]}.must_raise Sequel::Error
19
+ end
20
+
21
+ it "should raise exception for plain strings arrays in filter methods" do
22
+ proc{@ds.where(["a"])}.must_raise Sequel::Error
23
+ end
24
+
25
+ it "should handle explicit literal strings in filter methods" do
26
+ @ds.where(Sequel.lit("a")).sql.must_equal 'SELECT * FROM t WHERE (a)'
27
+ @ds.having(Sequel.lit("a")).sql.must_equal 'SELECT * FROM t HAVING (a)'
28
+ @ds.filter(Sequel.lit("a")).sql.must_equal 'SELECT * FROM t WHERE (a)'
29
+ @ds.exclude_where(Sequel.lit("a")).sql.must_equal 'SELECT * FROM t WHERE NOT (a)'
30
+ @ds.exclude_having(Sequel.lit("a")).sql.must_equal 'SELECT * FROM t HAVING NOT (a)'
31
+ @ds.and(Sequel.lit("a")).sql.must_equal 'SELECT * FROM t WHERE (a)'
32
+ @ds.where(:a).or(Sequel.lit("a")).sql.must_equal 'SELECT * FROM t WHERE (a OR (a))'
33
+ @ds.first(Sequel.lit("a"))
34
+ @ds.order(:a).last(Sequel.lit("a"))
35
+ @ds[Sequel.lit("a")]
36
+ @ds.db.sqls.must_equal ["SELECT * FROM t WHERE (a) LIMIT 1",
37
+ "SELECT * FROM t WHERE (a) ORDER BY a DESC LIMIT 1",
38
+ "SELECT * FROM t WHERE (a) LIMIT 1"]
39
+ end
40
+
41
+ it "should handle literal strings in arrays in filter methods" do
42
+ @ds.where([Sequel.lit("a")]).sql.must_equal 'SELECT * FROM t WHERE (a)'
43
+ end
44
+
45
+ it "should handle other objects in filter methods" do
46
+ @ds.where(:a).sql.must_equal 'SELECT * FROM t WHERE a'
47
+ end
48
+
49
+ it "should raise exception for plain strings in update methods" do
50
+ proc{@ds.update("a = a + 1")}.must_raise Sequel::Error
51
+ proc{@ds.update_sql("a = a + 1")}.must_raise Sequel::Error
52
+ end
53
+
54
+ it "should handle explicit literal strings in update methods" do
55
+ @ds.update_sql(Sequel.lit("a = a + 1")).must_equal "UPDATE t SET a = a + 1"
56
+ @ds.update(Sequel.lit("a = a + 1"))
57
+ @ds.db.sqls.must_equal ["UPDATE t SET a = a + 1"]
58
+ end
59
+
60
+ it "should handle other objects in update methods" do
61
+ @ds.update_sql(:a=>:a).must_equal "UPDATE t SET a = a"
62
+ @ds.update(:a=>:a)
63
+ @ds.db.sqls.must_equal ["UPDATE t SET a = a"]
64
+ end
65
+ end
@@ -38,6 +38,7 @@ describe "pg_range extension" do
38
38
  @db.literal(@R.new(1, nil)).must_equal "'[1,]'"
39
39
  @db.literal(@R.new(1, 2, :db_type=>'int8range')).must_equal "int8range(1,2,'[]')"
40
40
  @db.literal(@R.new(nil, nil, :empty=>true)).must_equal "'empty'"
41
+ @db.literal(@R.new(nil, nil, :empty=>true, :db_type=>'int8range')).must_equal "'empty'::int8range"
41
42
  @db.literal(@R.new("", 2)).must_equal "'[\"\",2]'"
42
43
  end
43
44
 
@@ -1,15 +1,15 @@
1
1
  require 'rubygems'
2
2
 
3
- gem 'minitest'
4
- require 'minitest/autorun'
5
- require 'minitest/hooks/default'
6
- require 'minitest/shared_description'
7
-
8
3
  if ENV['COVERAGE']
9
4
  require File.join(File.dirname(File.expand_path(__FILE__)), "../sequel_coverage")
10
5
  SimpleCov.sequel_coverage(:filter=>%r{lib/sequel/(extensions|plugins)/\w+\.rb\z})
11
6
  end
12
7
 
8
+ gem 'minitest'
9
+ require 'minitest/autorun'
10
+ require 'minitest/hooks/default'
11
+ require 'minitest/shared_description'
12
+
13
13
  unless Object.const_defined?('Sequel') && Sequel.const_defined?('Model')
14
14
  $:.unshift(File.join(File.dirname(File.expand_path(__FILE__)), "../../lib/"))
15
15
  require 'sequel'
@@ -1,8 +1,22 @@
1
1
  require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
3
  describe "Sequel::Plugins::TacticalEagerLoading" do
4
+ def sql_match(*args)
5
+ sqls = DB.sqls
6
+ sqls.length.must_equal args.length
7
+ sqls.zip(args).each do |is, should|
8
+ if should.is_a?(Regexp)
9
+ is.must_match should
10
+ else
11
+ is.must_equal should
12
+ end
13
+ end
14
+ end
15
+
16
+ attr_reader :ts
17
+
4
18
  before do
5
- class ::TacticalEagerLoadingModel < Sequel::Model
19
+ class ::TacticalEagerLoadingModel < Sequel::Model(:t)
6
20
  plugin :tactical_eager_loading
7
21
  columns :id, :parent_id
8
22
  many_to_one :parent, :class=>self
@@ -22,61 +36,101 @@ describe "Sequel::Plugins::TacticalEagerLoading" do
22
36
  @c = ::TacticalEagerLoadingModel
23
37
  @ds = TacticalEagerLoadingModel.dataset
24
38
  DB.reset
39
+ @ts = @c.all
40
+ sql_match('SELECT * FROM t')
25
41
  end
26
42
  after do
27
43
  Object.send(:remove_const, :TacticalEagerLoadingModel)
44
+ sql_match
28
45
  end
29
46
 
30
47
  it "Dataset#all should set the retrieved_by and retrieved_with attributes" do
31
- ts = @c.all
32
48
  ts.map{|x| [x.retrieved_by, x.retrieved_with]}.must_equal [[@ds,ts], [@ds,ts], [@ds,ts], [@ds,ts]]
33
49
  end
34
50
 
35
51
  it "Dataset#all shouldn't raise an error if a Sequel::Model instance is not returned" do
36
52
  @c.naked.all
53
+ sql_match('SELECT * FROM t')
37
54
  end
38
55
 
39
56
  it "association getter methods should eagerly load the association if the association isn't cached" do
40
- DB.sqls.length.must_equal 0
41
- ts = @c.all
42
- DB.sqls.length.must_equal 1
43
57
  ts.map{|x| x.parent}.must_equal [ts[2], ts[3], nil, nil]
44
- DB.sqls.length.must_equal 1
58
+ sql_match(/\ASELECT \* FROM t WHERE \(t\.id IN \(10[12], 10[12]\)\)\z/)
45
59
  ts.map{|x| x.children}.must_equal [[], [], [ts[0]], [ts[1]]]
46
- DB.sqls.length.must_equal 1
60
+ sql_match(/\ASELECT \* FROM t WHERE \(t\.parent_id IN/)
47
61
  end
48
62
 
49
63
  it "association getter methods should not eagerly load the association if the association is cached" do
50
- DB.sqls.length.must_equal 0
51
- ts = @c.all
52
- DB.sqls.length.must_equal 1
53
64
  ts.map{|x| x.parent}.must_equal [ts[2], ts[3], nil, nil]
65
+ sql_match(/\ASELECT \* FROM t WHERE \(t\.id IN \(10[12], 10[12]\)\)\z/)
54
66
  def @ds.eager_load(*) raise end
55
67
  ts.map{|x| x.parent}.must_equal [ts[2], ts[3], nil, nil]
56
68
  end
57
69
 
70
+ it "association getter methods should not eagerly load the association if a block is given" do
71
+ ts.map{|x| x.parent{|ds| ds}}.must_equal [ts[2], ts[3], nil, nil]
72
+ sql_match('SELECT * FROM t WHERE (t.id = 101) LIMIT 1', 'SELECT * FROM t WHERE (t.id = 102) LIMIT 1')
73
+ end
74
+
75
+ it "association getter methods should not eagerly load the association if a callback proc is given" do
76
+ ts.map{|x| x.parent(:callback=>proc{|ds| ds})}.must_equal [ts[2], ts[3], nil, nil]
77
+ sql_match('SELECT * FROM t WHERE (t.id = 101) LIMIT 1', 'SELECT * FROM t WHERE (t.id = 102) LIMIT 1')
78
+ end
79
+
80
+ it "association getter methods should not eagerly load the association if true is passed" do
81
+ ts.map{|x| x.parent(true)}.must_equal [ts[2], ts[3], nil, nil]
82
+ sql_match('SELECT * FROM t WHERE id = 101', 'SELECT * FROM t WHERE id = 102')
83
+ end
84
+
85
+ it "association getter methods should not eagerly load the association if :reload=>true is passed" do
86
+ ts.map{|x| x.parent(:reload=>true)}.must_equal [ts[2], ts[3], nil, nil]
87
+ sql_match('SELECT * FROM t WHERE id = 101', 'SELECT * FROM t WHERE id = 102')
88
+ end
89
+
90
+ it "association getter methods should eagerly reload the association if :eager_reload=>true is passed" do
91
+ ts.first.parent(:reload=>true)
92
+ sql_match('SELECT * FROM t WHERE id = 101')
93
+ ts.map{|x| x.associations.fetch(:parent, 1)}.must_equal [ts[2], 1, 1, 1]
94
+ ts.first.parent(:eager_reload=>true)
95
+ sql_match(/\ASELECT \* FROM t WHERE \(t\.id IN \(10[12], 10[12]\)\)\z/)
96
+ ts.map{|x| x.associations.fetch(:parent, 1)}.must_equal [ts[2], ts[3], nil, nil]
97
+ end
98
+
99
+ it "association getter methods should support eagerly loading dependent associations via :eager" do
100
+ parents = ts.map{|x| x.parent(:eager=>:children)}
101
+ sql_match(/\ASELECT \* FROM t WHERE \(t\.id IN \(10[12], 10[12]\)\)\z/, /\ASELECT \* FROM t WHERE \(t\.parent_id IN/)
102
+ parents.must_equal [ts[2], ts[3], nil, nil]
103
+ parents[0..1].map{|x| x.children}.must_equal [[ts[0]], [ts[1]]]
104
+ end
105
+
106
+ it "association getter methods should support eager callbacks via :eager" do
107
+ parents = ts.map{|x| x.parent(:eager=>proc{|ds| ds.where{name > 'M'}.eager(:children)})}
108
+ sql_match(/\ASELECT \* FROM t WHERE \(\(t\.id IN \(10[12], 10[12]\)\) AND \(name > 'M'\)\)\z/, /\ASELECT \* FROM t WHERE \(t\.parent_id IN/)
109
+ parents.must_equal [ts[2], ts[3], nil, nil]
110
+ parents[0..1].map{|x| x.children}.must_equal [[ts[0]], [ts[1]]]
111
+ end
112
+
58
113
  it "should handle case where an association is valid on an instance, but not on all instances" do
59
114
  c = Class.new(@c)
60
115
  c.many_to_one :parent2, :class=>@c, :key=>:parent_id
61
116
  @c.dataset.row_proc = proc{|r| (r[:parent_id] == 101 ? c : @c).call(r)}
62
117
  @c.all{|x| x.parent2 if x.is_a?(c)}
118
+ sql_match('SELECT * FROM t', 'SELECT * FROM t WHERE id = 101')
63
119
  end
64
120
 
65
121
  it "association getter methods should not eagerly load the association if an instance is frozen" do
66
- ts = @c.all
67
122
  ts.first.freeze
68
- DB.sqls.length.must_equal 1
69
123
  ts.map{|x| x.parent}.must_equal [ts[2], ts[3], nil, nil]
70
- DB.sqls.length.must_equal 2
124
+ sql_match('SELECT * FROM t WHERE id = 101', 'SELECT * FROM t WHERE (t.id IN (102))')
71
125
  ts.map{|x| x.children}.must_equal [[], [], [ts[0]], [ts[1]]]
72
- DB.sqls.length.must_equal 2
126
+ sql_match('SELECT * FROM t WHERE (t.parent_id = 1)', /\ASELECT \* FROM t WHERE \(t\.parent_id IN/)
73
127
  ts.map{|x| x.parent}.must_equal [ts[2], ts[3], nil, nil]
74
- DB.sqls.length.must_equal 1
128
+ sql_match('SELECT * FROM t WHERE id = 101')
75
129
  ts.map{|x| x.children}.must_equal [[], [], [ts[0]], [ts[1]]]
76
- DB.sqls.length.must_equal 1
130
+ sql_match('SELECT * FROM t WHERE (t.parent_id = 1)')
77
131
  end
78
132
 
79
133
  it "#marshallable should make marshalling not fail" do
80
- Marshal.dump(@c.all.map{|x| x.marshallable!})
134
+ Marshal.dump(ts.map{|x| x.marshallable!})
81
135
  end
82
136
  end
@@ -1501,22 +1501,83 @@ BasicRegularAndCompositeKeyAssociations = shared_description do
1501
1501
  @album.tags.must_equal []
1502
1502
  @album.alias_tags.must_equal []
1503
1503
  @tag.albums.must_equal []
1504
+ unless @no_many_through_many
1505
+ @album.first_tag.must_equal nil
1506
+ end
1504
1507
  end
1505
1508
 
1506
- it "should have add and set methods work any associated objects" do
1509
+ it "should have set methods work" do
1510
+ # many to one
1507
1511
  @album.update(:artist => @artist)
1512
+ @album.reload.artist.must_equal @artist
1513
+ @album.update(:artist => nil)
1514
+ @album.reload.artist.must_equal nil
1515
+
1516
+ # one to one
1517
+ @artist.update(:first_album => @album)
1518
+ @artist.reload.first_album.must_equal @album
1519
+ @artist.update(:first_album => nil)
1520
+ @artist.reload.first_album.must_equal nil
1521
+
1522
+ unless @no_many_through_many
1523
+ tag = @pr.call.last
1524
+ # one through one
1525
+ @album.update(:first_tag => @tag)
1526
+ @album.reload.first_tag.must_equal @tag
1527
+ @album.update(:first_tag => @tag)
1528
+ @album.reload.first_tag.must_equal @tag
1529
+ @album.update(:first_tag => tag)
1530
+ @album.reload.first_tag.must_equal tag
1531
+ @album.update(:first_tag => nil)
1532
+ @album.reload.first_tag.must_equal nil
1533
+ @album.update(:first_tag => nil)
1534
+ @album.reload.first_tag.must_equal nil
1535
+
1536
+ # one through one with alias
1537
+ @album.update(:alias_t_tag => @tag)
1538
+ @album.reload.alias_t_tag.must_equal @tag
1539
+ @album.update(:alias_t_tag => nil)
1540
+ @album.reload.alias_t_tag.must_equal nil
1541
+ end
1542
+ end
1543
+
1544
+ it "should have add and remove methods work" do
1545
+ # one to many
1546
+ @artist.add_album(@album)
1547
+ @artist.reload.albums.must_equal [@album]
1548
+ @artist.remove_album(@album)
1549
+ @artist.reload.albums.must_equal []
1550
+
1551
+ # many to many
1508
1552
  @album.add_tag(@tag)
1509
-
1510
- @album.reload
1511
- @artist.reload
1512
- @tag.reload
1513
-
1514
- @album.artist.must_equal @artist
1515
- @artist.first_album.must_equal @album
1516
- @artist.albums.must_equal [@album]
1517
- @album.tags.must_equal [@tag]
1553
+ @album.reload.tags.must_equal [@tag]
1554
+ @tag.reload.albums.must_equal [@album]
1518
1555
  @album.alias_tags.must_equal [@tag]
1519
- @tag.albums.must_equal [@album]
1556
+ @album.remove_tag(@tag)
1557
+ @album.reload.tags.must_equal []
1558
+
1559
+ # many to many with alias
1560
+ @album.add_alias_tag(@tag)
1561
+ @album.reload.alias_tags.must_equal [@tag]
1562
+ @album.remove_alias_tag(@tag)
1563
+ @album.reload.alias_tags.must_equal []
1564
+ end
1565
+
1566
+ it "should have remove_all methods work" do
1567
+ # one to many
1568
+ @artist.add_album(@album)
1569
+ @artist.remove_all_albums
1570
+ @artist.reload.albums.must_equal []
1571
+
1572
+ # many to many
1573
+ @album.add_tag(@tag)
1574
+ @album.remove_all_tags
1575
+ @album.reload.tags.must_equal []
1576
+
1577
+ # many to many with alias
1578
+ @album.add_alias_tag(@tag)
1579
+ @album.remove_all_alias_tags
1580
+ @album.reload.alias_tags.must_equal []
1520
1581
  end
1521
1582
 
1522
1583
  it "should work correctly with prepared_statements_association plugin" do
@@ -1534,6 +1595,9 @@ BasicRegularAndCompositeKeyAssociations = shared_description do
1534
1595
  @album.tags.must_equal [@tag]
1535
1596
  @album.alias_tags.must_equal [@tag]
1536
1597
  @tag.albums.must_equal [@album]
1598
+ unless @no_many_through_many
1599
+ @album.first_tag.must_equal @tag
1600
+ end
1537
1601
  end
1538
1602
 
1539
1603
  it "should have working dataset associations" do
@@ -1599,55 +1663,6 @@ BasicRegularAndCompositeKeyAssociations = shared_description do
1599
1663
  Artist.filter(Artist.qualified_primary_key_hash(artist.pk)).albums.filter(Album.qualified_primary_key_hash(@album.pk)).tags.all.must_equal []
1600
1664
  end
1601
1665
 
1602
- it "should have remove methods work" do
1603
- @album.update(:artist => @artist)
1604
- @album.add_tag(@tag)
1605
-
1606
- @album.update(:artist => nil)
1607
- @album.remove_tag(@tag)
1608
-
1609
- @album.add_alias_tag(@tag)
1610
- @album.remove_alias_tag(@tag)
1611
-
1612
- @album.reload
1613
- @artist.reload
1614
- @tag.reload
1615
-
1616
- @album.artist.must_equal nil
1617
- @artist.albums.must_equal []
1618
- @album.tags.must_equal []
1619
- @tag.albums.must_equal []
1620
-
1621
- @album.add_alias_tag(@tag)
1622
- @album.remove_alias_tag(@tag)
1623
-
1624
- @album.reload
1625
- @album.alias_tags.must_equal []
1626
- end
1627
-
1628
- it "should have remove_all methods work" do
1629
- @artist.add_album(@album)
1630
- @album.add_tag(@tag)
1631
-
1632
- @album.remove_all_tags
1633
- @artist.remove_all_albums
1634
-
1635
- @album.reload
1636
- @artist.reload
1637
- @tag.reload
1638
-
1639
- @album.artist.must_equal nil
1640
- @artist.albums.must_equal []
1641
- @album.tags.must_equal []
1642
- @tag.albums.must_equal []
1643
-
1644
- @album.add_alias_tag(@tag)
1645
- @album.remove_all_alias_tags
1646
-
1647
- @album.reload
1648
- @album.alias_tags.must_equal []
1649
- end
1650
-
1651
1666
  it "should eager load via eager correctly" do
1652
1667
  @album.update(:artist => @artist)
1653
1668
  @album.add_tag(@tag)
@@ -1684,8 +1699,6 @@ BasicRegularAndCompositeKeyAssociations = shared_description do
1684
1699
  end
1685
1700
 
1686
1701
  RegularAndCompositeKeyAssociations = shared_description do
1687
- include BasicRegularAndCompositeKeyAssociations
1688
-
1689
1702
  describe "when filtering/excluding by associations when joining" do
1690
1703
  def self_join(c)
1691
1704
  c.join(Sequel.as(c.table_name, :b), Array(c.primary_key).zip(Array(c.primary_key))).select_all(c.table_name)
@@ -1884,6 +1897,7 @@ describe "Sequel::Model Simple Associations" do
1884
1897
  @db.drop_table?(:albums_tags, :tags, :albums, :artists)
1885
1898
  end
1886
1899
 
1900
+ include BasicRegularAndCompositeKeyAssociations
1887
1901
  include RegularAndCompositeKeyAssociations
1888
1902
 
1889
1903
  describe "with :correlated_subquery limit strategy" do
@@ -2160,6 +2174,7 @@ describe "Sequel::Model Composite Key Associations" do
2160
2174
  @db.drop_table?(:albums_tags, :tags, :albums, :artists)
2161
2175
  end
2162
2176
 
2177
+ include BasicRegularAndCompositeKeyAssociations
2163
2178
  include RegularAndCompositeKeyAssociations
2164
2179
 
2165
2180
  describe "with :correlated_subquery limit strategy" do