sequel 4.31.0 → 4.32.0

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