sequel 3.35.0 → 3.36.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +78 -0
- data/Rakefile +3 -3
- data/bin/sequel +3 -1
- data/doc/advanced_associations.rdoc +154 -11
- data/doc/migration.rdoc +18 -0
- data/doc/object_model.rdoc +541 -0
- data/doc/opening_databases.rdoc +4 -1
- data/doc/release_notes/3.36.0.txt +245 -0
- data/doc/schema_modification.rdoc +0 -6
- data/lib/sequel/adapters/do/mysql.rb +7 -0
- data/lib/sequel/adapters/jdbc.rb +11 -3
- data/lib/sequel/adapters/jdbc/mysql.rb +3 -5
- data/lib/sequel/adapters/jdbc/postgresql.rb +10 -8
- data/lib/sequel/adapters/jdbc/progress.rb +21 -0
- data/lib/sequel/adapters/mock.rb +2 -6
- data/lib/sequel/adapters/mysql.rb +3 -9
- data/lib/sequel/adapters/mysql2.rb +12 -11
- data/lib/sequel/adapters/postgres.rb +32 -40
- data/lib/sequel/adapters/shared/mssql.rb +15 -11
- data/lib/sequel/adapters/shared/mysql.rb +28 -3
- data/lib/sequel/adapters/shared/oracle.rb +5 -0
- data/lib/sequel/adapters/shared/postgres.rb +59 -5
- data/lib/sequel/adapters/shared/sqlite.rb +3 -13
- data/lib/sequel/adapters/sqlite.rb +0 -7
- data/lib/sequel/adapters/swift/mysql.rb +2 -5
- data/lib/sequel/adapters/tinytds.rb +1 -2
- data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
- data/lib/sequel/connection_pool/threaded.rb +9 -1
- data/lib/sequel/database/dataset_defaults.rb +3 -1
- data/lib/sequel/database/misc.rb +7 -1
- data/lib/sequel/database/query.rb +11 -3
- data/lib/sequel/database/schema_generator.rb +40 -9
- data/lib/sequel/database/schema_methods.rb +6 -1
- data/lib/sequel/dataset/actions.rb +5 -5
- data/lib/sequel/dataset/prepared_statements.rb +3 -1
- data/lib/sequel/dataset/query.rb +1 -1
- data/lib/sequel/extensions/migration.rb +28 -0
- data/lib/sequel/extensions/pg_auto_parameterize.rb +0 -9
- data/lib/sequel/extensions/pg_inet.rb +89 -0
- data/lib/sequel/extensions/pg_json.rb +178 -0
- data/lib/sequel/extensions/schema_dumper.rb +24 -6
- data/lib/sequel/model/associations.rb +19 -15
- data/lib/sequel/model/base.rb +11 -12
- data/lib/sequel/plugins/composition.rb +1 -2
- data/lib/sequel/plugins/eager_each.rb +59 -0
- data/lib/sequel/plugins/json_serializer.rb +41 -4
- data/lib/sequel/plugins/nested_attributes.rb +72 -52
- data/lib/sequel/plugins/optimistic_locking.rb +8 -0
- data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +271 -1
- data/spec/adapters/sqlite_spec.rb +11 -0
- data/spec/core/connection_pool_spec.rb +26 -1
- data/spec/core/database_spec.rb +19 -0
- data/spec/core/dataset_spec.rb +45 -5
- data/spec/core/expression_filters_spec.rb +31 -67
- data/spec/core/mock_adapter_spec.rb +4 -0
- data/spec/extensions/core_extensions_spec.rb +83 -0
- data/spec/extensions/eager_each_spec.rb +34 -0
- data/spec/extensions/inflector_spec.rb +0 -4
- data/spec/extensions/json_serializer_spec.rb +32 -1
- data/spec/extensions/migration_spec.rb +28 -0
- data/spec/extensions/nested_attributes_spec.rb +134 -1
- data/spec/extensions/optimistic_locking_spec.rb +15 -1
- data/spec/extensions/pg_hstore_spec.rb +1 -1
- data/spec/extensions/pg_inet_spec.rb +44 -0
- data/spec/extensions/pg_json_spec.rb +101 -0
- data/spec/extensions/prepared_statements_spec.rb +30 -0
- data/spec/extensions/rcte_tree_spec.rb +9 -0
- data/spec/extensions/schema_dumper_spec.rb +195 -7
- data/spec/extensions/serialization_spec.rb +4 -0
- data/spec/extensions/spec_helper.rb +9 -1
- data/spec/extensions/tactical_eager_loading_spec.rb +8 -0
- data/spec/integration/database_test.rb +5 -1
- data/spec/integration/prepared_statement_test.rb +20 -2
- data/spec/model/associations_spec.rb +27 -0
- data/spec/model/base_spec.rb +54 -0
- data/spec/model/model_spec.rb +6 -0
- data/spec/model/record_spec.rb +18 -0
- data/spec/rcov.opts +2 -0
- metadata +14 -3
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
2
|
+
|
3
|
+
describe "Sequel::Plugins::EagerEach" do
|
4
|
+
before do
|
5
|
+
@c = Class.new(Sequel::Model(:items))
|
6
|
+
@c.columns :id, :parent_id
|
7
|
+
@c.plugin :eager_each
|
8
|
+
@c.one_to_many :children, :class=>@c, :key=>:parent_id
|
9
|
+
@c.db.sqls
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should make #each on an eager dataset do eager loading" do
|
13
|
+
a = []
|
14
|
+
ds = @c.eager(:children)
|
15
|
+
ds._fetch = [{:id=>1, :parent_id=>nil}, {:id=>2, :parent_id=>nil}]
|
16
|
+
@c.dataset._fetch = [{:id=>3, :parent_id=>1}, {:id=>4, :parent_id=>1}, {:id=>5, :parent_id=>2}, {:id=>6, :parent_id=>2}]
|
17
|
+
ds.each{|c| a << c}
|
18
|
+
a.should == [@c.load(:id=>1, :parent_id=>nil), @c.load(:id=>2, :parent_id=>nil)]
|
19
|
+
a.map{|c| c.associations[:children]}.should == [[@c.load(:id=>3, :parent_id=>1), @c.load(:id=>4, :parent_id=>1)], [@c.load(:id=>5, :parent_id=>2), @c.load(:id=>6, :parent_id=>2)]]
|
20
|
+
@c.db.sqls.should == ['SELECT * FROM items', 'SELECT * FROM items WHERE (items.parent_id IN (1, 2))']
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should make #each on an eager_graph dataset do eager loading" do
|
24
|
+
a = []
|
25
|
+
ds = @c.eager_graph(:children)
|
26
|
+
ds._fetch = []
|
27
|
+
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}]
|
28
|
+
ds.each{|c| a << c}
|
29
|
+
a.should == [@c.load(:id=>1, :parent_id=>nil), @c.load(:id=>2, :parent_id=>nil)]
|
30
|
+
a.map{|c| c.associations[:children]}.should == [[@c.load(:id=>3, :parent_id=>1), @c.load(:id=>4, :parent_id=>1)], [@c.load(:id=>5, :parent_id=>2), @c.load(:id=>6, :parent_id=>2)]]
|
31
|
+
@c.db.sqls.should == ['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)']
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -1,8 +1,5 @@
|
|
1
1
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper')
|
2
2
|
|
3
|
-
if defined?(ActiveSupport::Inflector)
|
4
|
-
skip_warn "inflector extension: active_support inflector loaded"
|
5
|
-
else
|
6
3
|
describe String do
|
7
4
|
it "#camelize and #camelcase should transform the word to CamelCase" do
|
8
5
|
"egg_and_hams".camelize.should == "EggAndHams"
|
@@ -182,4 +179,3 @@ describe 'Default inflections' do
|
|
182
179
|
end
|
183
180
|
end
|
184
181
|
end
|
185
|
-
end
|
@@ -96,6 +96,22 @@ describe "Sequel::Plugins::JsonSerializer" do
|
|
96
96
|
@artist.id.should == 2
|
97
97
|
end
|
98
98
|
|
99
|
+
it "should support #from_json to support specific :fields" do
|
100
|
+
@album.from_json('{"name": "AS", "artist_id": 3}', :fields=>['name'])
|
101
|
+
@album.name.should == 'AS'
|
102
|
+
@album.artist_id.should == 2
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should support #from_json to support :missing=>:skip option" do
|
106
|
+
@album.from_json('{"artist_id": 3}', :fields=>['name'], :missing=>:skip)
|
107
|
+
@album.name.should == 'RF'
|
108
|
+
@album.artist_id.should == 2
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should support #from_json to support :missing=>:raise option" do
|
112
|
+
proc{@album.from_json('{"artist_id": 3}', :fields=>['name'], :missing=>:raise)}.should raise_error(Sequel::Error)
|
113
|
+
end
|
114
|
+
|
99
115
|
it "should raise an exception for json keys that aren't associations, columns, or setter methods" do
|
100
116
|
Album.send(:undef_method, :blah=)
|
101
117
|
proc{JSON.parse(@album.to_json(:include=>:blah))}.should raise_error(Sequel::Error)
|
@@ -149,7 +165,22 @@ describe "Sequel::Plugins::JsonSerializer" do
|
|
149
165
|
@album.to_json(:root=>true, :only => :name).to_s.should == '{"album":{"name":"RF"}}'
|
150
166
|
end
|
151
167
|
|
152
|
-
it "should handle the :root option to qualify a dataset of records" do
|
168
|
+
it "should handle the :root=>:both option to qualify a dataset of records" do
|
169
|
+
Album.dataset._fetch = [{:id=>1, :name=>'RF'}, {:id=>1, :name=>'RF'}]
|
170
|
+
Album.dataset.to_json(:root=>true, :only => :id).to_s.should == '{"albums":[{"album":{"id":1}},{"album":{"id":1}}]}'
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should handle the :root=>:collection option to qualify just the collection" do
|
174
|
+
Album.dataset._fetch = [{:id=>1, :name=>'RF'}, {:id=>1, :name=>'RF'}]
|
175
|
+
Album.dataset.to_json(:root=>:collection, :only => :id).to_s.should == '{"albums":[{"id":1},{"id":1}]}'
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should handle the :root=>:instance option to qualify just the instances" do
|
179
|
+
Album.dataset._fetch = [{:id=>1, :name=>'RF'}, {:id=>1, :name=>'RF'}]
|
180
|
+
Album.dataset.to_json(:root=>:instance, :only => :id).to_s.should == '[{"album":{"id":1}},{"album":{"id":1}}]'
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should handle the :root=>true option be the same as :root=>:both for backwards compatibility" do
|
153
184
|
Album.dataset._fetch = [{:id=>1, :name=>'RF'}, {:id=>1, :name=>'RF'}]
|
154
185
|
Album.dataset.to_json(:root=>true, :only => :id).to_s.should == '{"albums":[{"album":{"id":1}},{"album":{"id":1}}]}'
|
155
186
|
end
|
@@ -277,8 +277,20 @@ describe "Sequel::IntegerMigrator" do
|
|
277
277
|
@db.creates.should == [1111, 2222, 3333]
|
278
278
|
@db.version.should == 3
|
279
279
|
@db.sqls.map{|x| x =~ /\AUPDATE.*(\d+)/ ? $1.to_i : nil}.compact.should == [1, 2, 3]
|
280
|
+
end
|
281
|
+
|
282
|
+
specify "should be able to tell whether there are outstanding migrations" do
|
283
|
+
Sequel::Migrator.is_current?(@db, @dirname).should be_false
|
284
|
+
Sequel::Migrator.apply(@db, @dirname)
|
285
|
+
Sequel::Migrator.is_current?(@db, @dirname).should be_true
|
280
286
|
end
|
281
287
|
|
288
|
+
specify "should have #check_current raise an exception if the migrator is not current" do
|
289
|
+
proc{Sequel::Migrator.check_current(@db, @dirname)}.should raise_error(Sequel::Migrator::NotCurrentError)
|
290
|
+
Sequel::Migrator.apply(@db, @dirname)
|
291
|
+
proc{Sequel::Migrator.check_current(@db, @dirname)}.should_not raise_error
|
292
|
+
end
|
293
|
+
|
282
294
|
specify "should apply migrations correctly in the up direction with target" do
|
283
295
|
Sequel::Migrator.apply(@db, @dirname, 2)
|
284
296
|
@db.creates.should == [1111, 2222]
|
@@ -448,6 +460,22 @@ describe "Sequel::TimestampMigrator" do
|
|
448
460
|
@db[:schema_migrations].select_order_map(:filename).should == %w'1273253849_create_sessions.rb'
|
449
461
|
end
|
450
462
|
|
463
|
+
specify "should not be current when there are migrations to apply" do
|
464
|
+
@dir = 'spec/files/timestamped_migrations'
|
465
|
+
@m.apply(@db, @dir)
|
466
|
+
@m.is_current?(@db, @dir).should be_true
|
467
|
+
@dir = 'spec/files/interleaved_timestamped_migrations'
|
468
|
+
@m.is_current?(@db, @dir).should be_false
|
469
|
+
end
|
470
|
+
|
471
|
+
specify "should raise an exception if the migrator is not current" do
|
472
|
+
@dir = 'spec/files/timestamped_migrations'
|
473
|
+
@m.apply(@db, @dir)
|
474
|
+
proc{@m.check_current(@db, @dir)}.should_not raise_error
|
475
|
+
@dir = 'spec/files/interleaved_timestamped_migrations'
|
476
|
+
proc{@m.check_current(@db, @dir)}.should raise_error(Sequel::Migrator::NotCurrentError)
|
477
|
+
end
|
478
|
+
|
451
479
|
specify "should apply all missing files when migrating up" do
|
452
480
|
@dir = 'spec/files/timestamped_migrations'
|
453
481
|
@m.apply(@db, @dir)
|
@@ -22,17 +22,24 @@ describe "NestedAttributes plugin" do
|
|
22
22
|
@Artist = Class.new(@c).set_dataset(:artists)
|
23
23
|
@Album = Class.new(@c).set_dataset(:albums)
|
24
24
|
@Tag = Class.new(@c).set_dataset(:tags)
|
25
|
+
@Concert = Class.new(@c).set_dataset(:concerts)
|
25
26
|
@Artist.plugin :skip_create_refresh
|
26
27
|
@Album.plugin :skip_create_refresh
|
27
28
|
@Tag.plugin :skip_create_refresh
|
29
|
+
@Concert.plugin :skip_create_refresh
|
28
30
|
@Artist.columns :id, :name
|
29
31
|
@Album.columns :id, :name, :artist_id
|
30
32
|
@Tag.columns :id, :name
|
33
|
+
@Concert.columns :tour, :date, :artist_id, :playlist
|
34
|
+
@Concert.set_primary_key([:tour, :date])
|
35
|
+
@Concert.unrestrict_primary_key
|
31
36
|
@Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id
|
37
|
+
@Artist.one_to_many :concerts, :class=>@Concert, :key=>:artist_id
|
32
38
|
@Artist.one_to_one :first_album, :class=>@Album, :key=>:artist_id
|
33
39
|
@Album.many_to_one :artist, :class=>@Artist
|
34
40
|
@Album.many_to_many :tags, :class=>@Tag, :left_key=>:album_id, :right_key=>:tag_id, :join_table=>:at
|
35
41
|
@Artist.nested_attributes :albums, :first_album, :destroy=>true, :remove=>true
|
42
|
+
@Artist.nested_attributes :concerts, :destroy=>true, :remove=>true
|
36
43
|
@Album.nested_attributes :artist, :tags, :destroy=>true, :remove=>true
|
37
44
|
@db.sqls
|
38
45
|
end
|
@@ -80,6 +87,56 @@ describe "NestedAttributes plugin" do
|
|
80
87
|
a.albums.first.tags.should == [@Tag.new(:name=>'T')]
|
81
88
|
end
|
82
89
|
|
90
|
+
it "should support creating new objects with composite primary keys" do
|
91
|
+
insert = nil
|
92
|
+
@Concert.class_eval do
|
93
|
+
define_method :_insert do
|
94
|
+
insert = values
|
95
|
+
end
|
96
|
+
def before_create # Have to define the CPK somehow.
|
97
|
+
self.tour = 'To'
|
98
|
+
self.date = '2004-04-05'
|
99
|
+
super
|
100
|
+
end
|
101
|
+
end
|
102
|
+
a = @Artist.new({:name=>'Ar', :concerts_attributes=>[{:playlist=>'Pl'}]})
|
103
|
+
@db.sqls.should == []
|
104
|
+
a.save
|
105
|
+
@db.sqls.should == ["INSERT INTO artists (name) VALUES ('Ar')"]
|
106
|
+
insert.should == {:tour=>'To', :date=>'2004-04-05', :artist_id=>1, :playlist=>'Pl'}
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should support creating new objects with specific primary keys if :unmatched_pk => :create is set" do
|
110
|
+
@Artist.nested_attributes :albums, :unmatched_pk=>:create
|
111
|
+
insert = nil
|
112
|
+
@Album.class_eval do
|
113
|
+
unrestrict_primary_key
|
114
|
+
define_method :_insert do
|
115
|
+
insert = values
|
116
|
+
end
|
117
|
+
end
|
118
|
+
a = @Artist.new({:name=>'Ar', :albums_attributes=>[{:id=>7, :name=>'Al'}]})
|
119
|
+
@db.sqls.should == []
|
120
|
+
a.save
|
121
|
+
@db.sqls.should == ["INSERT INTO artists (name) VALUES ('Ar')"]
|
122
|
+
insert.should == {:artist_id=>1, :name=>'Al', :id=>7}
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should support creating new objects with specific composite primary keys if :unmatched_pk => :create is set" do
|
126
|
+
insert = nil
|
127
|
+
@Artist.nested_attributes :concerts, :unmatched_pk=>:create
|
128
|
+
@Concert.class_eval do
|
129
|
+
define_method :_insert do
|
130
|
+
insert = values
|
131
|
+
end
|
132
|
+
end
|
133
|
+
a = @Artist.new({:name=>'Ar', :concerts_attributes=>[{:tour=>'To', :date=>'2004-04-05', :playlist=>'Pl'}]})
|
134
|
+
@db.sqls.should == []
|
135
|
+
a.save
|
136
|
+
@db.sqls.should == ["INSERT INTO artists (name) VALUES ('Ar')"]
|
137
|
+
insert.should == {:tour=>'To', :date=>'2004-04-05', :artist_id=>1, :playlist=>'Pl'}
|
138
|
+
end
|
139
|
+
|
83
140
|
it "should support updating many_to_one objects" do
|
84
141
|
al = @Album.load(:id=>10, :name=>'Al')
|
85
142
|
ar = @Artist.load(:id=>20, :name=>'Ar')
|
@@ -140,6 +197,16 @@ describe "NestedAttributes plugin" do
|
|
140
197
|
@db.sqls.should == ["UPDATE albums SET name = 'Al' WHERE (id = 10)", "UPDATE tags SET name = 'T2' WHERE (id = 20)"]
|
141
198
|
end
|
142
199
|
|
200
|
+
it "should support updating objects with composite primary keys" do
|
201
|
+
ar = @Artist.load(:id=>10, :name=>'Ar')
|
202
|
+
co = @Concert.load(:tour=>'To', :date=>'2004-04-05', :playlist=>'Pl')
|
203
|
+
ar.associations[:concerts] = [co]
|
204
|
+
ar.set(:concerts_attributes=>[{:tour=>'To', :date=>'2004-04-05', :playlist=>'Pl2'}])
|
205
|
+
@db.sqls.should == []
|
206
|
+
ar.save
|
207
|
+
check_sql_array("UPDATE artists SET name = 'Ar' WHERE (id = 10)", ["UPDATE concerts SET playlist = 'Pl2' WHERE ((tour = 'To') AND (date = '2004-04-05'))", "UPDATE concerts SET playlist = 'Pl2' WHERE ((date = '2004-04-05') AND (tour = 'To'))"])
|
208
|
+
end
|
209
|
+
|
143
210
|
it "should support removing many_to_one objects" do
|
144
211
|
al = @Album.load(:id=>10, :name=>'Al')
|
145
212
|
ar = @Artist.load(:id=>20, :name=>'Ar')
|
@@ -183,6 +250,19 @@ describe "NestedAttributes plugin" do
|
|
183
250
|
@db.sqls.should == ["DELETE FROM at WHERE ((album_id = 10) AND (tag_id = 20))", "UPDATE albums SET name = 'Al' WHERE (id = 10)"]
|
184
251
|
end
|
185
252
|
|
253
|
+
it "should support removing objects with composite primary keys" do
|
254
|
+
ar = @Artist.load(:id=>10, :name=>'Ar')
|
255
|
+
co = @Concert.load(:tour=>'To', :date=>'2004-04-05', :playlist=>'Pl')
|
256
|
+
ar.associations[:concerts] = [co]
|
257
|
+
ar.set(:concerts_attributes=>[{:tour=>'To', :date=>'2004-04-05', :_remove=>'t'}])
|
258
|
+
@db.sqls.should == []
|
259
|
+
@Concert.dataset._fetch = {:id=>1}
|
260
|
+
ar.save
|
261
|
+
check_sql_array(["SELECT 1 AS one FROM concerts WHERE ((concerts.artist_id = 10) AND (tour = 'To') AND (date = '2004-04-05')) LIMIT 1", "SELECT 1 AS one FROM concerts WHERE ((concerts.artist_id = 10) AND (date = '2004-04-05') AND (tour = 'To')) LIMIT 1"],
|
262
|
+
["UPDATE concerts SET artist_id = NULL, playlist = 'Pl' WHERE ((tour = 'To') AND (date = '2004-04-05'))", "UPDATE concerts SET playlist = 'Pl', artist_id = NULL WHERE ((tour = 'To') AND (date = '2004-04-05'))", "UPDATE concerts SET artist_id = NULL, playlist = 'Pl' WHERE ((date = '2004-04-05') AND (tour = 'To'))", "UPDATE concerts SET playlist = 'Pl', artist_id = NULL WHERE ((date = '2004-04-05') AND (tour = 'To'))"],
|
263
|
+
"UPDATE artists SET name = 'Ar' WHERE (id = 10)")
|
264
|
+
end
|
265
|
+
|
186
266
|
it "should support destroying many_to_one objects" do
|
187
267
|
al = @Album.load(:id=>10, :name=>'Al')
|
188
268
|
ar = @Artist.load(:id=>20, :name=>'Ar')
|
@@ -224,6 +304,16 @@ describe "NestedAttributes plugin" do
|
|
224
304
|
@db.sqls.should == ["DELETE FROM at WHERE ((album_id = 10) AND (tag_id = 20))", "UPDATE albums SET name = 'Al' WHERE (id = 10)", "DELETE FROM tags WHERE (id = 20)"]
|
225
305
|
end
|
226
306
|
|
307
|
+
it "should support destroying objects with composite primary keys" do
|
308
|
+
ar = @Artist.load(:id=>10, :name=>'Ar')
|
309
|
+
co = @Concert.load(:tour=>'To', :date=>'2004-04-05', :playlist=>'Pl')
|
310
|
+
ar.associations[:concerts] = [co]
|
311
|
+
ar.set(:concerts_attributes=>[{:tour=>'To', :date=>'2004-04-05', :_delete=>'t'}])
|
312
|
+
@db.sqls.should == []
|
313
|
+
ar.save
|
314
|
+
check_sql_array("UPDATE artists SET name = 'Ar' WHERE (id = 10)", ["DELETE FROM concerts WHERE ((tour = 'To') AND (date = '2004-04-05'))", "DELETE FROM concerts WHERE ((date = '2004-04-05') AND (tour = 'To'))"])
|
315
|
+
end
|
316
|
+
|
227
317
|
it "should support both string and symbol keys in nested attribute hashes" do
|
228
318
|
a = @Album.load(:id=>10, :name=>'Al')
|
229
319
|
t = @Tag.load(:id=>20, :name=>'T')
|
@@ -283,6 +373,36 @@ describe "NestedAttributes plugin" do
|
|
283
373
|
@db.sqls.should == ["UPDATE artists SET name = 'Ar' WHERE (id = 20)"]
|
284
374
|
end
|
285
375
|
|
376
|
+
it "should not raise an Error if an unmatched primary key is given, if the :unmatched_pk=>:ignore option is used" do
|
377
|
+
@Artist.nested_attributes :albums, :unmatched_pk=>:ignore
|
378
|
+
al = @Album.load(:id=>10, :name=>'Al')
|
379
|
+
ar = @Artist.load(:id=>20, :name=>'Ar')
|
380
|
+
ar.associations[:albums] = [al]
|
381
|
+
ar.set(:albums_attributes=>[{:id=>30, :_delete=>'t'}])
|
382
|
+
@db.sqls.should == []
|
383
|
+
ar.save
|
384
|
+
@db.sqls.should == ["UPDATE artists SET name = 'Ar' WHERE (id = 20)"]
|
385
|
+
end
|
386
|
+
|
387
|
+
it "should raise an Error if a composite primary key is given in a nested attribute hash, but no matching associated object exists" do
|
388
|
+
ar = @Artist.load(:id=>10, :name=>'Ar')
|
389
|
+
co = @Concert.load(:tour=>'To', :date=>'2004-04-05', :playlist=>'Pl')
|
390
|
+
ar.associations[:concerts] = [co]
|
391
|
+
proc{ar.set(:concerts_attributes=>[{:tour=>'To', :date=>'2004-04-04', :_delete=>'t'}])}.should raise_error(Sequel::Error)
|
392
|
+
proc{ar.set(:concerts_attributes=>[{:tour=>'To', :date=>'2004-04-05', :_delete=>'t'}])}.should_not raise_error(Sequel::Error)
|
393
|
+
end
|
394
|
+
|
395
|
+
it "should not raise an Error if an unmatched composite primary key is given, if the :strict=>false option is used" do
|
396
|
+
@Artist.nested_attributes :concerts, :strict=>false
|
397
|
+
ar = @Artist.load(:id=>10, :name=>'Ar')
|
398
|
+
co = @Concert.load(:tour=>'To', :date=>'2004-04-05', :playlist=>'Pl')
|
399
|
+
ar.associations[:concerts] = [co]
|
400
|
+
ar.set(:concerts_attributes=>[{:tour=>'To', :date=>'2004-04-06', :_delete=>'t'}])
|
401
|
+
@db.sqls.should == []
|
402
|
+
ar.save
|
403
|
+
@db.sqls.should == ["UPDATE artists SET name = 'Ar' WHERE (id = 10)"]
|
404
|
+
end
|
405
|
+
|
286
406
|
it "should not save if nested attribute is not valid and should include nested attribute validation errors in the main object's validation errors" do
|
287
407
|
@Artist.class_eval do
|
288
408
|
def validate
|
@@ -370,6 +490,19 @@ describe "NestedAttributes plugin" do
|
|
370
490
|
["INSERT INTO at (album_id, tag_id) VALUES (1, 4)", "INSERT INTO at (tag_id, album_id) VALUES (4, 1)"])
|
371
491
|
end
|
372
492
|
|
493
|
+
it "should accept a :transform block that returns a changed attributes hash" do
|
494
|
+
@Album.nested_attributes :tags, :transform=>proc{|parent, hash| hash[:name] << parent.name; hash }
|
495
|
+
a = @Album.new(:name => 'Al')
|
496
|
+
a.set(:tags_attributes=>[{:name=>'T'}, {:name=>'T2'}])
|
497
|
+
@db.sqls.should == []
|
498
|
+
a.save
|
499
|
+
check_sql_array("INSERT INTO albums (name) VALUES ('Al')",
|
500
|
+
"INSERT INTO tags (name) VALUES ('TAl')",
|
501
|
+
["INSERT INTO at (album_id, tag_id) VALUES (1, 2)", "INSERT INTO at (tag_id, album_id) VALUES (2, 1)"],
|
502
|
+
"INSERT INTO tags (name) VALUES ('T2Al')",
|
503
|
+
["INSERT INTO at (album_id, tag_id) VALUES (1, 4)", "INSERT INTO at (tag_id, album_id) VALUES (4, 1)"])
|
504
|
+
end
|
505
|
+
|
373
506
|
it "should return objects created/modified in the internal methods" do
|
374
507
|
@Album.nested_attributes :tags, :remove=>true, :strict=>false
|
375
508
|
objs = []
|
@@ -381,7 +514,7 @@ describe "NestedAttributes plugin" do
|
|
381
514
|
a = @Album.new(:name=>'Al')
|
382
515
|
a.associations[:tags] = [@Tag.load(:id=>6, :name=>'A'), @Tag.load(:id=>7, :name=>'A2')]
|
383
516
|
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}]
|
384
|
-
objs.should == [[@Tag.load(:id=>6, :name=>'T'), :update], [@Tag.load(:id=>7, :name=>'A2'), :remove], [@Tag.new(:name=>'T3'), :create]
|
517
|
+
objs.should == [[@Tag.load(:id=>6, :name=>'T'), :update], [@Tag.load(:id=>7, :name=>'A2'), :remove], [@Tag.new(:name=>'T3'), :create]]
|
385
518
|
end
|
386
519
|
|
387
520
|
it "should raise an error if updating modifies the associated objects keys" do
|
@@ -37,7 +37,7 @@ describe "optimistic_locking plugin" do
|
|
37
37
|
puts sql
|
38
38
|
end
|
39
39
|
end
|
40
|
-
@c.dataset._fetch = proc do |sql|
|
40
|
+
@c.instance_dataset._fetch = @c.dataset._fetch = proc do |sql|
|
41
41
|
m = h[1].dup
|
42
42
|
v = m.delete(:lock_version)
|
43
43
|
m[lv.to_sym] = v
|
@@ -95,6 +95,20 @@ describe "optimistic_locking plugin" do
|
|
95
95
|
proc{p2.update(:name=>'Bob')}.should raise_error(Sequel::Plugins::OptimisticLocking::Error)
|
96
96
|
end
|
97
97
|
|
98
|
+
specify "should work correctly if attempting to refresh and save again after a failed save" do
|
99
|
+
p1 = @c[1]
|
100
|
+
p2 = @c[1]
|
101
|
+
p1.update(:name=>'Jim')
|
102
|
+
begin
|
103
|
+
p2.update(:name=>'Bob')
|
104
|
+
rescue Sequel::Plugins::OptimisticLocking::Error
|
105
|
+
p2.refresh
|
106
|
+
@c.db.sqls
|
107
|
+
proc{p2.update(:name=>'Bob')}.should_not raise_error
|
108
|
+
end
|
109
|
+
@c.db.sqls.first.should =~ /UPDATE people SET (name = 'Bob', lock_version = 4|lock_version = 4, name = 'Bob') WHERE \(\(id = 1\) AND \(lock_version = 3\)\)/
|
110
|
+
end
|
111
|
+
|
98
112
|
specify "should increment the lock column when #modified! even if no columns are changed" do
|
99
113
|
p1 = @c[1]
|
100
114
|
p1.modified!
|
@@ -180,7 +180,7 @@ describe "pg_hstore extension" do
|
|
180
180
|
@db.schema(:items).map{|e| e[1][:type]}.should == [:integer, :hstore]
|
181
181
|
end
|
182
182
|
|
183
|
-
it "should support typecasting
|
183
|
+
it "should support typecasting for the hstore type" do
|
184
184
|
@db.extend @c::DatabaseMethods
|
185
185
|
h = {1=>2}.hstore
|
186
186
|
@db.typecast_value(:hstore, h).should equal(h)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
2
|
+
|
3
|
+
describe "pg_inet extension" do
|
4
|
+
ipv6_broken = (IPAddr.new('::1'); false) rescue true
|
5
|
+
before do
|
6
|
+
@db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
|
7
|
+
@db.extend(Module.new{def bound_variable_arg(arg, conn) arg end})
|
8
|
+
@db.extend(Sequel::Postgres::InetDatabaseMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should literalize IPAddr v4 instances to strings correctly" do
|
12
|
+
@db.literal(IPAddr.new('127.0.0.1')).should == "'127.0.0.1/32'"
|
13
|
+
@db.literal(IPAddr.new('127.0.0.0/8')).should == "'127.0.0.0/8'"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should literalize IPAddr v6 instances to strings correctly" do
|
17
|
+
@db.literal(IPAddr.new('2001:4f8:3:ba::/64')).should == "'2001:4f8:3:ba::/64'"
|
18
|
+
@db.literal(IPAddr.new('2001:4f8:3:ba:2e0:81ff:fe22:d1f1')).should == "'2001:4f8:3:ba:2e0:81ff:fe22:d1f1/128'"
|
19
|
+
end unless ipv6_broken
|
20
|
+
|
21
|
+
it "should not affect literalization of custom objects" do
|
22
|
+
o = Object.new
|
23
|
+
def o.sql_literal(ds) 'v' end
|
24
|
+
@db.literal(o).should == 'v'
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should support using IPAddr as bound variables" do
|
28
|
+
@db.bound_variable_arg(1, nil).should == 1
|
29
|
+
@db.bound_variable_arg(IPAddr.new('127.0.0.1'), nil).should == '127.0.0.1/32'
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should parse inet/cidr type from the schema correctly" do
|
33
|
+
@db.fetch = [{:name=>'id', :db_type=>'integer'}, {:name=>'i', :db_type=>'inet'}, {:name=>'c', :db_type=>'cidr'}]
|
34
|
+
@db.schema(:items).map{|e| e[1][:type]}.should == [:integer, :ipaddr, :ipaddr]
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should support typecasting for the ipaddr type" do
|
38
|
+
ip = IPAddr.new('127.0.0.1')
|
39
|
+
@db.typecast_value(:ipaddr, ip).should equal(ip)
|
40
|
+
@db.typecast_value(:ipaddr, ip.to_s).should == ip
|
41
|
+
proc{@db.typecast_value(:ipaddr, '')}.should raise_error(Sequel::InvalidValue)
|
42
|
+
proc{@db.typecast_value(:ipaddr, 1)}.should raise_error(Sequel::InvalidValue)
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
2
|
+
|
3
|
+
begin
|
4
|
+
Sequel.extension :pg_json
|
5
|
+
rescue LoadError => e
|
6
|
+
skip_warn "can't load pg_json extension (#{e.class}: #{e})"
|
7
|
+
else
|
8
|
+
describe "pg_json extension" do
|
9
|
+
before(:all) do
|
10
|
+
m = Sequel::Postgres
|
11
|
+
@m = m::JSONDatabaseMethods
|
12
|
+
@hc = m::JSONHash
|
13
|
+
@ac = m::JSONArray
|
14
|
+
|
15
|
+
# Create subclass in correct namespace for easily overriding methods
|
16
|
+
j = m::JSON = JSON.dup
|
17
|
+
j.instance_eval do
|
18
|
+
Parser = JSON::Parser
|
19
|
+
alias old_parse parse
|
20
|
+
def parse(s)
|
21
|
+
return 1 if s == '1'
|
22
|
+
old_parse(s)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
before do
|
27
|
+
@db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
|
28
|
+
@db.extend(Module.new{def bound_variable_arg(arg, conn) arg end})
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should parse json strings correctly" do
|
32
|
+
@m.parse_json('[]').should be_a_kind_of(@ac)
|
33
|
+
@m.parse_json('[]').to_a.should == []
|
34
|
+
@m.parse_json('[1]').to_a.should == [1]
|
35
|
+
@m.parse_json('[1, 2]').to_a.should == [1, 2]
|
36
|
+
@m.parse_json('[1, [2], {"a": "b"}]').to_a.should == [1, [2], {'a'=>'b'}]
|
37
|
+
@m.parse_json('{}').should be_a_kind_of(@hc)
|
38
|
+
@m.parse_json('{}').to_hash.should == {}
|
39
|
+
@m.parse_json('{"a": "b"}').to_hash.should == {'a'=>'b'}
|
40
|
+
@m.parse_json('{"a": "b", "c": [1, 2, 3]}').to_hash.should == {'a'=>'b', 'c'=>[1, 2, 3]}
|
41
|
+
@m.parse_json('{"a": "b", "c": {"d": "e"}}').to_hash.should == {'a'=>'b', 'c'=>{'d'=>'e'}}
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should raise an error when attempting to parse invalid json" do
|
45
|
+
proc{@m.parse_json('')}.should raise_error(Sequel::InvalidValue)
|
46
|
+
proc{@m.parse_json('1')}.should raise_error(Sequel::InvalidValue)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should literalize HStores to strings correctly" do
|
50
|
+
@db.literal([].pg_json).should == "'[]'::json"
|
51
|
+
@db.literal([1, [2], {'a'=>'b'}].pg_json).should == "'[1,[2],{\"a\":\"b\"}]'::json"
|
52
|
+
@db.literal({}.pg_json).should == "'{}'::json"
|
53
|
+
@db.literal({'a'=>'b'}.pg_json).should == "'{\"a\":\"b\"}'::json"
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should have Hash#pg_json method for creating JSONHash instances" do
|
57
|
+
{}.pg_json.should be_a_kind_of(@hc)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should have Array#pg_json method for creating JSONArray instances" do
|
61
|
+
[].pg_json.should be_a_kind_of(@ac)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should have JSONHash#to_hash method for getting underlying hash" do
|
65
|
+
{}.pg_json.to_hash.should be_a_kind_of(Hash)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should have JSONArray#to_a method for getting underlying array" do
|
69
|
+
[].pg_json.to_a.should be_a_kind_of(Array)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should support using JSONHash and JSONArray as bound variables" do
|
73
|
+
@db.extend @m
|
74
|
+
@db.bound_variable_arg(1, nil).should == 1
|
75
|
+
@db.bound_variable_arg([1].pg_json, nil).should == '[1]'
|
76
|
+
@db.bound_variable_arg({'a'=>'b'}.pg_json, nil).should == '{"a":"b"}'
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should parse json type from the schema correctly" do
|
80
|
+
@db.extend @m
|
81
|
+
@db.fetch = [{:name=>'id', :db_type=>'integer'}, {:name=>'i', :db_type=>'json'}]
|
82
|
+
@db.schema(:items).map{|e| e[1][:type]}.should == [:integer, :json]
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should support typecasting for the json type" do
|
86
|
+
@db.extend @m
|
87
|
+
h = {1=>2}.pg_json
|
88
|
+
a = [1].pg_json
|
89
|
+
@db.typecast_value(:json, h).should equal(h)
|
90
|
+
@db.typecast_value(:json, h.to_hash).should == h
|
91
|
+
@db.typecast_value(:json, h.to_hash).should be_a_kind_of(@hc)
|
92
|
+
@db.typecast_value(:json, a).should equal(a)
|
93
|
+
@db.typecast_value(:json, a.to_a).should == a
|
94
|
+
@db.typecast_value(:json, a.to_a).should be_a_kind_of(@ac)
|
95
|
+
@db.typecast_value(:json, '[]').should == [].pg_json
|
96
|
+
@db.typecast_value(:json, '{"a": "b"}').should == {"a"=>"b"}.pg_json
|
97
|
+
proc{@db.typecast_value(:json, '')}.should raise_error(Sequel::InvalidValue)
|
98
|
+
proc{@db.typecast_value(:json, 1)}.should raise_error(Sequel::InvalidValue)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|