sequel 3.35.0 → 3.36.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/CHANGELOG +78 -0
  2. data/Rakefile +3 -3
  3. data/bin/sequel +3 -1
  4. data/doc/advanced_associations.rdoc +154 -11
  5. data/doc/migration.rdoc +18 -0
  6. data/doc/object_model.rdoc +541 -0
  7. data/doc/opening_databases.rdoc +4 -1
  8. data/doc/release_notes/3.36.0.txt +245 -0
  9. data/doc/schema_modification.rdoc +0 -6
  10. data/lib/sequel/adapters/do/mysql.rb +7 -0
  11. data/lib/sequel/adapters/jdbc.rb +11 -3
  12. data/lib/sequel/adapters/jdbc/mysql.rb +3 -5
  13. data/lib/sequel/adapters/jdbc/postgresql.rb +10 -8
  14. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  15. data/lib/sequel/adapters/mock.rb +2 -6
  16. data/lib/sequel/adapters/mysql.rb +3 -9
  17. data/lib/sequel/adapters/mysql2.rb +12 -11
  18. data/lib/sequel/adapters/postgres.rb +32 -40
  19. data/lib/sequel/adapters/shared/mssql.rb +15 -11
  20. data/lib/sequel/adapters/shared/mysql.rb +28 -3
  21. data/lib/sequel/adapters/shared/oracle.rb +5 -0
  22. data/lib/sequel/adapters/shared/postgres.rb +59 -5
  23. data/lib/sequel/adapters/shared/sqlite.rb +3 -13
  24. data/lib/sequel/adapters/sqlite.rb +0 -7
  25. data/lib/sequel/adapters/swift/mysql.rb +2 -5
  26. data/lib/sequel/adapters/tinytds.rb +1 -2
  27. data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
  28. data/lib/sequel/connection_pool/threaded.rb +9 -1
  29. data/lib/sequel/database/dataset_defaults.rb +3 -1
  30. data/lib/sequel/database/misc.rb +7 -1
  31. data/lib/sequel/database/query.rb +11 -3
  32. data/lib/sequel/database/schema_generator.rb +40 -9
  33. data/lib/sequel/database/schema_methods.rb +6 -1
  34. data/lib/sequel/dataset/actions.rb +5 -5
  35. data/lib/sequel/dataset/prepared_statements.rb +3 -1
  36. data/lib/sequel/dataset/query.rb +1 -1
  37. data/lib/sequel/extensions/migration.rb +28 -0
  38. data/lib/sequel/extensions/pg_auto_parameterize.rb +0 -9
  39. data/lib/sequel/extensions/pg_inet.rb +89 -0
  40. data/lib/sequel/extensions/pg_json.rb +178 -0
  41. data/lib/sequel/extensions/schema_dumper.rb +24 -6
  42. data/lib/sequel/model/associations.rb +19 -15
  43. data/lib/sequel/model/base.rb +11 -12
  44. data/lib/sequel/plugins/composition.rb +1 -2
  45. data/lib/sequel/plugins/eager_each.rb +59 -0
  46. data/lib/sequel/plugins/json_serializer.rb +41 -4
  47. data/lib/sequel/plugins/nested_attributes.rb +72 -52
  48. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  49. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  50. data/lib/sequel/version.rb +1 -1
  51. data/spec/adapters/postgres_spec.rb +271 -1
  52. data/spec/adapters/sqlite_spec.rb +11 -0
  53. data/spec/core/connection_pool_spec.rb +26 -1
  54. data/spec/core/database_spec.rb +19 -0
  55. data/spec/core/dataset_spec.rb +45 -5
  56. data/spec/core/expression_filters_spec.rb +31 -67
  57. data/spec/core/mock_adapter_spec.rb +4 -0
  58. data/spec/extensions/core_extensions_spec.rb +83 -0
  59. data/spec/extensions/eager_each_spec.rb +34 -0
  60. data/spec/extensions/inflector_spec.rb +0 -4
  61. data/spec/extensions/json_serializer_spec.rb +32 -1
  62. data/spec/extensions/migration_spec.rb +28 -0
  63. data/spec/extensions/nested_attributes_spec.rb +134 -1
  64. data/spec/extensions/optimistic_locking_spec.rb +15 -1
  65. data/spec/extensions/pg_hstore_spec.rb +1 -1
  66. data/spec/extensions/pg_inet_spec.rb +44 -0
  67. data/spec/extensions/pg_json_spec.rb +101 -0
  68. data/spec/extensions/prepared_statements_spec.rb +30 -0
  69. data/spec/extensions/rcte_tree_spec.rb +9 -0
  70. data/spec/extensions/schema_dumper_spec.rb +195 -7
  71. data/spec/extensions/serialization_spec.rb +4 -0
  72. data/spec/extensions/spec_helper.rb +9 -1
  73. data/spec/extensions/tactical_eager_loading_spec.rb +8 -0
  74. data/spec/integration/database_test.rb +5 -1
  75. data/spec/integration/prepared_statement_test.rb +20 -2
  76. data/spec/model/associations_spec.rb +27 -0
  77. data/spec/model/base_spec.rb +54 -0
  78. data/spec/model/model_spec.rb +6 -0
  79. data/spec/model/record_spec.rb +18 -0
  80. data/spec/rcov.opts +2 -0
  81. metadata +14 -3
