sequel 4.11.0 → 4.12.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.
- checksums.yaml +4 -4
- data/CHANGELOG +32 -0
- data/Rakefile +1 -5
- data/doc/opening_databases.rdoc +5 -1
- data/doc/release_notes/4.12.0.txt +105 -0
- data/lib/sequel/adapters/jdbc.rb +1 -0
- data/lib/sequel/adapters/oracle.rb +1 -0
- data/lib/sequel/adapters/postgres.rb +17 -8
- data/lib/sequel/adapters/shared/cubrid.rb +2 -1
- data/lib/sequel/adapters/shared/db2.rb +1 -0
- data/lib/sequel/adapters/shared/mssql.rb +1 -0
- data/lib/sequel/adapters/shared/postgres.rb +23 -2
- data/lib/sequel/adapters/shared/sqlanywhere.rb +1 -0
- data/lib/sequel/adapters/sqlite.rb +11 -5
- data/lib/sequel/database/query.rb +14 -1
- data/lib/sequel/dataset/prepared_statements.rb +2 -1
- data/lib/sequel/dataset/query.rb +48 -37
- data/lib/sequel/dataset/sql.rb +0 -39
- data/lib/sequel/extensions/pg_interval.rb +1 -1
- data/lib/sequel/extensions/pg_static_cache_updater.rb +11 -5
- data/lib/sequel/model/associations.rb +2 -2
- data/lib/sequel/plugins/auto_validations.rb +16 -4
- data/lib/sequel/plugins/hook_class_methods.rb +1 -1
- data/lib/sequel/plugins/nested_attributes.rb +72 -59
- data/lib/sequel/plugins/prepared_statements.rb +16 -5
- data/lib/sequel/plugins/prepared_statements_associations.rb +14 -0
- data/lib/sequel/sql.rb +2 -43
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +49 -20
- data/spec/bin_spec.rb +2 -2
- data/spec/core/dataset_spec.rb +18 -6
- data/spec/core/schema_spec.rb +2 -1
- data/spec/extensions/auto_validations_spec.rb +23 -2
- data/spec/extensions/nested_attributes_spec.rb +32 -1
- data/spec/extensions/pg_static_cache_updater_spec.rb +12 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +17 -17
- data/spec/extensions/prepared_statements_spec.rb +11 -8
- data/spec/integration/plugin_test.rb +43 -0
- data/spec/integration/schema_test.rb +7 -0
- data/spec/model/eager_loading_spec.rb +9 -0
- metadata +4 -2
@@ -53,6 +53,20 @@ module Sequel
|
|
53
53
|
opts.send(:cached_fetch, :prepared_statement) do
|
54
54
|
unless opts[:instance_specific]
|
55
55
|
ds, bv = _associated_dataset(opts, {}).unbind
|
56
|
+
|
57
|
+
f = ds.opts[:from]
|
58
|
+
if f && f.length == 1
|
59
|
+
s = ds.opts[:select]
|
60
|
+
if ds.opts[:join]
|
61
|
+
if opts.eager_loading_use_associated_key? && s && s.length == 1 && s.first.is_a?(SQL::ColumnAll)
|
62
|
+
table = s.first.table
|
63
|
+
ds = ds.select(*opts.associated_class.columns.map{|c| Sequel.identifier(c).qualify(table)})
|
64
|
+
end
|
65
|
+
elsif !s || s.empty?
|
66
|
+
ds = ds.select(*opts.associated_class.columns.map{|c| Sequel.identifier(c)})
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
56
70
|
if bv.length != assoc_bv.length
|
57
71
|
h = {}
|
58
72
|
bv.each do |k,v|
|
data/lib/sequel/sql.rb
CHANGED
@@ -1339,16 +1339,6 @@ module Sequel
|
|
1339
1339
|
end
|
1340
1340
|
end
|
1341
1341
|
|
1342
|
-
# REMOVE411
|
1343
|
-
class EmulatedFunction < Function
|
1344
|
-
def self.new(name, *args)
|
1345
|
-
Deprecation.deprecate("Sequel::SQL::EmulatedFunction", "Please use Sequel::SQL::Function.new!(name, args, :emulate=>true) to create an emulated SQL function")
|
1346
|
-
Function.new!(name, args, :emulate=>true)
|
1347
|
-
end
|
1348
|
-
|
1349
|
-
to_s_method :emulated_function_sql
|
1350
|
-
end
|
1351
|
-
|
1352
1342
|
class GenericExpression
|
1353
1343
|
include AliasMethods
|
1354
1344
|
include BooleanMethods
|
@@ -1394,18 +1384,9 @@ module Sequel
|
|
1394
1384
|
attr_reader :table_expr
|
1395
1385
|
|
1396
1386
|
# Create an object with the given join_type and table expression.
|
1397
|
-
def initialize(join_type,
|
1387
|
+
def initialize(join_type, table_expr)
|
1398
1388
|
@join_type = join_type
|
1399
|
-
|
1400
|
-
@table_expr = if table.is_a?(AliasedExpression)
|
1401
|
-
table
|
1402
|
-
# REMOVE411
|
1403
|
-
elsif table_alias
|
1404
|
-
Deprecation.deprecate("The table_alias argument to Sequel::SQL::JoinClause#initialize", "Please use a Sequel::SQL::AliasedExpression as the table argument instead.")
|
1405
|
-
AliasedExpression.new(table, table_alias)
|
1406
|
-
else
|
1407
|
-
table
|
1408
|
-
end
|
1389
|
+
@table_expr = table_expr
|
1409
1390
|
end
|
1410
1391
|
|
1411
1392
|
# The table/set related to the JOIN, without any alias.
|
@@ -1827,28 +1808,6 @@ module Sequel
|
|
1827
1808
|
to_s_method :window_sql, '@opts'
|
1828
1809
|
end
|
1829
1810
|
|
1830
|
-
# REMOVE411
|
1831
|
-
class WindowFunction < GenericExpression
|
1832
|
-
# The function to use, should be an <tt>SQL::Function</tt>.
|
1833
|
-
attr_reader :function
|
1834
|
-
|
1835
|
-
# The window to use, should be an <tt>SQL::Window</tt>.
|
1836
|
-
attr_reader :window
|
1837
|
-
|
1838
|
-
def self.new(function, window)
|
1839
|
-
Deprecation.deprecate("Sequel::SQL::WindowFunction", "Please use Sequel::SQL::Function.new(name, *args).over(...) to create an SQL window function")
|
1840
|
-
function.over(window)
|
1841
|
-
end
|
1842
|
-
|
1843
|
-
# Set the function and window.
|
1844
|
-
def initialize(function, window)
|
1845
|
-
Deprecation.deprecate("Sequel::SQL::WindowFunction", "Please use Sequel::SQL::Function.new(name, *args).over(...) to create an SQL window function")
|
1846
|
-
@function, @window = function, window
|
1847
|
-
end
|
1848
|
-
|
1849
|
-
to_s_method :window_function_sql, '@function, @window'
|
1850
|
-
end
|
1851
|
-
|
1852
1811
|
# A +Wrapper+ is a simple way to wrap an existing object so that it supports
|
1853
1812
|
# the Sequel DSL.
|
1854
1813
|
class Wrapper < GenericExpression
|
data/lib/sequel/version.rb
CHANGED
@@ -3,7 +3,7 @@ module Sequel
|
|
3
3
|
MAJOR = 4
|
4
4
|
# The minor version of Sequel. Bumped for every non-patch level
|
5
5
|
# release, generally around once a month.
|
6
|
-
MINOR =
|
6
|
+
MINOR = 12
|
7
7
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
8
8
|
# releases that fix regressions from previous versions.
|
9
9
|
TINY = 0
|
@@ -179,6 +179,19 @@ describe "A PostgreSQL database" do
|
|
179
179
|
@db.server_version.should > 70000
|
180
180
|
end
|
181
181
|
|
182
|
+
specify "should create a dataset using the VALUES clause via #values" do
|
183
|
+
@db.values([[1, 2], [3, 4]]).map([:column1, :column2]).should == [[1, 2], [3, 4]]
|
184
|
+
end
|
185
|
+
|
186
|
+
specify "should support ordering and limiting with #values" do
|
187
|
+
@db.values([[1, 2], [3, 4]]).reverse(:column2, :column1).limit(1).map([:column1, :column2]).should == [[3, 4]]
|
188
|
+
@db.values([[1, 2], [3, 4]]).reverse(:column2, :column1).offset(1).map([:column1, :column2]).should == [[1, 2]]
|
189
|
+
end
|
190
|
+
|
191
|
+
specify "should support subqueries with #values" do
|
192
|
+
@db.values([[1, 2]]).from_self.cross_join(@db.values([[3, 4]]).as(:x, [:c1, :c2])).map([:column1, :column2, :c1, :c2]).should == [[1, 2, 3, 4]]
|
193
|
+
end
|
194
|
+
|
182
195
|
specify "should respect the :read_only option per-savepoint" do
|
183
196
|
proc{@db.transaction{@db.transaction(:savepoint=>true, :read_only=>true){@db[:public__testfk].insert}}}.should raise_error(Sequel::DatabaseError)
|
184
197
|
proc{@db.transaction(:auto_savepoint=>true, :read_only=>true){@db.transaction(:read_only=>false){@db[:public__testfk].insert}}}.should raise_error(Sequel::DatabaseError)
|
@@ -257,6 +270,12 @@ describe "A PostgreSQL database" do
|
|
257
270
|
ds.insert(:id=>1)
|
258
271
|
ds.select_map(:id).should == [1]
|
259
272
|
end
|
273
|
+
|
274
|
+
specify "should have notice receiver receive notices" do
|
275
|
+
a = nil
|
276
|
+
Sequel.connect(DB.opts.merge(:notice_receiver=>proc{|r| a = r.result_error_message})){|db| db.do("BEGIN\nRAISE WARNING 'foo';\nEND;")}
|
277
|
+
a.should == "WARNING: foo\n"
|
278
|
+
end if DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG && DB.server_version >= 90000
|
260
279
|
end
|
261
280
|
|
262
281
|
describe "A PostgreSQL database with domain types" do
|
@@ -1035,6 +1054,18 @@ describe "Postgres::Dataset#insert" do
|
|
1035
1054
|
@ds.first(:xid=>h[:xid])[:value].should == 10
|
1036
1055
|
end
|
1037
1056
|
|
1057
|
+
specify "should have insert_select respect existing returning clause" do
|
1058
|
+
h = @ds.returning(:value___v, :xid___x).insert_select(:value=>10)
|
1059
|
+
h[:v].should == 10
|
1060
|
+
@ds.first(:xid=>h[:x])[:value].should == 10
|
1061
|
+
end
|
1062
|
+
|
1063
|
+
specify "should have prepared insert_select respect existing returning clause" do
|
1064
|
+
h = @ds.returning(:value___v, :xid___x).prepare(:insert_select, :insert_select, :value=>10).call
|
1065
|
+
h[:v].should == 10
|
1066
|
+
@ds.first(:xid=>h[:x])[:value].should == 10
|
1067
|
+
end
|
1068
|
+
|
1038
1069
|
specify "should correctly return the inserted record's primary key value" do
|
1039
1070
|
value1 = 10
|
1040
1071
|
id1 = @ds.insert(:value=>value1)
|
@@ -2466,14 +2497,11 @@ describe 'PostgreSQL hstore handling' do
|
|
2466
2497
|
@ds.get(h2.merge(h3).keys.length).should == 2
|
2467
2498
|
@ds.get(h1.merge(h3).keys.length).should == 3
|
2468
2499
|
|
2469
|
-
|
2470
|
-
|
2471
|
-
@ds.get(h1.contain_all(%w'a c')).should == true
|
2472
|
-
@ds.get(h1.contain_all(%w'a d')).should == false
|
2500
|
+
@ds.get(h1.contain_all(%w'a c')).should == true
|
2501
|
+
@ds.get(h1.contain_all(%w'a d')).should == false
|
2473
2502
|
|
2474
|
-
|
2475
|
-
|
2476
|
-
end
|
2503
|
+
@ds.get(h1.contain_any(%w'a d')).should == true
|
2504
|
+
@ds.get(h1.contain_any(%w'e d')).should == false
|
2477
2505
|
|
2478
2506
|
@ds.get(h1.contains(h2)).should == true
|
2479
2507
|
@ds.get(h1.contains(h3)).should == false
|
@@ -2491,18 +2519,16 @@ describe 'PostgreSQL hstore handling' do
|
|
2491
2519
|
|
2492
2520
|
@ds.from(Sequel.hstore('a'=>'b', 'c'=>nil).op.each).order(:key).all.should == [{:key=>'a', :value=>'b'}, {:key=>'c', :value=>nil}]
|
2493
2521
|
|
2494
|
-
|
2495
|
-
|
2496
|
-
|
2497
|
-
|
2498
|
-
|
2499
|
-
|
2500
|
-
|
2501
|
-
|
2502
|
-
|
2503
|
-
|
2504
|
-
@ds.get(h1.exist?('d')).should == false
|
2505
|
-
end
|
2522
|
+
@ds.get(h1.has_key?('c')).should == true
|
2523
|
+
@ds.get(h1.include?('c')).should == true
|
2524
|
+
@ds.get(h1.key?('c')).should == true
|
2525
|
+
@ds.get(h1.member?('c')).should == true
|
2526
|
+
@ds.get(h1.exist?('c')).should == true
|
2527
|
+
@ds.get(h1.has_key?('d')).should == false
|
2528
|
+
@ds.get(h1.include?('d')).should == false
|
2529
|
+
@ds.get(h1.key?('d')).should == false
|
2530
|
+
@ds.get(h1.member?('d')).should == false
|
2531
|
+
@ds.get(h1.exist?('d')).should == false
|
2506
2532
|
|
2507
2533
|
@ds.get(h1.hstore.hstore.hstore.keys.length).should == 2
|
2508
2534
|
@ds.get(h1.keys.length).should == 2
|
@@ -3022,6 +3048,7 @@ describe 'PostgreSQL interval types' do
|
|
3022
3048
|
['1 second', '00:00:01', 1, [[:seconds, 1]]],
|
3023
3049
|
['1 minute', '00:01:00', 60, [[:seconds, 60]]],
|
3024
3050
|
['1 hour', '01:00:00', 3600, [[:seconds, 3600]]],
|
3051
|
+
['123000 hours', '123000:00:00', 442800000, [[:seconds, 442800000]]],
|
3025
3052
|
['1 day', '1 day', 86400, [[:days, 1]]],
|
3026
3053
|
['1 week', '7 days', 86400*7, [[:days, 7]]],
|
3027
3054
|
['1 month', '1 mon', 86400*30, [[:months, 1]]],
|
@@ -3390,7 +3417,8 @@ describe 'pg_static_cache_updater extension' do
|
|
3390
3417
|
q = Queue.new
|
3391
3418
|
q1 = Queue.new
|
3392
3419
|
|
3393
|
-
@db.listen_for_static_cache_updates(@Thing, :timeout=>0, :loop=>proc{q.push(nil); q1.pop.call})
|
3420
|
+
@db.listen_for_static_cache_updates(@Thing, :timeout=>0, :loop=>proc{q.push(nil); q1.pop.call}, :before_thread_exit=>proc{q.push(nil)})
|
3421
|
+
|
3394
3422
|
q.pop
|
3395
3423
|
q1.push(proc{@db[:things].insert(1, 'A')})
|
3396
3424
|
q.pop
|
@@ -3405,5 +3433,6 @@ describe 'pg_static_cache_updater extension' do
|
|
3405
3433
|
@Thing.all.should == []
|
3406
3434
|
|
3407
3435
|
q1.push(proc{throw :stop})
|
3436
|
+
q.pop
|
3408
3437
|
end
|
3409
3438
|
end if DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG && DB.server_version >= 90000
|
data/spec/bin_spec.rb
CHANGED
@@ -88,8 +88,8 @@ END
|
|
88
88
|
DB2.tables.sort_by{|t| t.to_s}.should == [:a, :b]
|
89
89
|
DB[:a].all.should == [{:a=>1, :name=>'foo'}]
|
90
90
|
DB[:b].all.should == [{:a=>1}]
|
91
|
-
DB2.schema(:a).should == [[:a,
|
92
|
-
DB2.schema(:b).should == [[:a,
|
91
|
+
DB2.schema(:a).map{|col, sch| [col, *sch.values_at(:allow_null, :default, :primary_key, :db_type, :type, :ruby_default)]}.should == [[:a, false, nil, true, "integer", :integer, nil], [:name, true, nil, false, "varchar(255)", :string, nil]]
|
92
|
+
DB2.schema(:b).map{|col, sch| [col, *sch.values_at(:allow_null, :default, :primary_key, :db_type, :type, :ruby_default)]}.should == [[:a, true, nil, false, "integer", :integer, nil]]
|
93
93
|
DB2.indexes(:a).should == {}
|
94
94
|
DB2.indexes(:b).should == {:b_a_index=>{:unique=>false, :columns=>[:a]}}
|
95
95
|
DB2.foreign_key_list(:a).should == []
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -2127,12 +2127,20 @@ describe "Dataset#join_table" do
|
|
2127
2127
|
@d.cross_join(:categories).sql.should == 'SELECT * FROM "items" CROSS JOIN "categories"'
|
2128
2128
|
end
|
2129
2129
|
|
2130
|
-
specify "should
|
2131
|
-
|
2132
|
-
|
2133
|
-
|
2134
|
-
|
2135
|
-
|
2130
|
+
specify "should support options hashes for join methods that don't take conditions" do
|
2131
|
+
@d.natural_join(:categories, :table_alias=>:a).sql.should == 'SELECT * FROM "items" NATURAL JOIN "categories" AS "a"'
|
2132
|
+
@d.natural_left_join(:categories, :table_alias=>:a).sql.should == 'SELECT * FROM "items" NATURAL LEFT JOIN "categories" AS "a"'
|
2133
|
+
@d.natural_right_join(:categories, :table_alias=>:a).sql.should == 'SELECT * FROM "items" NATURAL RIGHT JOIN "categories" AS "a"'
|
2134
|
+
@d.natural_full_join(:categories, :table_alias=>:a).sql.should == 'SELECT * FROM "items" NATURAL FULL JOIN "categories" AS "a"'
|
2135
|
+
@d.cross_join(:categories, :table_alias=>:a).sql.should == 'SELECT * FROM "items" CROSS JOIN "categories" AS "a"'
|
2136
|
+
end
|
2137
|
+
|
2138
|
+
specify "should raise an error if non-hash arguments are provided to join methods that don't take conditions" do
|
2139
|
+
proc{@d.natural_join(:categories, nil)}.should raise_error(Sequel::Error)
|
2140
|
+
proc{@d.natural_left_join(:categories, nil)}.should raise_error(Sequel::Error)
|
2141
|
+
proc{@d.natural_right_join(:categories, nil)}.should raise_error(Sequel::Error)
|
2142
|
+
proc{@d.natural_full_join(:categories, nil)}.should raise_error(Sequel::Error)
|
2143
|
+
proc{@d.cross_join(:categories, nil)}.should raise_error(Sequel::Error)
|
2136
2144
|
end
|
2137
2145
|
|
2138
2146
|
specify "should raise an error if blocks are provided to join methods that don't pass them" do
|
@@ -2184,6 +2192,10 @@ describe "Dataset#join_table" do
|
|
2184
2192
|
@d.from('stats').join('players', {:id => :player_id}, :implicit_qualifier=>:p).sql.should == 'SELECT * FROM "stats" INNER JOIN "players" ON ("players"."id" = "p"."player_id")'
|
2185
2193
|
end
|
2186
2194
|
|
2195
|
+
specify "should support the :reset_implicit_qualifier option" do
|
2196
|
+
@d.from(:stats).join(:a, [:b], :reset_implicit_qualifier=>false).join(:players, {:id => :player_id}).sql.should == 'SELECT * FROM "stats" INNER JOIN "a" USING ("b") INNER JOIN "players" ON ("players"."id" = "stats"."player_id")'
|
2197
|
+
end
|
2198
|
+
|
2187
2199
|
specify "should default :qualify option to default_join_table_qualification" do
|
2188
2200
|
def @d.default_join_table_qualification; false; end
|
2189
2201
|
@d.from('stats').join(:players, :id => :player_id).sql.should == 'SELECT * FROM "stats" INNER JOIN "players" ON ("id" = "player_id")'
|
data/spec/core/schema_spec.rb
CHANGED
@@ -1550,7 +1550,7 @@ describe "Schema Parser" do
|
|
1550
1550
|
specify "should correctly parse all supported data types" do
|
1551
1551
|
sm = Module.new do
|
1552
1552
|
def schema_parse_table(t, opts)
|
1553
|
-
[[:x, {:type=>schema_column_type(t.to_s)}]]
|
1553
|
+
[[:x, {:db_type=>t.to_s, :type=>schema_column_type(t.to_s)}]]
|
1554
1554
|
end
|
1555
1555
|
end
|
1556
1556
|
@db.extend(sm)
|
@@ -1563,6 +1563,7 @@ describe "Schema Parser" do
|
|
1563
1563
|
@db.schema(:"character varying").first.last[:type].should == :string
|
1564
1564
|
@db.schema(:varchar).first.last[:type].should == :string
|
1565
1565
|
@db.schema(:"varchar(255)").first.last[:type].should == :string
|
1566
|
+
@db.schema(:"varchar(255)").first.last[:max_length].should == 255
|
1566
1567
|
@db.schema(:text).first.last[:type].should == :string
|
1567
1568
|
@db.schema(:date).first.last[:type].should == :date
|
1568
1569
|
@db.schema(:datetime).first.last[:type].should == :datetime
|
@@ -2,13 +2,13 @@ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
|
2
2
|
|
3
3
|
describe "Sequel::Plugins::AutoValidations" do
|
4
4
|
before do
|
5
|
-
db = Sequel.mock(:fetch=>{:v=>1})
|
5
|
+
db = Sequel.mock(:fetch=>proc{|sql| sql =~ /a{51}/ ? {:v=>0} : {:v=>1}})
|
6
6
|
def db.schema_parse_table(*) true; end
|
7
7
|
def db.schema(t, *)
|
8
8
|
t = t.first_source if t.is_a?(Sequel::Dataset)
|
9
9
|
return [] if t != :test
|
10
10
|
[[:id, {:primary_key=>true, :type=>:integer, :allow_null=>false}],
|
11
|
-
[:name, {:primary_key=>false, :type=>:string, :allow_null=>false}],
|
11
|
+
[:name, {:primary_key=>false, :type=>:string, :allow_null=>false, :max_length=>50}],
|
12
12
|
[:num, {:primary_key=>false, :type=>:integer, :allow_null=>true}],
|
13
13
|
[:d, {:primary_key=>false, :type=>:date, :allow_null=>false}],
|
14
14
|
[:nnd, {:primary_key=>false, :type=>:string, :allow_null=>false, :ruby_default=>'nnd'}]]
|
@@ -42,6 +42,10 @@ describe "Sequel::Plugins::AutoValidations" do
|
|
42
42
|
@m.set(:d=>Date.today, :num=>1)
|
43
43
|
@m.valid?.should == false
|
44
44
|
@m.errors.should == {[:name, :num]=>["is already taken"]}
|
45
|
+
|
46
|
+
@m.set(:name=>'a'*51)
|
47
|
+
@m.valid?.should == false
|
48
|
+
@m.errors.should == {:name=>["is longer than 50 characters"]}
|
45
49
|
end
|
46
50
|
|
47
51
|
it "should handle databases that don't support index parsing" do
|
@@ -86,6 +90,13 @@ describe "Sequel::Plugins::AutoValidations" do
|
|
86
90
|
|
87
91
|
@c.skip_auto_validations(:unique)
|
88
92
|
@m.valid?.should == true
|
93
|
+
|
94
|
+
@m.set(:name=>'a'*51)
|
95
|
+
@m.valid?.should == false
|
96
|
+
@m.errors.should == {:name=>["is longer than 50 characters"]}
|
97
|
+
|
98
|
+
@c.skip_auto_validations(:max_length)
|
99
|
+
@m.valid?.should == true
|
89
100
|
end
|
90
101
|
|
91
102
|
it "should allow skipping all auto validations" do
|
@@ -95,6 +106,8 @@ describe "Sequel::Plugins::AutoValidations" do
|
|
95
106
|
@m.valid?.should == true
|
96
107
|
@m.set(:d=>'/', :num=>'a', :name=>'1')
|
97
108
|
@m.valid?.should == true
|
109
|
+
@m.set(:name=>'a'*51)
|
110
|
+
@m.valid?.should == true
|
98
111
|
end
|
99
112
|
|
100
113
|
it "should work correctly in subclasses" do
|
@@ -110,6 +123,10 @@ describe "Sequel::Plugins::AutoValidations" do
|
|
110
123
|
@m.set(:d=>Date.today, :num=>1)
|
111
124
|
@m.valid?.should == false
|
112
125
|
@m.errors.should == {[:name, :num]=>["is already taken"]}
|
126
|
+
|
127
|
+
@m.set(:name=>'a'*51)
|
128
|
+
@m.valid?.should == false
|
129
|
+
@m.errors.should == {:name=>["is longer than 50 characters"]}
|
113
130
|
end
|
114
131
|
|
115
132
|
it "should work correctly in STI subclasses" do
|
@@ -128,6 +145,10 @@ describe "Sequel::Plugins::AutoValidations" do
|
|
128
145
|
@m.valid?.should == false
|
129
146
|
@m.errors.should == {[:name, :num]=>["is already taken"]}
|
130
147
|
@m.db.sqls.should == ["SELECT count(*) AS count FROM test WHERE ((name = '1') AND (num = 1)) LIMIT 1"]
|
148
|
+
|
149
|
+
@m.set(:name=>'a'*51)
|
150
|
+
@m.valid?.should == false
|
151
|
+
@m.errors.should == {:name=>["is longer than 50 characters"]}
|
131
152
|
end
|
132
153
|
|
133
154
|
it "should work correctly when changing the dataset" do
|
@@ -38,6 +38,7 @@ describe "NestedAttributes plugin" do
|
|
38
38
|
@Artist.one_to_one :first_album, :class=>@Album, :key=>:artist_id
|
39
39
|
@Album.many_to_one :artist, :class=>@Artist, :reciprocal=>:albums
|
40
40
|
@Album.many_to_many :tags, :class=>@Tag, :left_key=>:album_id, :right_key=>:tag_id, :join_table=>:at
|
41
|
+
@Tag.many_to_many :albums, :class=>@Album, :left_key=>:tag_id, :right_key=>:album_id, :join_table=>:at
|
41
42
|
@Artist.nested_attributes :albums, :first_album, :destroy=>true, :remove=>true
|
42
43
|
@Artist.nested_attributes :concerts, :destroy=>true, :remove=>true
|
43
44
|
@Album.nested_attributes :artist, :tags, :destroy=>true, :remove=>true
|
@@ -117,9 +118,12 @@ describe "NestedAttributes plugin" do
|
|
117
118
|
end
|
118
119
|
|
119
120
|
it "should add new objects to the cached association array as soon as the *_attributes= method is called" do
|
120
|
-
a = @Artist.new({:name=>'Ar', :albums_attributes=>[{:name=>'Al', :tags_attributes=>[{:name=>'T'}]}]})
|
121
|
+
a = @Artist.new({:name=>'Ar', :first_album_attributes=>{:name=>'B'}, :albums_attributes=>[{:name=>'Al', :tags_attributes=>[{:name=>'T'}]}]})
|
121
122
|
a.albums.should == [@Album.new(:name=>'Al')]
|
123
|
+
a.albums.first.artist.should == a
|
122
124
|
a.albums.first.tags.should == [@Tag.new(:name=>'T')]
|
125
|
+
a.first_album.should == @Album.new(:name=>'B')
|
126
|
+
a.first_album.artist.should == a
|
123
127
|
end
|
124
128
|
|
125
129
|
it "should support creating new objects with composite primary keys" do
|
@@ -608,4 +612,31 @@ describe "NestedAttributes plugin" do
|
|
608
612
|
proc{al.set(:tags_attributes=>[{:id=>30, :name=>'T2', :number=>3}])}.should raise_error(Sequel::Error)
|
609
613
|
proc{al.set(:tags_attributes=>[{:name=>'T2', :number=>3}])}.should raise_error(Sequel::Error)
|
610
614
|
end
|
615
|
+
|
616
|
+
it "should allow per-call options via the set_nested_attributes method" do
|
617
|
+
@Tag.columns :id, :name, :number
|
618
|
+
@Album.nested_attributes :tags
|
619
|
+
|
620
|
+
al = @Album.load(:id=>10, :name=>'Al')
|
621
|
+
t = @Tag.load(:id=>30, :name=>'T', :number=>10)
|
622
|
+
al.associations[:tags] = [t]
|
623
|
+
al.set_nested_attributes(:tags, [{:id=>30, :name=>'T2'}, {:name=>'T3'}], :fields=>[:name])
|
624
|
+
@db.sqls.should == []
|
625
|
+
al.save
|
626
|
+
check_sql_array("UPDATE albums SET name = 'Al' WHERE (id = 10)",
|
627
|
+
"UPDATE tags SET name = 'T2' WHERE (id = 30)",
|
628
|
+
"INSERT INTO tags (name) VALUES ('T3')",
|
629
|
+
["INSERT INTO at (album_id, tag_id) VALUES (10, 1)", "INSERT INTO at (tag_id, album_id) VALUES (1, 10)"])
|
630
|
+
proc{al.set_nested_attributes(:tags, [{:id=>30, :name=>'T2', :number=>3}], :fields=>[:name])}.should raise_error(Sequel::Error)
|
631
|
+
proc{al.set_nested_attributes(:tags, [{:name=>'T2', :number=>3}], :fields=>[:name])}.should raise_error(Sequel::Error)
|
632
|
+
end
|
633
|
+
|
634
|
+
it "should have set_nested_attributes method raise error if called with a bad association" do
|
635
|
+
proc{@Album.load(:id=>10, :name=>'Al').set_nested_attributes(:tags2, [{:id=>30, :name=>'T2', :number=>3}], :fields=>[:name])}.should raise_error(Sequel::Error)
|
636
|
+
end
|
637
|
+
|
638
|
+
it "should have set_nested_attributes method raise error if called with an association that doesn't support nested attributes" do
|
639
|
+
@Tag.columns :id, :name, :number
|
640
|
+
proc{@Album.load(:id=>10, :name=>'Al').set_nested_attributes(:tags, [{:id=>30, :name=>'T2', :number=>3}], :fields=>[:name])}.should raise_error(Sequel::Error)
|
641
|
+
end
|
611
642
|
end
|
@@ -77,4 +77,16 @@ describe "pg_static_cache_updater extension" do
|
|
77
77
|
@db.fetch = {:v=>1234}
|
78
78
|
proc{@db.listen_for_static_cache_updates(Class.new(Sequel::Model(@db[:table])))}.should raise_error(Sequel::Error)
|
79
79
|
end
|
80
|
+
|
81
|
+
specify "#listen_for_static_cache_updates should handle a :before_thread_exit option" do
|
82
|
+
a = []
|
83
|
+
@db.listen_for_static_cache_updates([@model], :yield=>[nil, nil, 12345], :before_thread_exit=>proc{a << 1}).join
|
84
|
+
a.should == [1]
|
85
|
+
end
|
86
|
+
|
87
|
+
specify "#listen_for_static_cache_updates should call :before_thread_exit option even if listen raises an exception" do
|
88
|
+
a = []
|
89
|
+
@db.listen_for_static_cache_updates([@model], :yield=>[nil, nil, 12345], :after_listen=>proc{raise ArgumentError}, :before_thread_exit=>proc{a << 1}).join
|
90
|
+
a.should == [1]
|
91
|
+
end
|
80
92
|
end
|
@@ -31,25 +31,25 @@ describe "Sequel::Plugins::PreparedStatementsAssociations" do
|
|
31
31
|
|
32
32
|
specify "should run correct SQL for associations" do
|
33
33
|
@Artist.load(:id=>1).albums
|
34
|
-
@db.sqls.should == ["SELECT
|
34
|
+
@db.sqls.should == ["SELECT id, artist_id, id2, artist_id2 FROM albums WHERE (albums.artist_id = 1) -- prepared"]
|
35
35
|
|
36
36
|
@Artist.load(:id=>1).album
|
37
|
-
@db.sqls.should == ["SELECT
|
37
|
+
@db.sqls.should == ["SELECT id, artist_id, id2, artist_id2 FROM albums WHERE (albums.artist_id = 1) LIMIT 1 -- prepared"]
|
38
38
|
|
39
39
|
@Album.load(:id=>1, :artist_id=>2).artist
|
40
|
-
@db.sqls.should == ["SELECT
|
40
|
+
@db.sqls.should == ["SELECT id, id2 FROM artists WHERE (artists.id = 2) LIMIT 1 -- prepared"]
|
41
41
|
|
42
42
|
@Album.load(:id=>1, :artist_id=>2).tags
|
43
|
-
@db.sqls.should == ["SELECT tags
|
43
|
+
@db.sqls.should == ["SELECT tags.id, tags.id2 FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) WHERE (albums_tags.album_id = 1) -- prepared"]
|
44
44
|
|
45
45
|
@Album.load(:id=>1, :artist_id=>2).tag
|
46
|
-
@db.sqls.should == ["SELECT tags
|
46
|
+
@db.sqls.should == ["SELECT tags.id, tags.id2 FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) WHERE (albums_tags.album_id = 1) LIMIT 1 -- prepared"]
|
47
47
|
|
48
48
|
@Artist.load(:id=>1).tags
|
49
|
-
@db.sqls.should == ["SELECT tags
|
49
|
+
@db.sqls.should == ["SELECT tags.id, tags.id2 FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) WHERE (albums.artist_id = 1) -- prepared"]
|
50
50
|
|
51
51
|
@Artist.load(:id=>1).tag
|
52
|
-
@db.sqls.should == ["SELECT tags
|
52
|
+
@db.sqls.should == ["SELECT tags.id, tags.id2 FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) WHERE (albums.artist_id = 1) LIMIT 1 -- prepared"]
|
53
53
|
end
|
54
54
|
|
55
55
|
specify "should run correct SQL for composite key associations" do
|
@@ -63,25 +63,25 @@ describe "Sequel::Plugins::PreparedStatementsAssociations" do
|
|
63
63
|
@Artist.one_through_many :tag, :clone=>:tags
|
64
64
|
|
65
65
|
@Artist.load(:id=>1, :id2=>2).albums
|
66
|
-
@db.sqls.should == ["SELECT
|
66
|
+
@db.sqls.should == ["SELECT id, artist_id, id2, artist_id2 FROM albums WHERE ((albums.artist_id = 1) AND (albums.artist_id2 = 2)) -- prepared"]
|
67
67
|
|
68
68
|
@Artist.load(:id=>1, :id2=>2).album
|
69
|
-
@db.sqls.should == ["SELECT
|
69
|
+
@db.sqls.should == ["SELECT id, artist_id, id2, artist_id2 FROM albums WHERE ((albums.artist_id = 1) AND (albums.artist_id2 = 2)) LIMIT 1 -- prepared"]
|
70
70
|
|
71
71
|
@Album.load(:id=>1, :artist_id=>2, :artist_id2=>3).artist
|
72
|
-
@db.sqls.should == ["SELECT
|
72
|
+
@db.sqls.should == ["SELECT id, id2 FROM artists WHERE ((artists.id = 2) AND (artists.id2 = 3)) LIMIT 1 -- prepared"]
|
73
73
|
|
74
74
|
@Album.load(:id=>1, :artist_id=>2, :id2=>3).tags
|
75
|
-
@db.sqls.should == ["SELECT tags
|
75
|
+
@db.sqls.should == ["SELECT tags.id, tags.id2 FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.tag_id2 = tags.id2)) WHERE ((albums_tags.album_id = 1) AND (albums_tags.album_id2 = 3)) -- prepared"]
|
76
76
|
|
77
77
|
@Album.load(:id=>1, :artist_id=>2, :id2=>3).tag
|
78
|
-
@db.sqls.should == ["SELECT tags
|
78
|
+
@db.sqls.should == ["SELECT tags.id, tags.id2 FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.tag_id2 = tags.id2)) WHERE ((albums_tags.album_id = 1) AND (albums_tags.album_id2 = 3)) LIMIT 1 -- prepared"]
|
79
79
|
|
80
80
|
@Artist.load(:id=>1, :id2=>2).tags
|
81
|
-
@db.sqls.should == ["SELECT tags
|
81
|
+
@db.sqls.should == ["SELECT tags.id, tags.id2 FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.tag_id2 = tags.id2)) INNER JOIN albums ON ((albums.id = albums_tags.album_id) AND (albums.id2 = albums_tags.album_id2)) WHERE ((albums.artist_id = 1) AND (albums.artist_id2 = 2)) -- prepared"]
|
82
82
|
|
83
83
|
@Artist.load(:id=>1, :id2=>2).tag
|
84
|
-
@db.sqls.should == ["SELECT tags
|
84
|
+
@db.sqls.should == ["SELECT tags.id, tags.id2 FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.tag_id2 = tags.id2)) INNER JOIN albums ON ((albums.id = albums_tags.album_id) AND (albums.id2 = albums_tags.album_id2)) WHERE ((albums.artist_id = 1) AND (albums.artist_id2 = 2)) LIMIT 1 -- prepared"]
|
85
85
|
end
|
86
86
|
|
87
87
|
specify "should not run query if no objects can be associated" do
|
@@ -120,19 +120,19 @@ describe "Sequel::Plugins::PreparedStatementsAssociations" do
|
|
120
120
|
@Album.dataset = @Album.dataset.where(:a=>2)
|
121
121
|
@Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id
|
122
122
|
@Artist.load(:id=>1).albums
|
123
|
-
@db.sqls.should == ["SELECT
|
123
|
+
@db.sqls.should == ["SELECT id, artist_id, id2, artist_id2 FROM albums WHERE ((a = 2) AND (albums.artist_id = 1)) -- prepared"]
|
124
124
|
end
|
125
125
|
|
126
126
|
specify "should use a prepared statement if the :conditions association option" do
|
127
127
|
@Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id, :conditions=>{:a=>2}
|
128
128
|
@Artist.load(:id=>1).albums
|
129
|
-
@db.sqls.should == ["SELECT
|
129
|
+
@db.sqls.should == ["SELECT id, artist_id, id2, artist_id2 FROM albums WHERE ((a = 2) AND (albums.artist_id = 1)) -- prepared"]
|
130
130
|
end
|
131
131
|
|
132
132
|
specify "should not use a prepared statement if :conditions association option uses an identifier" do
|
133
133
|
@Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id, :conditions=>{Sequel.identifier('a')=>2}
|
134
134
|
@Artist.load(:id=>1).albums
|
135
|
-
@db.sqls.should == ["SELECT
|
135
|
+
@db.sqls.should == ["SELECT id, artist_id, id2, artist_id2 FROM albums WHERE ((a = 2) AND (albums.artist_id = 1)) -- prepared"]
|
136
136
|
end
|
137
137
|
|
138
138
|
specify "should run a regular query if :dataset option is used when defining the association" do
|