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