@@ -54,4 +54,34 @@ describe "prepared_statements plugin" do
54
54
  c[1].should == c.load(:id=>1, :name=>'foo', :i=>2)
55
55
  @db.sqls.should == ["SELECT * FROM people WHERE (id = 1) LIMIT 1 -- read_only"]
56
56
  end
57
+
58
+ describe " with placeholder type specifiers" do
59
+ before do
60
+ @ds.meta_def(:requires_placeholder_type_specifiers?){true}
61
+ end
62
+
63
+ specify "should correctly handle without schema type" do
64
+ @c[1].should == @p
65
+ @db.sqls.should == ["SELECT * FROM people WHERE (id = 1) LIMIT 1 -- read_only"]
66
+ end
67
+
68
+ specify "should correctly handle with schema type" do
69
+ @c.db_schema[:id][:type] = :integer
70
+ ds = @c.send(:prepared_lookup)
71
+ def ds.literal_symbol_append(sql, v)
72
+ if @opts[:bind_vars] and match = /\A\$(.*)\z/.match(v.to_s)
73
+ s = match[1].split('__')[0].to_sym
74
+ if prepared_arg?(s)
75
+ literal_append(sql, prepared_arg(s))
76
+ else
77
+ sql << v.to_s
78
+ end
79
+ else
80
+ super
81
+ end
82
+ end
83
+ @c[1].should == @p
84
+ @db.sqls.should == ["SELECT * FROM people WHERE (id = 1) LIMIT 1 -- read_only"]
85
+ end
86
+ end
57
87
  end
@@ -30,6 +30,15 @@ describe Sequel::Model, "rcte_tree" do
30
30
  @o.descendants_dataset.sql.should == 'WITH t AS (SELECT * FROM nodes WHERE (parent_id = 2) UNION ALL SELECT nodes.* FROM nodes INNER JOIN t ON (t.id = nodes.parent_id)) SELECT * FROM t AS nodes'
31
31
  end
32
32
 
33
+ it "should use the correct SQL for lazy associations when recursive CTEs require column aliases" do
34
+ @c.dataset.meta_def(:recursive_cte_requires_column_aliases?){true}
35
+ @c.plugin :rcte_tree
36
+ @o.parent_dataset.sql.should == 'SELECT * FROM nodes WHERE (nodes.id = 1) LIMIT 1'
37
+ @o.children_dataset.sql.should == 'SELECT * FROM nodes WHERE (nodes.parent_id = 2)'
38
+ @o.ancestors_dataset.sql.should == 'WITH t(id, name, parent_id, i, pi) AS (SELECT id, name, parent_id, i, pi FROM nodes WHERE (id = 1) UNION ALL SELECT nodes.id, nodes.name, nodes.parent_id, nodes.i, nodes.pi FROM nodes INNER JOIN t ON (t.parent_id = nodes.id)) SELECT * FROM t AS nodes'
39
+ @o.descendants_dataset.sql.should == 'WITH t(id, name, parent_id, i, pi) AS (SELECT id, name, parent_id, i, pi FROM nodes WHERE (parent_id = 2) UNION ALL SELECT nodes.id, nodes.name, nodes.parent_id, nodes.i, nodes.pi FROM nodes INNER JOIN t ON (t.id = nodes.parent_id)) SELECT * FROM t AS nodes'
40
+ end
41
+
33
42
  it "should use the correct SQL for lazy associations when giving options" do
