sequel 3.5.0 → 3.6.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.
- data/CHANGELOG +108 -0
- data/README.rdoc +25 -14
- data/Rakefile +20 -1
- data/doc/advanced_associations.rdoc +61 -64
- data/doc/cheat_sheet.rdoc +16 -7
- data/doc/opening_databases.rdoc +3 -3
- data/doc/prepared_statements.rdoc +1 -1
- data/doc/reflection.rdoc +2 -1
- data/doc/release_notes/3.6.0.txt +366 -0
- data/doc/schema.rdoc +19 -14
- data/lib/sequel/adapters/amalgalite.rb +5 -27
- data/lib/sequel/adapters/jdbc.rb +13 -3
- data/lib/sequel/adapters/jdbc/h2.rb +17 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +20 -7
- data/lib/sequel/adapters/mysql.rb +4 -3
- data/lib/sequel/adapters/oracle.rb +1 -1
- data/lib/sequel/adapters/postgres.rb +87 -28
- data/lib/sequel/adapters/shared/mssql.rb +47 -6
- data/lib/sequel/adapters/shared/mysql.rb +12 -31
- data/lib/sequel/adapters/shared/postgres.rb +15 -12
- data/lib/sequel/adapters/shared/sqlite.rb +18 -0
- data/lib/sequel/adapters/sqlite.rb +1 -16
- data/lib/sequel/connection_pool.rb +1 -1
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +2 -0
- data/lib/sequel/database/schema_sql.rb +1 -1
- data/lib/sequel/dataset.rb +5 -179
- data/lib/sequel/dataset/actions.rb +123 -0
- data/lib/sequel/dataset/convenience.rb +18 -10
- data/lib/sequel/dataset/features.rb +65 -0
- data/lib/sequel/dataset/prepared_statements.rb +29 -23
- data/lib/sequel/dataset/query.rb +429 -0
- data/lib/sequel/dataset/sql.rb +67 -435
- data/lib/sequel/model/associations.rb +77 -13
- data/lib/sequel/model/base.rb +30 -8
- data/lib/sequel/model/errors.rb +4 -4
- data/lib/sequel/plugins/caching.rb +38 -15
- data/lib/sequel/plugins/force_encoding.rb +18 -7
- data/lib/sequel/plugins/hook_class_methods.rb +4 -0
- data/lib/sequel/plugins/many_through_many.rb +1 -1
- data/lib/sequel/plugins/nested_attributes.rb +40 -11
- data/lib/sequel/plugins/serialization.rb +17 -3
- data/lib/sequel/plugins/validation_helpers.rb +65 -18
- data/lib/sequel/sql.rb +23 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +96 -10
- data/spec/adapters/mysql_spec.rb +19 -0
- data/spec/adapters/postgres_spec.rb +65 -2
- data/spec/adapters/sqlite_spec.rb +10 -0
- data/spec/core/core_sql_spec.rb +9 -0
- data/spec/core/database_spec.rb +8 -4
- data/spec/core/dataset_spec.rb +122 -29
- data/spec/core/expression_filters_spec.rb +17 -0
- data/spec/extensions/caching_spec.rb +43 -3
- data/spec/extensions/force_encoding_spec.rb +43 -1
- data/spec/extensions/nested_attributes_spec.rb +55 -2
- data/spec/extensions/validation_helpers_spec.rb +71 -0
- data/spec/integration/associations_test.rb +281 -0
- data/spec/integration/dataset_test.rb +383 -9
- data/spec/integration/eager_loader_test.rb +0 -65
- data/spec/integration/model_test.rb +110 -0
- data/spec/integration/plugin_test.rb +306 -0
- data/spec/integration/prepared_statement_test.rb +32 -0
- data/spec/integration/schema_test.rb +8 -3
- data/spec/integration/spec_helper.rb +1 -25
- data/spec/model/association_reflection_spec.rb +38 -0
- data/spec/model/associations_spec.rb +184 -8
- data/spec/model/eager_loading_spec.rb +23 -0
- data/spec/model/model_spec.rb +8 -0
- data/spec/model/record_spec.rb +84 -1
- metadata +9 -2
@@ -12,6 +12,14 @@ describe Sequel::Model, "caching" do
|
|
12
12
|
cache = @cache_class.new
|
13
13
|
@cache = cache
|
14
14
|
|
15
|
+
@memcached_class = Class.new(Hash) do
|
16
|
+
attr_accessor :ttl
|
17
|
+
def set(k, v, ttl); self[k] = v; @ttl = ttl; end
|
18
|
+
def get(k); if self[k] then return self[k]; else raise ArgumentError; end end
|
19
|
+
end
|
20
|
+
cache2 = @memcached_class.new
|
21
|
+
@memcached = cache2
|
22
|
+
|
15
23
|
@c = Class.new(Sequel::Model(:items))
|
16
24
|
@c.class_eval do
|
17
25
|
plugin :caching, cache
|
@@ -19,9 +27,26 @@ describe Sequel::Model, "caching" do
|
|
19
27
|
|
20
28
|
columns :name, :id
|
21
29
|
end
|
22
|
-
|
30
|
+
|
31
|
+
@c3 = Class.new(Sequel::Model(:items))
|
32
|
+
@c3.class_eval do
|
33
|
+
plugin :caching, cache2
|
34
|
+
def self.name; 'Item' end
|
35
|
+
|
36
|
+
columns :name, :id
|
37
|
+
end
|
38
|
+
|
39
|
+
@c4 = Class.new(Sequel::Model(:items))
|
40
|
+
@c4.class_eval do
|
41
|
+
plugin :caching, cache2, :ignore_exceptions => true
|
42
|
+
def self.name; 'Item' end
|
43
|
+
|
44
|
+
columns :name, :id
|
45
|
+
end
|
46
|
+
|
47
|
+
|
23
48
|
$cache_dataset_row = {:name => 'sharon', :id => 1}
|
24
|
-
@dataset = @c.dataset
|
49
|
+
@dataset = @c.dataset = @c3.dataset = @c4.dataset
|
25
50
|
$sqls = []
|
26
51
|
@dataset.extend(Module.new {
|
27
52
|
def fetch_rows(sql)
|
@@ -38,9 +63,10 @@ describe Sequel::Model, "caching" do
|
|
38
63
|
$sqls << delete_sql
|
39
64
|
end
|
40
65
|
})
|
66
|
+
|
41
67
|
@c2 = Class.new(@c) do
|
42
68
|
def self.name; 'SubItem' end
|
43
|
-
end
|
69
|
+
end
|
44
70
|
end
|
45
71
|
|
46
72
|
it "should set the model's cache store" do
|
@@ -205,4 +231,18 @@ describe Sequel::Model, "caching" do
|
|
205
231
|
"SELECT * FROM items WHERE (id = 1) LIMIT 1", \
|
206
232
|
"SELECT * FROM items WHERE (id = 4) LIMIT 1"]
|
207
233
|
end
|
234
|
+
|
235
|
+
it "should support ignore_exception option" do
|
236
|
+
c = Class.new(Sequel::Model(:items))
|
237
|
+
c.plugin :caching, @cache, :ignore_exceptions => true
|
238
|
+
Class.new(c).cache_ignore_exceptions.should == true
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should raise an exception if cache_store is memcached and ignore_exception is not enabled" do
|
242
|
+
proc{@c3[1]}.should raise_error
|
243
|
+
end
|
244
|
+
|
245
|
+
it "should rescue an exception if cache_store is memcached and ignore_exception is enabled" do
|
246
|
+
@c4[1].values.should == $cache_dataset_row
|
247
|
+
end
|
208
248
|
end
|
@@ -71,5 +71,47 @@ describe "force_encoding plugin" do
|
|
71
71
|
o.x.should == ''
|
72
72
|
o.x.encoding.should == Encoding.find('UTF-16LE')
|
73
73
|
end
|
74
|
+
|
75
|
+
specify "should work when saving new model instances" do
|
76
|
+
o = @c.new
|
77
|
+
@c.dataset = ds = MODEL_DB[:a]
|
78
|
+
def ds.first
|
79
|
+
s = 'blah'
|
80
|
+
s.force_encoding('US-ASCII')
|
81
|
+
{:id=>1, :x=>s}
|
82
|
+
end
|
83
|
+
o.save
|
84
|
+
o.x.should == 'blah'
|
85
|
+
o.x.encoding.should == @e1
|
86
|
+
end
|
87
|
+
|
88
|
+
specify "should work when refreshing model instances" do
|
89
|
+
o = @c.load(:id=>1, :x=>'as')
|
90
|
+
@c.dataset = ds = MODEL_DB[:a]
|
91
|
+
def ds.first
|
92
|
+
s = 'blah'
|
93
|
+
s.force_encoding('US-ASCII')
|
94
|
+
{:id=>1, :x=>s}
|
95
|
+
end
|
96
|
+
o.refresh
|
97
|
+
o.x.should == 'blah'
|
98
|
+
o.x.encoding.should == @e1
|
99
|
+
end
|
100
|
+
|
101
|
+
specify "should work when used with the identity_map plugin if the identity_map plugin is setup first" do
|
102
|
+
@c = Class.new(Sequel::Model) do
|
103
|
+
end
|
104
|
+
@c.columns :id, :x
|
105
|
+
@c.plugin :identity_map
|
106
|
+
@c.plugin :force_encoding, 'UTF-8'
|
107
|
+
@c.with_identity_map do
|
108
|
+
o = @c.load(:id=>1)
|
109
|
+
s = 'blah'
|
110
|
+
s.force_encoding('US-ASCII')
|
111
|
+
@c.load(:id=>1, :x=>s)
|
112
|
+
o.x.should == 'blah'
|
113
|
+
o.x.encoding.should == @e1
|
114
|
+
end
|
115
|
+
end
|
74
116
|
end
|
75
|
-
end
|
117
|
+
end
|
@@ -7,6 +7,7 @@ describe "NestedAttributes plugin" do
|
|
7
7
|
gi = lambda{i}
|
8
8
|
ii = lambda{i+=1}
|
9
9
|
ds_mod = Module.new do
|
10
|
+
def empty?; false; end
|
10
11
|
define_method(:insert) do |h|
|
11
12
|
x = ii.call
|
12
13
|
mods << [:i, first_source, h, x]
|
@@ -71,6 +72,12 @@ describe "NestedAttributes plugin" do
|
|
71
72
|
@mods.should == [[:is, :albums, {:name=>"Al"}, 1], [:is, :tags, {:name=>"T"}, 2], [:i, :at, {:album_id=>1, :tag_id=>2}, 3]]
|
72
73
|
end
|
73
74
|
|
75
|
+
it "should add new objects to the cached association array as soon as the *_attributes= method is called" do
|
76
|
+
a = @Artist.new({:name=>'Ar', :albums_attributes=>[{:name=>'Al', :tags_attributes=>[{:name=>'T'}]}]})
|
77
|
+
a.albums.should == [@Album.new(:name=>'Al')]
|
78
|
+
a.albums.first.tags.should == [@Tag.new(:name=>'T')]
|
79
|
+
end
|
80
|
+
|
74
81
|
it "should support updating many_to_one objects" do
|
75
82
|
al = @Album.load(:id=>10, :name=>'Al')
|
76
83
|
ar = @Artist.load(:id=>20, :name=>'Ar')
|
@@ -294,7 +301,53 @@ describe "NestedAttributes plugin" do
|
|
294
301
|
@mods.should == [[:is, :albums, {:name=>"Al"}, 1], [:is, :tags, {:name=>"T"}, 2], [:i, :at, {:album_id=>1, :tag_id=>2}, 3], [:is, :tags, {:name=>"T2"}, 4], [:i, :at, {:album_id=>1, :tag_id=>4}, 5]]
|
295
302
|
end
|
296
303
|
|
297
|
-
it "should
|
298
|
-
|
304
|
+
it "should return objects created/modified in the internal methods" do
|
305
|
+
@Album.nested_attributes :tags, :remove=>true, :strict=>false
|
306
|
+
objs = []
|
307
|
+
@Album.class_eval do
|
308
|
+
define_method(:nested_attributes_create){|*a| objs << [super(*a), :create]}
|
309
|
+
define_method(:nested_attributes_remove){|*a| objs << [super(*a), :remove]}
|
310
|
+
define_method(:nested_attributes_update){|*a| objs << [super(*a), :update]}
|
311
|
+
end
|
312
|
+
a = @Album.new(:name=>'Al')
|
313
|
+
a.associations[:tags] = [@Tag.load(:id=>6, :name=>'A'), @Tag.load(:id=>7, :name=>'A2')]
|
314
|
+
a.tags_attributes = [{:id=>6, :name=>'T'}, {:id=>7, :name=>'T2', :_remove=>true}, {:name=>'T3'}, {:id=>8, :name=>'T4'}, {:id=>9, :name=>'T5', :_remove=>true}]
|
315
|
+
objs.should == [[@Tag.load(:id=>6, :name=>'T'), :update], [@Tag.load(:id=>7, :name=>'A2'), :remove], [@Tag.new(:name=>'T3'), :create], [nil, :update], [nil, :remove]]
|
316
|
+
end
|
317
|
+
|
318
|
+
it "should raise an error if updating modifies the associated objects keys" do
|
319
|
+
@Artist.columns :id, :name, :artist_id
|
320
|
+
@Album.columns :id, :name, :artist_id
|
321
|
+
@Tag.columns :id, :name, :tag_id
|
322
|
+
@Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id, :primary_key=>:artist_id
|
323
|
+
@Album.many_to_one :artist, :class=>@Artist, :primary_key=>:artist_id
|
324
|
+
@Album.many_to_many :tags, :class=>@Tag, :left_key=>:album_id, :right_key=>:tag_id, :join_table=>:at, :right_primary_key=>:tag_id
|
325
|
+
@Artist.nested_attributes :albums, :destroy=>true, :remove=>true
|
326
|
+
@Album.nested_attributes :artist, :tags, :destroy=>true, :remove=>true
|
327
|
+
|
328
|
+
al = @Album.load(:id=>10, :name=>'Al', :artist_id=>25)
|
329
|
+
ar = @Artist.load(:id=>20, :name=>'Ar', :artist_id=>25)
|
330
|
+
t = @Tag.load(:id=>30, :name=>'T', :tag_id=>15)
|
331
|
+
al.associations[:artist] = ar
|
332
|
+
al.associations[:tags] = [t]
|
333
|
+
ar.associations[:albums] = [al]
|
334
|
+
proc{ar.set(:albums_attributes=>[{:id=>10, :name=>'Al2', :artist_id=>'3'}])}.should raise_error(Sequel::Error)
|
335
|
+
proc{al.set(:artist_attributes=>{:id=>20, :name=>'Ar2', :artist_id=>'3'})}.should raise_error(Sequel::Error)
|
336
|
+
proc{al.set(:tags_attributes=>[{:id=>30, :name=>'T2', :tag_id=>'3'}])}.should raise_error(Sequel::Error)
|
337
|
+
end
|
338
|
+
|
339
|
+
it "should accept a :fields option and only allow modification of those fields" do
|
340
|
+
@Tag.columns :id, :name, :number
|
341
|
+
@Album.nested_attributes :tags, :destroy=>true, :remove=>true, :fields=>[:name]
|
342
|
+
|
343
|
+
al = @Album.load(:id=>10, :name=>'Al')
|
344
|
+
t = @Tag.load(:id=>30, :name=>'T', :number=>10)
|
345
|
+
al.associations[:tags] = [t]
|
346
|
+
al.set(:tags_attributes=>[{:id=>30, :name=>'T2'}, {:name=>'T3'}])
|
347
|
+
@mods.should == []
|
348
|
+
al.save
|
349
|
+
@mods.should == [[:u, :albums, {:name=>'Al'}, '(id = 10)'], [:u, :tags, {:name=>'T2'}, '(id = 30)'], [:is, :tags, {:name=>"T3"}, 1], [:i, :at, {:album_id=>10, :tag_id=>1}, 2]]
|
350
|
+
proc{al.set(:tags_attributes=>[{:id=>30, :name=>'T2', :number=>3}])}.should raise_error(Sequel::Error)
|
351
|
+
proc{al.set(:tags_attributes=>[{:name=>'T2', :number=>3}])}.should raise_error(Sequel::Error)
|
299
352
|
end
|
300
353
|
end
|
@@ -55,6 +55,13 @@ describe "Sequel::Plugins::ValidationHelpers" do
|
|
55
55
|
@m.value = '1_1'
|
56
56
|
@m.should be_valid
|
57
57
|
end
|
58
|
+
|
59
|
+
specify "should allow a proc for the :message option" do
|
60
|
+
@c.set_validations{validates_format(/.+_.+/, :value, :message=>proc{|f| "doesn't match #{f.inspect}"})}
|
61
|
+
@m.value = 'abc_'
|
62
|
+
@m.should_not be_valid
|
63
|
+
@m.errors.should == {:value=>["doesn't match /.+_.+/"]}
|
64
|
+
end
|
58
65
|
|
59
66
|
specify "should take multiple attributes in the same call" do
|
60
67
|
@c.columns :value, :value2
|
@@ -65,6 +72,70 @@ describe "Sequel::Plugins::ValidationHelpers" do
|
|
65
72
|
@m.value2 = 1
|
66
73
|
@m.should be_valid
|
67
74
|
end
|
75
|
+
|
76
|
+
specify "should support modifying default options for all models" do
|
77
|
+
@c.set_validations{validates_presence(:value)}
|
78
|
+
@m.should_not be_valid
|
79
|
+
@m.errors.should == {:value=>['is not present']}
|
80
|
+
o = Sequel::Plugins::ValidationHelpers::DEFAULT_OPTIONS[:presence].dup
|
81
|
+
Sequel::Plugins::ValidationHelpers::DEFAULT_OPTIONS[:presence][:message] = lambda{"was not entered"}
|
82
|
+
@m.should_not be_valid
|
83
|
+
@m.errors.should == {:value=>["was not entered"]}
|
84
|
+
@m.value = 1
|
85
|
+
@m.should be_valid
|
86
|
+
|
87
|
+
@m.values.clear
|
88
|
+
Sequel::Plugins::ValidationHelpers::DEFAULT_OPTIONS[:presence][:allow_missing] = true
|
89
|
+
@m.should be_valid
|
90
|
+
@m.value = nil
|
91
|
+
@m.should_not be_valid
|
92
|
+
@m.errors.should == {:value=>["was not entered"]}
|
93
|
+
|
94
|
+
|
95
|
+
c = Class.new(Sequel::Model)
|
96
|
+
c.class_eval do
|
97
|
+
plugin :validation_helpers
|
98
|
+
set_columns([:value])
|
99
|
+
def validate
|
100
|
+
validates_presence(:value)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
m = c.new(:value=>nil)
|
104
|
+
m.should_not be_valid
|
105
|
+
m.errors.should == {:value=>["was not entered"]}
|
106
|
+
Sequel::Plugins::ValidationHelpers::DEFAULT_OPTIONS[:presence] = o
|
107
|
+
end
|
108
|
+
|
109
|
+
specify "should support modifying default validation options for a particular model" do
|
110
|
+
@c.set_validations{validates_presence(:value)}
|
111
|
+
@m.should_not be_valid
|
112
|
+
@m.errors.should == {:value=>['is not present']}
|
113
|
+
@c.class_eval do
|
114
|
+
def default_validation_helpers_options(type)
|
115
|
+
{:allow_missing=>true, :message=>proc{'was not entered'}}
|
116
|
+
end
|
117
|
+
end
|
118
|
+
@m.value = nil
|
119
|
+
@m.should_not be_valid
|
120
|
+
@m.errors.should == {:value=>["was not entered"]}
|
121
|
+
@m.value = 1
|
122
|
+
@m.should be_valid
|
123
|
+
|
124
|
+
@m.values.clear
|
125
|
+
@m.should be_valid
|
126
|
+
|
127
|
+
c = Class.new(Sequel::Model)
|
128
|
+
c.class_eval do
|
129
|
+
plugin :validation_helpers
|
130
|
+
attr_accessor :value
|
131
|
+
def validate
|
132
|
+
validates_presence(:value)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
m = c.new
|
136
|
+
m.should_not be_valid
|
137
|
+
m.errors.should == {:value=>['is not present']}
|
138
|
+
end
|
68
139
|
|
69
140
|
specify "should support validates_exact_length" do
|
70
141
|
@c.set_validations{validates_exact_length(3, :value)}
|
@@ -0,0 +1,281 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper.rb')
|
2
|
+
|
3
|
+
shared_examples_for "regular and composite key associations" do
|
4
|
+
specify "should return no objects if none are associated" do
|
5
|
+
@album.artist.should == nil
|
6
|
+
@artist.albums.should == []
|
7
|
+
@album.tags.should == []
|
8
|
+
@tag.albums.should == []
|
9
|
+
end
|
10
|
+
|
11
|
+
specify "should have add and set methods work any associated objects" do
|
12
|
+
@album.update(:artist => @artist)
|
13
|
+
@album.add_tag(@tag)
|
14
|
+
|
15
|
+
@album.reload
|
16
|
+
@artist.reload
|
17
|
+
@tag.reload
|
18
|
+
|
19
|
+
@album.artist.should == @artist
|
20
|
+
@artist.albums.should == [@album]
|
21
|
+
@album.tags.should == [@tag]
|
22
|
+
@tag.albums.should == [@album]
|
23
|
+
end
|
24
|
+
|
25
|
+
specify "should have remove methods work" do
|
26
|
+
@album.update(:artist => @artist)
|
27
|
+
@album.add_tag(@tag)
|
28
|
+
|
29
|
+
@album.update(:artist => nil)
|
30
|
+
@album.remove_tag(@tag)
|
31
|
+
|
32
|
+
@album.reload
|
33
|
+
@artist.reload
|
34
|
+
@tag.reload
|
35
|
+
|
36
|
+
@album.artist.should == nil
|
37
|
+
@artist.albums.should == []
|
38
|
+
@album.tags.should == []
|
39
|
+
@tag.albums.should == []
|
40
|
+
end
|
41
|
+
|
42
|
+
specify "should have remove_all methods work" do
|
43
|
+
@artist.add_album(@album)
|
44
|
+
@album.add_tag(@tag)
|
45
|
+
|
46
|
+
@artist.remove_all_albums
|
47
|
+
@album.remove_all_tags
|
48
|
+
|
49
|
+
@album.reload
|
50
|
+
@artist.reload
|
51
|
+
@tag.reload
|
52
|
+
|
53
|
+
@album.artist.should == nil
|
54
|
+
@artist.albums.should == []
|
55
|
+
@album.tags.should == []
|
56
|
+
@tag.albums.should == []
|
57
|
+
end
|
58
|
+
|
59
|
+
specify "should eager load via eager correctly" do
|
60
|
+
@album.update(:artist => @artist)
|
61
|
+
@album.add_tag(@tag)
|
62
|
+
|
63
|
+
a = Artist.eager(:albums=>:tags).all
|
64
|
+
a.should == [@artist]
|
65
|
+
a.first.albums.should == [@album]
|
66
|
+
a.first.albums.first.tags.should == [@tag]
|
67
|
+
|
68
|
+
a = Tag.eager(:albums=>:artist).all
|
69
|
+
a.should == [@tag]
|
70
|
+
a.first.albums.should == [@album]
|
71
|
+
a.first.albums.first.artist.should == @artist
|
72
|
+
end
|
73
|
+
|
74
|
+
specify "should eager load via eager_graph correctly" do
|
75
|
+
@album.update(:artist => @artist)
|
76
|
+
@album.add_tag(@tag)
|
77
|
+
|
78
|
+
a = Artist.eager_graph(:albums=>:tags).all
|
79
|
+
a.should == [@artist]
|
80
|
+
a.first.albums.should == [@album]
|
81
|
+
a.first.albums.first.tags.should == [@tag]
|
82
|
+
|
83
|
+
a = Tag.eager_graph(:albums=>:artist).all
|
84
|
+
a.should == [@tag]
|
85
|
+
a.first.albums.should == [@album]
|
86
|
+
a.first.albums.first.artist.should == @artist
|
87
|
+
end
|
88
|
+
|
89
|
+
specify "should work with a many_through_many association" do
|
90
|
+
@album.update(:artist => @artist)
|
91
|
+
@album.add_tag(@tag)
|
92
|
+
|
93
|
+
@album.reload
|
94
|
+
@artist.reload
|
95
|
+
@tag.reload
|
96
|
+
|
97
|
+
@album.tags.should == [@tag]
|
98
|
+
|
99
|
+
a = Artist.eager(:tags).all
|
100
|
+
a.should == [@artist]
|
101
|
+
a.first.tags.should == [@tag]
|
102
|
+
|
103
|
+
a = Artist.eager_graph(:tags).all
|
104
|
+
a.should == [@artist]
|
105
|
+
a.first.tags.should == [@tag]
|
106
|
+
|
107
|
+
a = Album.eager(:artist=>:tags).all
|
108
|
+
a.should == [@album]
|
109
|
+
a.first.artist.should == @artist
|
110
|
+
a.first.artist.tags.should == [@tag]
|
111
|
+
|
112
|
+
a = Album.eager_graph(:artist=>:tags).all
|
113
|
+
a.should == [@album]
|
114
|
+
a.first.artist.should == @artist
|
115
|
+
a.first.artist.tags.should == [@tag]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "Sequel::Model Simple Associations" do
|
120
|
+
before do
|
121
|
+
@db = INTEGRATION_DB
|
122
|
+
@db.create_table!(:artists) do
|
123
|
+
primary_key :id
|
124
|
+
String :name
|
125
|
+
end
|
126
|
+
@db.create_table!(:albums) do
|
127
|
+
primary_key :id
|
128
|
+
String :name
|
129
|
+
foreign_key :artist_id, :artists
|
130
|
+
end
|
131
|
+
@db.create_table!(:tags) do
|
132
|
+
primary_key :id
|
133
|
+
String :name
|
134
|
+
end
|
135
|
+
@db.create_table!(:albums_tags) do
|
136
|
+
foreign_key :album_id, :albums
|
137
|
+
foreign_key :tag_id, :tags
|
138
|
+
end
|
139
|
+
class ::Artist < Sequel::Model(@db)
|
140
|
+
one_to_many :albums
|
141
|
+
plugin :many_through_many
|
142
|
+
Artist.many_through_many :tags, [[:albums, :artist_id, :id], [:albums_tags, :album_id, :tag_id]]
|
143
|
+
end
|
144
|
+
class ::Album < Sequel::Model(@db)
|
145
|
+
many_to_one :artist
|
146
|
+
many_to_many :tags
|
147
|
+
end
|
148
|
+
class ::Tag < Sequel::Model(@db)
|
149
|
+
many_to_many :albums
|
150
|
+
end
|
151
|
+
@album = Album.create(:name=>'Al')
|
152
|
+
@artist = Artist.create(:name=>'Ar')
|
153
|
+
@tag = Tag.create(:name=>'T')
|
154
|
+
end
|
155
|
+
after do
|
156
|
+
@db.drop_table(:albums_tags, :tags, :albums, :artists)
|
157
|
+
[:Tag, :Album, :Artist].each{|x| Object.send(:remove_const, x)}
|
158
|
+
end
|
159
|
+
|
160
|
+
it_should_behave_like "regular and composite key associations"
|
161
|
+
|
162
|
+
specify "should have add method accept hashes and create new records" do
|
163
|
+
@artist.remove_all_albums
|
164
|
+
Album.delete
|
165
|
+
@album = @artist.add_album(:name=>'Al2')
|
166
|
+
Album.first[:name].should == 'Al2'
|
167
|
+
@artist.albums_dataset.first[:name].should == 'Al2'
|
168
|
+
|
169
|
+
@album.remove_all_tags
|
170
|
+
Tag.delete
|
171
|
+
@album.add_tag(:name=>'T2')
|
172
|
+
Tag.first[:name].should == 'T2'
|
173
|
+
@album.tags_dataset.first[:name].should == 'T2'
|
174
|
+
end
|
175
|
+
|
176
|
+
specify "should have remove method accept primary key and remove related album" do
|
177
|
+
@artist.add_album(@album)
|
178
|
+
@artist.reload.remove_album(@album.id)
|
179
|
+
@artist.reload.albums.should == []
|
180
|
+
|
181
|
+
@album.add_tag(@tag)
|
182
|
+
@album.reload.remove_tag(@tag.id)
|
183
|
+
@tag.reload.albums.should == []
|
184
|
+
end
|
185
|
+
|
186
|
+
specify "should have remove method raise an error for one_to_many records if the object isn't already associated" do
|
187
|
+
proc{@artist.remove_album(@album.id)}.should raise_error(Sequel::Error)
|
188
|
+
proc{@artist.remove_album(@album)}.should raise_error(Sequel::Error)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe "Sequel::Model Composite Key Associations" do
|
193
|
+
before do
|
194
|
+
@db = INTEGRATION_DB
|
195
|
+
@db.create_table!(:artists) do
|
196
|
+
Integer :id1
|
197
|
+
Integer :id2
|
198
|
+
String :name
|
199
|
+
primary_key [:id1, :id2]
|
200
|
+
end
|
201
|
+
@db.create_table!(:albums) do
|
202
|
+
Integer :id1
|
203
|
+
Integer :id2
|
204
|
+
String :name
|
205
|
+
Integer :artist_id1
|
206
|
+
Integer :artist_id2
|
207
|
+
foreign_key [:artist_id1, :artist_id2], :artists
|
208
|
+
primary_key [:id1, :id2]
|
209
|
+
end
|
210
|
+
@db.create_table!(:tags) do
|
211
|
+
Integer :id1
|
212
|
+
Integer :id2
|
213
|
+
String :name
|
214
|
+
primary_key [:id1, :id2]
|
215
|
+
end
|
216
|
+
@db.create_table!(:albums_tags) do
|
217
|
+
Integer :album_id1
|
218
|
+
Integer :album_id2
|
219
|
+
Integer :tag_id1
|
220
|
+
Integer :tag_id2
|
221
|
+
foreign_key [:album_id1, :album_id2], :albums
|
222
|
+
foreign_key [:tag_id1, :tag_id2], :tags
|
223
|
+
end
|
224
|
+
class ::Artist < Sequel::Model(@db)
|
225
|
+
set_primary_key :id1, :id2
|
226
|
+
unrestrict_primary_key
|
227
|
+
one_to_many :albums, :key=>[:artist_id1, :artist_id2]
|
228
|
+
plugin :many_through_many
|
229
|
+
Artist.many_through_many :tags, [[:albums, [:artist_id1, :artist_id2], [:id1, :id2]], [:albums_tags, [:album_id1, :album_id2], [:tag_id1, :tag_id2]]]
|
230
|
+
end
|
231
|
+
class ::Album < Sequel::Model(@db)
|
232
|
+
set_primary_key :id1, :id2
|
233
|
+
unrestrict_primary_key
|
234
|
+
many_to_one :artist, :key=>[:artist_id1, :artist_id2]
|
235
|
+
many_to_many :tags, :left_key=>[:album_id1, :album_id2], :right_key=>[:tag_id1, :tag_id2]
|
236
|
+
end
|
237
|
+
class ::Tag < Sequel::Model(@db)
|
238
|
+
set_primary_key :id1, :id2
|
239
|
+
unrestrict_primary_key
|
240
|
+
many_to_many :albums, :right_key=>[:album_id1, :album_id2], :left_key=>[:tag_id1, :tag_id2]
|
241
|
+
end
|
242
|
+
@album = Album.create(:name=>'Al', :id1=>1, :id2=>2)
|
243
|
+
@artist = Artist.create(:name=>'Ar', :id1=>3, :id2=>4)
|
244
|
+
@tag = Tag.create(:name=>'T', :id1=>5, :id2=>6)
|
245
|
+
end
|
246
|
+
after do
|
247
|
+
@db.drop_table(:albums_tags, :tags, :albums, :artists)
|
248
|
+
[:Tag, :Album, :Artist].each{|x| Object.send(:remove_const, x)}
|
249
|
+
end
|
250
|
+
|
251
|
+
it_should_behave_like "regular and composite key associations"
|
252
|
+
|
253
|
+
specify "should have add method accept hashes and create new records" do
|
254
|
+
@artist.remove_all_albums
|
255
|
+
Album.delete
|
256
|
+
@artist.add_album(:id1=>1, :id2=>2, :name=>'Al2')
|
257
|
+
Album.first[:name].should == 'Al2'
|
258
|
+
@artist.albums_dataset.first[:name].should == 'Al2'
|
259
|
+
|
260
|
+
@album.remove_all_tags
|
261
|
+
Tag.delete
|
262
|
+
@album.add_tag(:id1=>1, :id2=>2, :name=>'T2')
|
263
|
+
Tag.first[:name].should == 'T2'
|
264
|
+
@album.tags_dataset.first[:name].should == 'T2'
|
265
|
+
end
|
266
|
+
|
267
|
+
specify "should have remove method accept primary key and remove related album" do
|
268
|
+
@artist.add_album(@album)
|
269
|
+
@artist.reload.remove_album([@album.id1, @album.id2])
|
270
|
+
@artist.reload.albums.should == []
|
271
|
+
|
272
|
+
@album.add_tag(@tag)
|
273
|
+
@album.reload.remove_tag([@tag.id1, @tag.id2])
|
274
|
+
@tag.reload.albums.should == []
|
275
|
+
end
|
276
|
+
|
277
|
+
specify "should have remove method raise an error for one_to_many records if the object isn't already associated" do
|
278
|
+
proc{@artist.remove_album([@album.id1, @album.id2])}.should raise_error(Sequel::Error)
|
279
|
+
proc{@artist.remove_album(@album)}.should raise_error(Sequel::Error)
|
280
|
+
end
|
281
|
+
end
|