sequel 3.35.0 → 3.36.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 +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
|