34
43
  @c.plugin :rcte_tree, :primary_key=>:i, :key=>:pi, :cte_name=>:cte, :order=>:name, :ancestors=>{:name=>:as}, :children=>{:name=>:cs}, :descendants=>{:name=>:ds}, :parent=>{:name=>:p}
35
44
  @o.p_dataset.sql.should == 'SELECT * FROM nodes WHERE (nodes.i = 4) ORDER BY name LIMIT 1'
@@ -50,9 +50,9 @@ add_index :t, [:b, :c], :unique=>true
50
50
  END_CODE
51
51
 
52
52
  g.dump_indexes(:drop_index=>:t).should == (<<END_CODE).strip
53
- drop_index :t, [:a]
54
- drop_index :t, [:c, :e], :name=>:blah
55
53
  drop_index :t, [:b, :c], :unique=>true
54
+ drop_index :t, [:c, :e], :name=>:blah
55
+ drop_index :t, [:a]
56
56
  END_CODE
57
57
  end
58
58
 
@@ -340,6 +340,112 @@ end
340
340
  END_MIG
341
341
  end
342
342
 
343
+ it "should honor the :index_names => false option to not include names of indexes" do
344
+ @d.meta_def(:indexes) do |t|
345
+ {:i1=>{:columns=>[:c1], :unique=>false},
346
+ :t1_c2_c1_index=>{:columns=>[:c2, :c1], :unique=>true}}
347
+ end
348
+ @d.dump_table_schema(:t1, :index_names=>false).should == "create_table(:t1, :ignore_index_errors=>true) do\n primary_key :c1\n String :c2, :size=>20\n \n index [:c1]\n index [:c2, :c1], :unique=>true\nend"
349
+ @d.dump_schema_migration(:index_names=>false).should == <<-END_MIG
350
+ Sequel.migration do
351
+ up do
352
+ create_table(:t1, :ignore_index_errors=>true) do
353
+ primary_key :c1
354
+ String :c2, :size=>20
355
+
356
+ index [:c1]
357
+ index [:c2, :c1], :unique=>true
358
+ end
359
+
360
+ create_table(:t2, :ignore_index_errors=>true) do
361
+ Integer :c1, :null=>false
362
+ BigDecimal :c2, :null=>false
363
+
364
+ primary_key [:c1, :c2]
365
+
366
+ index [:c1]
367
+ index [:c2, :c1], :unique=>true
368
+ end
369
+ end
370
+
371
+ down do
372
+ drop_table(:t2, :t1)
373
+ end
374
+ end
375
+ END_MIG
376
+ end
377
+
378
+ it "should make :index_names => :namespace option a noop if there is a global index namespace" do
379
+ @d.meta_def(:indexes) do |t|
380
+ {:i1=>{:columns=>[:c1], :unique=>false},
381
+ :t1_c2_c1_index=>{:columns=>[:c2, :c1], :unique=>false}}
382
+ end
383
+ @d.dump_table_schema(:t1, :index_names=>:namespace).should == "create_table(:t1, :ignore_index_errors=>true) do\n primary_key :c1\n String :c2, :size=>20\n \n index [:c1], :name=>:i1\n index [:c2, :c1]\nend"
384
+ @d.dump_schema_migration(:index_names=>:namespace).should == <<-END_MIG
385
+ Sequel.migration do
386
+ up do
387
+ create_table(:t1, :ignore_index_errors=>true) do
388
+ primary_key :c1
389
+ String :c2, :size=>20
390
+
391
+ index [:c1], :name=>:i1
392
+ index [:c2, :c1]
393
+ end
394
+
395
+ create_table(:t2, :ignore_index_errors=>true) do
396
+ Integer :c1, :null=>false
397
+ BigDecimal :c2, :null=>false
398
+
399
+ primary_key [:c1, :c2]
400
+
401
+ index [:c1], :name=>:i1
402
+ index [:c2, :c1], :name=>:t1_c2_c1_index
403
+ end
404
+ end
405
+
406
+ down do
407
+ drop_table(:t2, :t1)
408
+ end
409
+ end
410
+ END_MIG
411
+ end
412
+
413
+ it "should honor the :index_names => :namespace option to include names of indexes with prepended table name if there is no global index namespace" do
414
+ @d.meta_def(:global_index_namespace?){false}
415
+ @d.meta_def(:indexes) do |t|
416
+ {:i1=>{:columns=>[:c1], :unique=>false},
417
+ :t1_c2_c1_index=>{:columns=>[:c2, :c1], :unique=>false}}
418
+ end
419
+ @d.dump_table_schema(:t1, :index_names=>:namespace).should == "create_table(:t1, :ignore_index_errors=>true) do\n primary_key :c1\n String :c2, :size=>20\n \n index [:c1], :name=>:t1_i1\n index [:c2, :c1]\nend"
420
+ @d.dump_schema_migration(:index_names=>:namespace).should == <<-END_MIG
421
+ Sequel.migration do
422
+ up do
423
+ create_table(:t1, :ignore_index_errors=>true) do
424
+ primary_key :c1
425
+ String :c2, :size=>20
426
+
427
+ index [:c1], :name=>:t1_i1
428
+ index [:c2, :c1]
429
+ end
430
+
431
+ create_table(:t2, :ignore_index_errors=>true) do
432
+ Integer :c1, :null=>false
433
+ BigDecimal :c2, :null=>false
434
+
435
+ primary_key [:c1, :c2]
436
+
437
+ index [:c1], :name=>:t2_i1
438
+ index [:c2, :c1], :name=>:t2_t1_c2_c1_index
439
+ end
440
+ end
441
+
442
+ down do
443
+ drop_table(:t2, :t1)
444
+ end
445
+ end
446
+ END_MIG
447
+ end
448
+
343
449
  it "should honor the :indexes => false option to not include indexes" do
344
450
  @d.meta_def(:indexes) do |t|
345
451
  {:i1=>{:columns=>[:c1], :unique=>false},
@@ -397,8 +503,84 @@ Sequel.migration do
397
503
  end
398
504
 
399
505
  down do
506
+ drop_index :t1, [:c2, :c1], :ignore_errors=>true, :unique=>true
400
507
  drop_index :t1, [:c1], :ignore_errors=>true, :name=>:i1
508
+ end
509
+ end
510
+ END_MIG
511
+ end
512
+
513
+ it "should honor the :index_names => false option to not include names of indexes when dumping just indexes as a migration" do
514
+ @d.meta_def(:tables){|o| [:t1]}
515
+ @d.meta_def(:indexes) do |t|
516
+ {:i1=>{:columns=>[:c1], :unique=>false},
517
+ :t1_c2_c1_index=>{:columns=>[:c2, :c1], :unique=>true}}
518
+ end
519
+ @d.dump_indexes_migration(:index_names=>false).should == <<-END_MIG
520
+ Sequel.migration do
521
+ up do
522
+ add_index :t1, [:c1], :ignore_errors=>true
523
+ add_index :t1, [:c2, :c1], :ignore_errors=>true, :unique=>true
524
+ end
525
+
526
+ down do
401
527
  drop_index :t1, [:c2, :c1], :ignore_errors=>true, :unique=>true
528
+ drop_index :t1, [:c1], :ignore_errors=>true
529
+ end
530
+ end
531
+ END_MIG
532
+ end
533
+
534
+ it "should honor the :index_names => :namespace option be a noop if there is a global index namespace" do
535
+ @d.meta_def(:tables){|o| [:t1, :t2]}
536
+ @d.meta_def(:indexes) do |t|
537
+ {:i1=>{:columns=>[:c1], :unique=>false},
538
+ :t1_c2_c1_index=>{:columns=>[:c2, :c1], :unique=>false}}
539
+ end
540
+ @d.dump_indexes_migration(:index_names=>:namespace).should == <<-END_MIG
541
+ Sequel.migration do
542
+ up do
543
+ add_index :t1, [:c1], :ignore_errors=>true, :name=>:i1
544
+ add_index :t1, [:c2, :c1], :ignore_errors=>true
545
+
546
+ add_index :t2, [:c1], :ignore_errors=>true, :name=>:i1
547
+ add_index :t2, [:c2, :c1], :ignore_errors=>true, :name=>:t1_c2_c1_index
548
+ end
549
+
550
+ down do
551
+ drop_index :t2, [:c2, :c1], :ignore_errors=>true, :name=>:t1_c2_c1_index
552
+ drop_index :t2, [:c1], :ignore_errors=>true, :name=>:i1
553
+
554
+ drop_index :t1, [:c2, :c1], :ignore_errors=>true
555
+ drop_index :t1, [:c1], :ignore_errors=>true, :name=>:i1
556
+ end
557
+ end
558
+ END_MIG
559
+ end
560
+
561
+ it "should honor the :index_names => :namespace option to include names of indexes with prepended table name when dumping just indexes as a migration if there is no global index namespace" do
562
+ @d.meta_def(:global_index_namespace?){false}
563
+ @d.meta_def(:tables){|o| [:t1, :t2]}
564
+ @d.meta_def(:indexes) do |t|
565
+ {:i1=>{:columns=>[:c1], :unique=>false},
566
+ :t1_c2_c1_index=>{:columns=>[:c2, :c1], :unique=>false}}
567
+ end
568
+ @d.dump_indexes_migration(:index_names=>:namespace).should == <<-END_MIG
569
+ Sequel.migration do
570
+ up do
571
+ add_index :t1, [:c1], :ignore_errors=>true, :name=>:t1_i1
572
+ add_index :t1, [:c2, :c1], :ignore_errors=>true
573
+
574
+ add_index :t2, [:c1], :ignore_errors=>true, :name=>:t2_i1
575
+ add_index :t2, [:c2, :c1], :ignore_errors=>true, :name=>:t2_t1_c2_c1_index
576
+ end
577
+
578
+ down do
579
+ drop_index :t2, [:c2, :c1], :ignore_errors=>true, :name=>:t2_t1_c2_c1_index
580
+ drop_index :t2, [:c1], :ignore_errors=>true, :name=>:t2_i1
581
+
582
+ drop_index :t1, [:c2, :c1], :ignore_errors=>true
583
+ drop_index :t1, [:c1], :ignore_errors=>true, :name=>:t1_i1
402
584
  end
403
585
  end
404
586
  END_MIG
@@ -508,7 +690,9 @@ END_MIG
508
690
  ["double precision", "timestamp with time zone", "timestamp without time zone",
509
691
  "time with time zone", "time without time zone", "character varying(20)"] +
510
692
  %w"nvarchar ntext smalldatetime smallmoney binary varbinary nchar" +
511
- ["timestamp(6) without time zone", "timestamp(6) with time zone", "int(12) unsigned", 'bigint unsigned', 'tinyint(3) unsigned', 'identity', 'int identity']
693
+ ["timestamp(6) without time zone", "timestamp(6) with time zone", 'mediumint(10) unsigned', 'int(9) unsigned',
694
+ 'int(10) unsigned', "int(12) unsigned", 'bigint unsigned', 'tinyint(3) unsigned', 'identity', 'int identity'] +
695
+ %w"integer(10)"
512
696
  @d.meta_def(:schema) do |t, *o|
513
697
  i = 0
514
698
  types.map{|x| [:"c#{i+=1}", {:db_type=>x, :allow_null=>true}]}
@@ -579,10 +763,14 @@ create_table(:x) do
579
763
  DateTime :c62, :size=>6
580
764
  DateTime :c63, :size=>6
581
765
  Integer :c64
582
- Bignum :c65
583
- Integer :c66
584
- Integer :c67
585
- Integer :c68
766
+ Integer :c65
767
+ Bignum :c66
768
+ Bignum :c67
769
+ Bignum :c68
770
+ Integer :c69
771
+ Integer :c70
772
+ Integer :c71
773
+ Integer :c72
586
774
  end
587
775
  END_MIG
588
776
  end
@@ -72,6 +72,10 @@ describe "Serialization plugin" do
72
72
  MODEL_DB.sqls.should == ["INSERT INTO items (abc) VALUES ('olleh')"]
73
73
  end
74
74
 
75
+ it "should raise an error if specificing serializer as an unregistered symbol" do
76
+ proc{@c.plugin :serialization, :foo, :abc}.should raise_error(Sequel::Error)
77
+ end
78
+
75
79
  it "should translate values to and from yaml serialization format using accessor methods" do
76
80
  @c.set_primary_key :id
77
81
  @c.plugin :serialization, :yaml, :abc, :def
@@ -8,7 +8,15 @@ unless Sequel.const_defined?('Model')
8
8
  require 'sequel/model'
9
9
  end
10
10
 
11
- Sequel.extension(*%w'string_date_time inflector pagination query pretty_table blank migration schema_dumper looser_typecasting sql_expr thread_local_timezones to_dot columns_introspection server_block arbitrary_servers pg_auto_parameterize pg_statement_cache pg_hstore pg_hstore_ops schema_caching null_dataset select_remove query_literals')
11
+ begin
12
+ # Attempt to load ActiveSupport inflector first, so Sequel inflector
13
+ # can override it.
14
+ require 'active_support/inflector'
15
+ require 'active_support/string/inflections'
16
+ rescue LoadError
17
+ end
18
+
19
+ Sequel.extension(*%w'string_date_time inflector pagination query pretty_table blank migration schema_dumper looser_typecasting sql_expr thread_local_timezones to_dot columns_introspection server_block arbitrary_servers pg_auto_parameterize pg_statement_cache pg_hstore pg_hstore_ops pg_inet schema_caching null_dataset select_remove query_literals')
12
20
  {:hook_class_methods=>[], :schema=>[], :validation_class_methods=>[]}.each{|p, opts| Sequel::Model.plugin(p, *opts)}
13
21
 
14
22
  Sequel::Dataset.introspect_all_columns if ENV['SEQUEL_COLUMNS_INTROSPECTION']
@@ -52,4 +52,12 @@ describe "Sequel::Plugins::TacticalEagerLoading" do
52
52
  @ds.should_not_receive(:eager_load)
53
53
  ts.map{|x| x.parent}.should == [ts[2], ts[3], nil, nil]
54
54
  end
55
+
56
+ it "should handle case where an association is valid on an instance, but not on all instances" do
57
+ c = Class.new(@c)
58
+ c.many_to_one :parent2, :class=>@c, :key=>:parent_id
59
+ @c.dataset.row_proc = proc{|r| (r[:parent_id] == 101 ? c : @c).call(r)}
60
+ @c.all{|x| x.parent2 if x.is_a?(c)}
61
+ end
62
+
55
63
  end
@@ -24,7 +24,11 @@ describe Sequel::Database do
24
24
  begin
25
25
  INTEGRATION_DB << "SELECT"
26
26
  rescue Sequel::DatabaseError=>e
27
- e.wrapped_exception.should be_a_kind_of(Exception)
27
+ if defined?(Java::JavaLang::Exception)
28
+ (e.wrapped_exception.is_a?(Exception) || e.wrapped_exception.is_a?(Java::JavaLang::Exception)).should be_true
29
+ else
30
+ e.wrapped_exception.should be_a_kind_of(Exception)
31
+ end
28
32
  end
29
33
  end
30
34
 
@@ -17,6 +17,7 @@ describe "Prepared Statements and Bound Arguments" do
17
17
  end
18
18
 
19
19
  specify "should support bound variables when selecting" do
20
+ @ds.filter(:numb=>:$n).call(:each, :n=>10){|h| h.should == {:id=>1, :numb=>10}}
20
21
  @ds.filter(:numb=>:$n).call(:select, :n=>10).should == [{:id=>1, :numb=>10}]
21
22
  @ds.filter(:numb=>:$n).call(:all, :n=>10).should == [{:id=>1, :numb=>10}]
22
23
  @ds.filter(:numb=>:$n).call(:first, :n=>10).should == {:id=>1, :numb=>10}
@@ -25,7 +26,9 @@ describe "Prepared Statements and Bound Arguments" do
25
26
  @ds.filter(:numb=>:$n).call([:to_hash_groups, :id, :numb], :n=>10).should == {1=>[10]}
26
27
  end
27
28
 
28
- specify "should support blocks for select, all, and map " do
29
+ specify "should support blocks for each, select, all, and map when using bound variables" do
30
+ a = []
31
+ @ds.filter(:numb=>:$n).call(:each, :n=>10){|r| r[:numb] *= 2; a << r}; a.should == [{:id=>1, :numb=>20}]
29
32
  @ds.filter(:numb=>:$n).call(:select, :n=>10){|r| r[:numb] *= 2}.should == [{:id=>1, :numb=>20}]
30
33
  @ds.filter(:numb=>:$n).call(:all, :n=>10){|r| r[:numb] *= 2}.should == [{:id=>1, :numb=>20}]
31
34
  @ds.filter(:numb=>:$n).call([:map], :n=>10){|r| r[:numb] * 2}.should == [20]
@@ -125,6 +128,8 @@ describe "Prepared Statements and Bound Arguments" do
125
128
  end
126
129
 
127
130
  specify "should support prepared statements when selecting" do
131
+ @ds.filter(:numb=>:$n).prepare(:each, :select_n)
132
+ @db.call(:select_n, :n=>10){|h| h.should == {:id=>1, :numb=>10}}
128
133
  @ds.filter(:numb=>:$n).prepare(:select, :select_n)
129
134
  @db.call(:select_n, :n=>10).should == [{:id=>1, :numb=>10}]
130
135
  @ds.filter(:numb=>:$n).prepare(:all, :select_n)
@@ -137,7 +142,20 @@ describe "Prepared Statements and Bound Arguments" do
137
142
  @db.call(:select_n, :n=>10).should == {1=>10}
138
143
  end
139
144
 
140
- specify "should support prepared statements being call multiple times with different arguments" do
145
+ specify "should support blocks for each, select, all, and map when using prepared statements" do
146
+ a = []
147
+ @ds.filter(:numb=>:$n).prepare(:each, :select_n).call(:n=>10){|r| r[:numb] *= 2; a << r}; a.should == [{:id=>1, :numb=>20}]
148
+ a = []
149
+ @db.call(:select_n, :n=>10){|r| r[:numb] *= 2; a << r}; a.should == [{:id=>1, :numb=>20}]
150
+ @ds.filter(:numb=>:$n).prepare(:select, :select_n).call(:n=>10){|r| r[:numb] *= 2}.should == [{:id=>1, :numb=>20}]
151
+ @db.call(:select_n, :n=>10){|r| r[:numb] *= 2}.should == [{:id=>1, :numb=>20}]
152
+ @ds.filter(:numb=>:$n).prepare(:all, :select_n).call(:n=>10){|r| r[:numb] *= 2}.should == [{:id=>1, :numb=>20}]
153
+ @db.call(:select_n, :n=>10){|r| r[:numb] *= 2}.should == [{:id=>1, :numb=>20}]
154
+ @ds.filter(:numb=>:$n).prepare([:map], :select_n).call(:n=>10){|r| r[:numb] *= 2}.should == [20]
155
+ @db.call(:select_n, :n=>10){|r| r[:numb] *= 2}.should == [20]
156
+ end
157
+
158
+ specify "should support prepared statements being called multiple times with different arguments" do
141
159
  @ds.filter(:numb=>:$n).prepare(:select, :select_n)
142
160
  @db.call(:select_n, :n=>10).should == [{:id=>1, :numb=>10}]
143
161
  @db.call(:select_n, :n=>0).should == []
@@ -457,6 +457,20 @@ describe Sequel::Model, "many_to_one" do
457
457
  MODEL_DB.sqls.should == []
458
458
  end
459
459
 
460
+ it "should have the setter not modify the reciprocal if set to same value as current" do
461
+ @c2.many_to_one :parent, :class => @c2
462
+ @c2.one_to_many :children, :class => @c2, :key=>:parent_id
463
+
464
+ c1 = @c2.load(:id => 1, :parent_id=>nil)
465
+ c2 = @c2.load(:id => 2, :parent_id=>1)
466
+ c3 = @c2.load(:id => 3, :parent_id=>1)
467
+ c1.associations[:children] = [c2, c3]
468
+ c2.associations[:parent] = c1
469
+ c2.parent = c1
470
+ c1.children.should == [c2, c3]
471
+ MODEL_DB.sqls.should == []
472
+ end
473
+
460
474
  it "should get all matching records and only return the first if :key option is set to nil" do
461
475
  @c2.one_to_many :children, :class => @c2, :key=>:parent_id
462
476
  @c2.many_to_one :first_grand_parent, :class => @c2, :key=>nil, :eager_graph=>:children, :dataset=>proc{model.filter(:children_id=>parent_id)}
@@ -959,6 +973,19 @@ describe Sequel::Model, "one_to_one" do
959
973
  f.child.should == nil
960
974
  end
961
975
 
976
+ it "should have the setter not modify the reciprocal if set to same value as current" do
977
+ @c2.one_to_one :parent, :class => @c2, :key=>:parent_id
978
+ @c2.many_to_one :child, :class => @c2, :key=>:parent_id
979
+
980
+ c1 = @c2.load(:id => 1, :parent_id=>nil)
981
+ c2 = @c2.load(:id => 2, :parent_id=>1)
982
+ c1.associations[:child] = c2
983
+ c2.associations[:parent] = c1
984
+ c2.parent = c1
985
+ c1.child.should == c2
986
+ MODEL_DB.sqls.should == []
987
+ end
988
+
962
989
  it "should not add associations methods directly to class" do
963
990
  @c2.one_to_one :parent, :class => @c2
964
991
  @c2.instance_methods.collect{|x| x.to_s}.should(include('parent'))
@@ -9,6 +9,12 @@ describe "Model attribute setters" do
9
9
  MODEL_DB.reset
10
10
  end
11
11
 
12
+ specify "refresh should return self" do
13
+ @o = @c[1]
14
+ @o.stub(:_refresh).and_return([])
15
+ @o.refresh.should == @o
16
+ end
17
+
12
18
  it "should mark the column value as changed" do
13
19
  @o.changed_columns.should == []
14
20
 
@@ -349,6 +355,54 @@ describe "Model.qualified_primary_key_hash" do
349
355
  end
350
356
  end
351
357
 
358
+ describe "Model.db" do
359
+ before do
360
+ @db = Sequel.mock
361
+ @databases = Sequel::DATABASES.dup
362
+ @model_db = Sequel::Model.db
363
+ Sequel::Model.db = nil
364
+ Sequel::DATABASES.clear
365
+ end
366
+ after do
367
+ Sequel::Model.instance_variable_get(:@db).should == nil
368
+ Sequel::DATABASES.replace(@databases)
369
+ Sequel::Model.db = @model_db
370
+ end
371
+
372
+ specify "should be required when create named model classes" do
373
+ begin
374
+ proc{class ModelTest < Sequel::Model; end}.should raise_error(Sequel::Error)
375
+ ensure
376
+ Object.send(:remove_const, :ModelTest)
377
+ end
378
+ end
379
+
380
+ specify "should be required when creating anonymous model classes without a database" do
381
+ proc{Sequel::Model(:foo)}.should raise_error(Sequel::Error)
382
+ end
383
+
384
+ specify "should not be required when creating anonymous model classes with a database" do
385
+ Sequel::Model(@db).db.should == @db
386
+ Sequel::Model(@db[:foo]).db.should == @db
387
+ end
388
+
389
+ specify "should work correctly when subclassing anonymous model classes with a database" do
390
+ begin
391
+ Class.new(Sequel::Model(@db)).db.should == @db
392
+ Class.new(Sequel::Model(@db[:foo])).db.should == @db
393
+ class ModelTest < Sequel::Model(@db)
394
+ db.should == @db
395
+ end
396
+ class ModelTest2 < Sequel::Model(@db[:foo])
397
+ db.should == @db
398
+ end
399
+ ensure
400
+ Object.send(:remove_const, :ModelTest)
401
+ Object.send(:remove_const, :ModelTest2)
402
+ end
403
+ end
404
+ end
405
+
352
406
  describe "Model.db=" do
353
407
  before do
354
408
  @db1 = Sequel.mock