sequel 4.26.0 → 4.29.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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +62 -0
  3. data/README.rdoc +4 -4
  4. data/bin/sequel +8 -0
  5. data/doc/opening_databases.rdoc +5 -7
  6. data/doc/postgresql.rdoc +13 -0
  7. data/doc/release_notes/4.27.0.txt +78 -0
  8. data/doc/release_notes/4.28.0.txt +57 -0
  9. data/doc/release_notes/4.29.0.txt +41 -0
  10. data/doc/thread_safety.rdoc +1 -1
  11. data/doc/transactions.rdoc +4 -1
  12. data/doc/validations.rdoc +1 -1
  13. data/lib/sequel/adapters/jdbc/postgresql.rb +1 -1
  14. data/lib/sequel/adapters/oracle.rb +1 -1
  15. data/lib/sequel/adapters/postgres.rb +9 -3
  16. data/lib/sequel/adapters/shared/postgres.rb +2 -2
  17. data/lib/sequel/adapters/sqlanywhere.rb +3 -3
  18. data/lib/sequel/core.rb +8 -18
  19. data/lib/sequel/database/query.rb +7 -2
  20. data/lib/sequel/database/schema_generator.rb +17 -4
  21. data/lib/sequel/database/transactions.rb +3 -3
  22. data/lib/sequel/dataset/actions.rb +38 -9
  23. data/lib/sequel/dataset/sql.rb +8 -3
  24. data/lib/sequel/extensions/date_arithmetic.rb +3 -0
  25. data/lib/sequel/extensions/pg_json_ops.rb +58 -5
  26. data/lib/sequel/extensions/schema_dumper.rb +12 -1
  27. data/lib/sequel/model/base.rb +32 -16
  28. data/lib/sequel/plugins/active_model.rb +7 -0
  29. data/lib/sequel/plugins/before_after_save.rb +48 -0
  30. data/lib/sequel/plugins/boolean_subsets.rb +56 -0
  31. data/lib/sequel/plugins/csv_serializer.rb +1 -1
  32. data/lib/sequel/plugins/defaults_setter.rb +8 -4
  33. data/lib/sequel/plugins/json_serializer.rb +25 -4
  34. data/lib/sequel/plugins/list.rb +9 -9
  35. data/lib/sequel/plugins/subset_conditions.rb +36 -0
  36. data/lib/sequel/plugins/uuid.rb +72 -0
  37. data/lib/sequel/version.rb +1 -1
  38. data/spec/adapters/postgres_spec.rb +11 -1
  39. data/spec/bin_spec.rb +4 -0
  40. data/spec/core/database_spec.rb +35 -0
  41. data/spec/core/dataset_spec.rb +34 -0
  42. data/spec/core/schema_generator_spec.rb +13 -0
  43. data/spec/core/schema_spec.rb +17 -0
  44. data/spec/extensions/active_model_spec.rb +70 -108
  45. data/spec/extensions/before_after_save_spec.rb +40 -0
  46. data/spec/extensions/boolean_subsets_spec.rb +47 -0
  47. data/spec/extensions/date_arithmetic_spec.rb +17 -0
  48. data/spec/extensions/json_serializer_spec.rb +7 -0
  49. data/spec/extensions/list_spec.rb +11 -0
  50. data/spec/extensions/pg_json_ops_spec.rb +46 -0
  51. data/spec/extensions/schema_dumper_spec.rb +18 -0
  52. data/spec/extensions/subset_conditions_spec.rb +38 -0
  53. data/spec/extensions/uuid_spec.rb +106 -0
  54. data/spec/integration/dataset_test.rb +14 -0
  55. data/spec/integration/prepared_statement_test.rb +3 -3
  56. data/spec/integration/schema_test.rb +7 -1
  57. data/spec/integration/transaction_test.rb +22 -0
  58. data/spec/model/class_dataset_methods_spec.rb +4 -0
  59. data/spec/model/model_spec.rb +1 -1
  60. data/spec/model/record_spec.rb +7 -1
  61. metadata +16 -2
@@ -0,0 +1,72 @@
1
+ require 'securerandom'
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The uuid plugin creates hooks that automatically create a uuid for every
6
+ # instance. Note that this uses SecureRandom.uuid to create UUIDs, and
7
+ # that method is not defined on ruby 1.8.7. If you would like to use this
8
+ # on ruby 1.8.7, you need to override the Model#create_uuid private method
9
+ # to return a valid uuid.
10
+ #
11
+ # Usage:
12
+ #
13
+ # # Uuid all model instances using +uuid+
14
+ # # (called before loading subclasses)
15
+ # Sequel::Model.plugin :uuid
16
+ #
17
+ # # Uuid Album instances, with custom column name
18
+ # Album.plugin :uuid, :field=>my_uuid
19
+ module Uuid
20
+ # Configure the plugin by setting the available options. Note that
21
+ # if this method is run more than once, previous settings are ignored,
22
+ # and it will just use the settings given or the default settings. Options:
23
+ # :field :: The field to hold the uuid (default: :uuid)
24
+ # :force :: Whether to overwrite an existing uuid (default: false)
25
+ def self.configure(model, opts=OPTS)
26
+ model.instance_eval do
27
+ @uuid_field = opts[:field]||:uuid
28
+ @uuid_overwrite = opts[:force]||false
29
+ end
30
+ end
31
+
32
+ module ClassMethods
33
+ # The field to store the uuid
34
+ attr_reader :uuid_field
35
+
36
+ # Whether to overwrite the create uuid if it already exists
37
+ def uuid_overwrite?
38
+ @uuid_overwrite
39
+ end
40
+
41
+ Plugins.inherited_instance_variables(self, :@uuid_field=>nil, :@uuid_overwrite=>nil)
42
+ end
43
+
44
+ module InstanceMethods
45
+ private
46
+
47
+ # Set the uuid when creating
48
+ def _before_validation
49
+ set_uuid if new?
50
+ super
51
+ end
52
+
53
+ # Create a new UUID. This method can be overridden to use a separate
54
+ # method for creating UUIDs. Note that this method does not work on
55
+ # ruby 1.8.7, you will have to override it if you are using ruby 1.8.7.
56
+ def create_uuid
57
+ SecureRandom.uuid
58
+ end
59
+
60
+ # If the object has accessor methods for the uuid field, and the uuid
61
+ # value is nil or overwriting it is allowed, set the uuid.
62
+ def set_uuid(uuid=create_uuid)
63
+ field = model.uuid_field
64
+ meth = :"#{field}="
65
+ if respond_to?(field) && respond_to?(meth) && (model.uuid_overwrite? || get_column_value(field).nil?)
66
+ set_column_value(meth, uuid)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -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 = 26
6
+ MINOR = 29
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
@@ -20,6 +20,7 @@ end
20
20
  describe "PostgreSQL", '#create_table' do
21
21
  before do
22
22
  @db = DB
23
+ @db.test_connection
23
24
  DB.sqls.clear
24
25
  end
25
26
  after do
@@ -986,7 +987,7 @@ describe "A PostgreSQL database" do
986
987
  t2 = "ruby sequel " * 1000
987
988
  @db[:posts].insert(:title=>t1)
988
989
  @db[:posts].insert(:title=>t2)
989
- @db[:posts].full_text_search(:title, 'ruby & sequel', :rank=>true).select_map(:title).must_equal [t1, t2]
990
+ @db[:posts].full_text_search(:title, 'ruby & sequel', :rank=>true).select_map(:title).must_equal [t2, t1]
990
991
  end
991
992
 
992
993
  it "should support spatial indexes" do
@@ -2868,6 +2869,15 @@ describe 'PostgreSQL json type' do
2868
2869
  end
2869
2870
  end
2870
2871
 
2872
+ if DB.server_version >= 90500 && json_type == :jsonb
2873
+ @db.get(pg_json.call([nil, 2]).op.strip_nulls[1]).must_equal 2
2874
+ @db.get(pg_json.call([nil, 2]).op.pretty).must_equal "[\n null,\n 2\n]"
2875
+ @db.from((jo - 'b').keys.as(:k)).select_order_map(:k).must_equal %w'a'
2876
+ @db.from(jo.delete_path(['b','c'])['b'].keys.as(:k)).select_order_map(:k).must_equal %w'd'
2877
+ @db.from(jo.concat('c'=>'d').keys.as(:k)).select_order_map(:k).must_equal %w'a b c'
2878
+ @db.get(jo.set(%w'a', 'f'=>'g')['a']['f']).must_equal 'g'
2879
+ end
2880
+
2871
2881
  @db.from(jo.keys.as(:k)).select_order_map(:k).must_equal %w'a b'
2872
2882
  @db.from(jo.each).select_order_map(:key).must_equal %w'a b'
2873
2883
  @db.from(jo.each).order(:key).select_map(:value).must_equal [1, {'c'=>2, 'd'=>{'e'=>3}}]
data/spec/bin_spec.rb CHANGED
@@ -203,6 +203,10 @@ END
203
203
  bin(:args=>'-S foo -C', :stderr=>true).must_equal "Error: Cannot specify -S and -C together\n"
204
204
  end
205
205
 
206
+ it "should warn if providing too many arguments" do
207
+ bin(:args=>'-c "" "" 1 2 3 4', :stderr=>true).must_equal "Warning: last 5 arguments ignored\n"
208
+ end
209
+
206
210
  it "should use a mock database if no database is given" do
207
211
  bin(:args=>'-c "print DB.adapter_scheme"', :no_conn=>true).must_equal "mock"
208
212
  end
@@ -614,6 +614,17 @@ describe "Database#table_exists?" do
614
614
  db.table_exists?(:b).must_equal true
615
615
  db.table_exists?(:c).must_equal true
616
616
  end
617
+
618
+ it "should use a savepoint if inside a transaction" do
619
+ db = Sequel.mock(:fetch=>[Sequel::Error, [], [{:a=>1}]])
620
+ def db.supports_savepoints?; true end
621
+ db.transaction do
622
+ db.table_exists?(:a).must_equal false
623
+ end
624
+ db.sqls.must_equal ["BEGIN", "SAVEPOINT autopoint_1", "SELECT NULL AS nil FROM a LIMIT 1", "ROLLBACK TO SAVEPOINT autopoint_1", "COMMIT"]
625
+ db.table_exists?(:b).must_equal true
626
+ db.table_exists?(:c).must_equal true
627
+ end
617
628
  end
618
629
 
619
630
  DatabaseTransactionSpecs = shared_description do
@@ -964,6 +975,30 @@ describe "Database#transaction with savepoint support" do
964
975
 
965
976
  include DatabaseTransactionSpecs
966
977
 
978
+ it "should support :retry_on option for automatically retrying transactions when using :savepoint option" do
979
+ a = []
980
+ @db.transaction do
981
+ @db.transaction(:retry_on=>Sequel::SerializationFailure, :savepoint=>true) do
982
+ a << 1
983
+ raise Sequel::SerializationFailure if a.length == 1
984
+ end
985
+ end
986
+ @db.sqls.must_equal ["BEGIN", "SAVEPOINT autopoint_1", "ROLLBACK TO SAVEPOINT autopoint_1", "SAVEPOINT autopoint_1", "RELEASE SAVEPOINT autopoint_1", "COMMIT"]
987
+ a.must_equal [1, 1]
988
+ end
989
+
990
+ it "should support :retry_on option for automatically retrying transactions inside an :auto_savepoint transaction" do
991
+ a = []
992
+ @db.transaction(:auto_savepoint=>true) do
993
+ @db.transaction(:retry_on=>Sequel::SerializationFailure) do
994
+ a << 1
995
+ raise Sequel::SerializationFailure if a.length == 1
996
+ end
997
+ end
998
+ @db.sqls.must_equal ["BEGIN", "SAVEPOINT autopoint_1", "ROLLBACK TO SAVEPOINT autopoint_1", "SAVEPOINT autopoint_1", "RELEASE SAVEPOINT autopoint_1", "COMMIT"]
999
+ a.must_equal [1, 1]
1000
+ end
1001
+
967
1002
  it "should support after_commit inside savepoints" do
968
1003
  @db.transaction do
969
1004
  @db.after_commit{@db.execute('foo')}
@@ -2728,6 +2728,23 @@ describe "Dataset#single_record" do
2728
2728
  end
2729
2729
  end
2730
2730
 
2731
+ describe "Dataset#single_record!" do
2732
+ before do
2733
+ @db = Sequel.mock
2734
+ end
2735
+
2736
+ it "should call each and return the first record" do
2737
+ @db.fetch = [{:a=>1}, {:a=>2}]
2738
+ @db[:test].single_record!.must_equal(:a=>1)
2739
+ @db.sqls.must_equal ['SELECT * FROM test']
2740
+ end
2741
+
2742
+ it "should return nil if no record is present" do
2743
+ @db[:test].single_record!.must_equal nil
2744
+ @db.sqls.must_equal ['SELECT * FROM test']
2745
+ end
2746
+ end
2747
+
2731
2748
  describe "Dataset#single_value" do
2732
2749
  before do
2733
2750
  @db = Sequel.mock
@@ -2752,6 +2769,23 @@ describe "Dataset#single_value" do
2752
2769
  end
2753
2770
  end
2754
2771
 
2772
+ describe "Dataset#single_value!" do
2773
+ before do
2774
+ @db = Sequel.mock
2775
+ end
2776
+
2777
+ it "should call each and return the first value of the first record" do
2778
+ @db.fetch = [{:a=>1, :b=>2}, {:a=>3, :b=>4}]
2779
+ @db[:test].single_value!.to_s.must_match /\A(1|2)\z/
2780
+ @db.sqls.must_equal ['SELECT * FROM test']
2781
+ end
2782
+
2783
+ it "should return nil if no records" do
2784
+ @db[:test].single_value!.must_equal nil
2785
+ @db.sqls.must_equal ['SELECT * FROM test']
2786
+ end
2787
+ end
2788
+
2755
2789
  describe "Dataset#get" do
2756
2790
  before do
2757
2791
  @d = Sequel.mock(:fetch=>proc{|s| {:name=>s}})[:test]
@@ -30,6 +30,19 @@ describe Sequel::Schema::Generator do
30
30
  @columns[3][:primary_key].must_equal nil
31
31
  end
32
32
 
33
+ it "should respect existing column order if primary_key :keep_order is used" do
34
+ generator = Sequel::Schema::Generator.new(Sequel.mock) do
35
+ string :title
36
+ primary_key :id, :keep_order=>true
37
+ end
38
+
39
+ columns = generator.columns
40
+ columns.last[:name].must_equal :id
41
+ columns.last[:primary_key].must_equal true
42
+ columns.first[:name].must_equal :title
43
+ columns.first[:primary_key].must_equal nil
44
+ end
45
+
33
46
  it "counts definitions correctly" do
34
47
  @columns.size.must_equal 6
35
48
  @indexes.size.must_equal 2
@@ -111,6 +111,18 @@ describe "DB#create_table" do
111
111
  primary_key :id, :type => :serial, :auto_increment => false
112
112
  end
113
113
  @db.sqls.must_equal ['CREATE TABLE cats (id serial PRIMARY KEY)']
114
+
115
+ @db.create_table(:cats) do
116
+ Integer :a
117
+ primary_key :id
118
+ end
119
+ @db.sqls.must_equal ['CREATE TABLE cats (id integer PRIMARY KEY AUTOINCREMENT, a integer)']
120
+
121
+ @db.create_table(:cats) do
122
+ Integer :a
123
+ primary_key :id, :keep_order=>true
124
+ end
125
+ @db.sqls.must_equal ['CREATE TABLE cats (a integer, id integer PRIMARY KEY AUTOINCREMENT)']
114
126
  end
115
127
 
116
128
  it "should allow naming primary key constraint with :primary_key_constraint_name option" do
@@ -1571,6 +1583,11 @@ describe "Schema Parser" do
1571
1583
  @db.schema(:x).last.last[:auto_increment].must_equal false
1572
1584
  end
1573
1585
 
1586
+ it "should set :auto_increment to true by default if set and not the first column" do
1587
+ meta_def(@db, :schema_parse_table){|*| [[:b, {}], [:a, {:primary_key=>true, :db_type=>'integer'}]]}
1588
+ @db.schema(:x).last.last[:auto_increment].must_equal true
1589
+ end
1590
+
1574
1591
  it "should convert various types of table name arguments" do
1575
1592
  meta_def(@db, :schema_parse_table) do |t, opts|
1576
1593
  [[t, opts]]
@@ -2,122 +2,84 @@ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
3
  begin
4
4
  require 'active_model'
5
- begin
6
- require 'minitest'
7
- if defined?(MiniTest::Unit)
8
- class << MiniTest::Unit
9
- def autorun; end
10
- end
11
- end
12
- if defined?(MiniTest::Test)
13
- test_class = MiniTest::Test
14
- end
15
- rescue
16
- require 'test/unit'
17
- test_class = Test::Unit::TestCase
18
- if Test::Unit.respond_to?(:run=)
19
- Test::Unit.run = false
20
- require 'test/unit/testresult'
21
- end
22
- end
23
5
  rescue LoadError => e
24
6
  skip_warn "active_model plugin: can't load active_model (#{e.class}: #{e})"
25
7
  else
26
8
  describe "ActiveModel plugin" do
27
- it "should be compliant to the ActiveModel spec" do
28
- tc = Class.new(test_class)
29
- tc.class_eval do
30
- define_method(:setup) do
31
- class ::AMLintTest < Sequel::Model
32
- set_primary_key :id
33
- columns :id, :id2
34
- def delete; end
35
- end
36
- module ::Blog
37
- class Post < Sequel::Model
38
- plugin :active_model
39
- end
40
- end
41
- @c = AMLintTest
42
- @c.plugin :active_model
43
- @m = @model = @c.new
44
- @o = @c.load({})
45
- super()
46
- end
47
- def teardown
48
- super
49
- Object.send(:remove_const, :AMLintTest)
50
- Object.send(:remove_const, :Blog)
9
+ before do
10
+ class ::AMLintTest < Sequel::Model
11
+ set_primary_key :id
12
+ columns :id, :id2
13
+ def delete; end
14
+ end
15
+ module ::Blog
16
+ class Post < Sequel::Model
17
+ plugin :active_model
51
18
  end
52
- include ActiveModel::Lint::Tests
19
+ end
20
+ @c = AMLintTest
21
+ @c.plugin :active_model
22
+ @m = @model = @c.new
23
+ @o = @c.load({})
24
+ end
25
+ after do
26
+ Object.send(:remove_const, :AMLintTest)
27
+ Object.send(:remove_const, :Blog)
28
+ end
29
+ include ActiveModel::Lint::Tests
53
30
 
54
- # Should return self, not a proxy object
55
- def test__to_model
56
- assert_equal @m.to_model.object_id, @m.object_id
57
- end
58
-
59
- def test__to_key
60
- assert_equal nil, @m.to_key
61
- @o.id = 1
62
- assert_equal [1], @o.to_key
63
- @o.id = nil
64
- assert_equal nil, @o.to_key
31
+ it ".to_model should return self, not a proxy object" do
32
+ @m.object_id.must_equal @m.to_model.object_id
33
+ end
34
+
35
+ it "#to_key should return a key array, or nil" do
36
+ @o.to_key.must_equal nil
37
+ @o.id = 1
38
+ @o.to_key.must_equal [1]
39
+ @o.id = nil
40
+ @o.to_key.must_equal nil
65
41
 
66
- @c.set_primary_key [:id2, :id]
67
- assert_equal nil, @o.to_key
68
- @o.id = 1
69
- @o.id2 = 2
70
- assert_equal [2, 1], @o.to_key
71
- @o.destroy
72
- assert_equal [2, 1], @o.to_key
73
- @o.id = nil
74
- assert_equal nil, @o.to_key
75
- end
76
-
77
- def test__to_param
78
- assert_equal nil, @m.to_param
79
- @o.id = 1
80
- assert_equal '1', @o.to_param
81
- @c.set_primary_key [:id2, :id]
82
- @o.id2 = 2
83
- assert_equal '2-1', @o.to_param
84
- @o.meta_def(:to_param_joiner){'|'}
85
- assert_equal '2|1', @o.to_param
86
- @o.destroy
87
- assert_equal nil, @o.to_param
88
- end
42
+ @c.set_primary_key [:id2, :id]
43
+ @o.to_key.must_equal nil
44
+ @o.id = 1
45
+ @o.id2 = 2
46
+ @o.to_key.must_equal [2, 1]
47
+ @o.destroy
48
+ @o.to_key.must_equal [2, 1]
49
+ @o.id = nil
50
+ @o.to_key.must_equal nil
51
+ end
52
+
53
+ it "#to_param should return a param string or nil" do
54
+ @o.to_param.must_equal nil
55
+ @o.id = 1
56
+ @o.to_param.must_equal '1'
57
+ @c.set_primary_key [:id2, :id]
58
+ @o.id2 = 2
59
+ @o.to_param.must_equal '2-1'
60
+ @o.meta_def(:to_param_joiner){'|'}
61
+ @o.to_param.must_equal '2|1'
62
+ @o.destroy
63
+ @o.to_param.must_equal nil
64
+ end
89
65
 
90
- def test__persisted?
91
- assert_equal false, @m.persisted?
92
- assert_equal true, @o.persisted?
93
- @m.destroy
94
- @o.destroy
95
- assert_equal false, @m.persisted?
96
- assert_equal false, @o.persisted?
97
- end
66
+ it "#persisted? should return true if the object exists and has not been destroyed" do
67
+ @m.persisted?.must_equal false
68
+ @o.persisted?.must_equal true
69
+ @m.destroy
70
+ @o.destroy
71
+ @m.persisted?.must_equal false
72
+ @o.persisted?.must_equal false
73
+ end
98
74
 
99
- # Should return self, not a proxy object
100
- def test__to_partial_path
101
- assert_equal 'am_lint_tests/am_lint_test', @m.to_partial_path
102
- assert_equal 'blog/posts/post', Blog::Post.new.to_partial_path
103
- end
104
-
105
- end
106
- if defined?(MiniTest::Test) || defined?(MiniTest::Unit)
107
- tc.instance_methods.map{|x| x.to_s}.reject{|n| n !~ /\Atest_/}.each do |m|
108
- i = tc.new(m)
109
- i.setup
110
- i.send(m)
111
- i.teardown
112
- end
113
- else
114
- res = ::Test::Unit::TestResult.new
115
- tc.suite.run(res){}
116
- if res.failure_count > 0
117
- puts res.instance_variable_get(:@failures)
118
- end
119
- res.failure_count.must_equal 0
120
- end
75
+ it "#persisted? should return false if the object is created and the transaction is rolled back" do
76
+ DB.transaction(:rollback=>:always){@m.save}
77
+ @m.persisted?.must_equal false
78
+ end
79
+
80
+ it "#to_partial_path should return a path string" do
81
+ @m.to_partial_path.must_equal 'am_lint_tests/am_lint_test'
82
+ Blog::Post.new.to_partial_path.must_equal 'blog/posts/post'
121
83
  end
122
- end
123
84
  end
85
+ end
@@ -0,0 +1,40 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe "Sequel::Plugins::BeforeAfterSave" do
4
+ before do
5
+ @db = Sequel.mock(:numrows=>1, :fetch=>{:id=>1, :name=>'b'})
6
+ @c = Class.new(Sequel::Model(@db[:test]))
7
+ @ds = @c.dataset
8
+ @c.columns :id, :name
9
+ @c.plugin :before_after_save
10
+ @c.plugin :instance_hooks
11
+ @o = @c.new
12
+ @db.sqls
13
+ end
14
+
15
+ it "should reset modified flag before calling after hooks" do
16
+ a = []
17
+ @o.after_create_hook{@o.modified?.must_equal false; a << 1}
18
+ @o.after_save_hook{@o.modified?.must_equal false; a << 2}
19
+
20
+ @o.modified!
21
+ @o.save
22
+ a.must_equal [1, 2]
23
+
24
+ @o.after_save_hook{@o.modified?.must_equal false; a << 2}
25
+ @o.after_update_hook{@o.modified?.must_equal false; a << 3}
26
+ a = []
27
+ @o.modified!
28
+ @o.save
29
+ a.must_equal [3, 2]
30
+ end
31
+
32
+ it "should refresh the instance before calling after hooks" do
33
+ a = []
34
+ @o.after_create_hook{@o.values.must_equal(:id=>1, :name=>'b'); a << 1}
35
+ @o.after_save_hook{@o.values.must_equal(:id=>1, :name=>'b'); a << 2}
36
+
37
+ @o.save
38
+ a.must_equal [1, 2]
39
+ end
40
+ end
@@ -0,0 +1,47 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe "boolean_subsets plugin" do
4
+ before do
5
+ @db = Sequel::Database.new
6
+ def @db.supports_schema_parsing?() true end
7
+ def @db.schema(*args)
8
+ [[:asdaf9898as, {}], [:active, {:type=>:boolean}]]
9
+ end
10
+
11
+ @c = Class.new(Sequel::Model(@db[:items]))
12
+ @p = proc do
13
+ @columns = [:asdaf9898as, :active]
14
+ def columns; @columns; end
15
+ end
16
+ @c.instance_eval(&@p)
17
+ end
18
+
19
+ it "should create subsets only for boolean attributes" do
20
+ @c.plugin(:boolean_subsets)
21
+ @c.active.sql.must_equal "SELECT * FROM items WHERE (active IS TRUE)"
22
+ @c.respond_to?(:asdaf9898as).must_equal false
23
+ end
24
+
25
+ it "should handle a block passed to the plugin" do
26
+ @c.plugin(:boolean_subsets){|c| ["where_#{c}", c]}
27
+ @c.where_active.sql.must_equal "SELECT * FROM items WHERE active"
28
+ @c.respond_to?(:active).must_equal false
29
+ end
30
+
31
+ it "should create boolean subsets when set_dataset is called" do
32
+ c = Class.new(Sequel::Model(@db))
33
+ c.instance_eval(&@p)
34
+ c.plugin(:boolean_subsets)
35
+ c.respond_to?(:active).must_equal false
36
+
37
+ c.set_dataset(@db[:items])
38
+ c.active.sql.must_equal "SELECT * FROM items WHERE (active IS TRUE)"
39
+ c.respond_to?(:asdaf9898as).must_equal false
40
+ end
41
+
42
+ it "should handle cases where getting the columns raises an error" do
43
+ @c.meta_def(:columns){raise Sequel::Error}
44
+ @c.plugin(:boolean_subsets)
45
+ @c.respond_to?(:active).must_equal false
46
+ end
47
+ end
@@ -65,6 +65,23 @@ describe "date_arithmetic extension" do
65
65
  end
66
66
  end
67
67
 
68
+ it "should use existing method" do
69
+ db = Sequel.mock
70
+ db.extend_datasets do
71
+ def date_add_sql_append(sql, da)
72
+ interval = ''
73
+ each_valid_interval_unit(da.interval, Sequel::SQL::DateAdd::DatasetMethods::DEF_DURATION_UNITS) do |value, sql_unit|
74
+ interval << "#{value} #{sql_unit} "
75
+ end
76
+ literal_append(sql, Sequel.function(:da, da.expr, interval))
77
+ end
78
+ end
79
+ db.extension :date_arithmetic
80
+ db.literal(Sequel.date_add(:a, @h0)).must_equal "da(a, '')"
81
+ db.literal(Sequel.date_add(:a, @h1)).must_equal "da(a, '1 days ')"
82
+ db.literal(Sequel.date_add(:a, @h2)).must_equal "da(a, '1 years 1 months 1 days 1 hours 1 minutes 1 seconds ')"
83
+ end
84
+
68
85
  it "should correctly literalize on Postgres" do
69
86
  db = dbf.call(:postgres)
70
87
  db.literal(Sequel.date_add(:a, @h0)).must_equal "CAST(a AS timestamp)"
@@ -43,6 +43,13 @@ describe "Sequel::Plugins::JsonSerializer" do
43
43
  Artist.from_json(Artist.load(:name=>Date.today).to_json).must_equal Artist.load(:name=>Date.today)
44
44
  end
45
45
 
46
+ it "should support setting json_serializer_opts on models" do
47
+ @artist.json_serializer_opts(:only=>:name)
48
+ Sequel.parse_json([@artist].to_json).must_equal [{'name'=>@artist.name}]
49
+ @artist.json_serializer_opts(:include=>{:albums=>{:only=>:name}})
50
+ Sequel.parse_json([@artist].to_json).must_equal [{'name'=>@artist.name, 'albums'=>[{'name'=>@album.name}]}]
51
+ end
52
+
46
53
  it "should handle the :only option" do
47
54
  Artist.from_json(@artist.to_json(:only=>:name)).must_equal Artist.load(:name=>@artist.name)
48
55
  Album.from_json(@album.to_json(:only=>[:id, :name])).must_equal Album.load(:id=>@album.id, :name=>@album.name)
@@ -264,4 +264,15 @@ describe "List plugin" do
264
264
  @o.prev(-1).must_equal @c.load(:id=>9, :position=>4)
265
265
  @db.sqls.must_equal ["SELECT * FROM items WHERE (position = 4) ORDER BY position LIMIT 1"]
266
266
  end
267
+
268
+ it "should work correctly with validation on position" do
269
+ @c.class_eval do
270
+ def validate
271
+ super
272
+ errors.add(:position, "not set") unless position
273
+ end
274
+ end
275
+ @c.create
276
+ @db.sqls.must_equal ["SELECT max(position) AS max FROM items LIMIT 1", "INSERT INTO items (position) VALUES (2)", "SELECT * FROM items WHERE (id = 10) ORDER BY position LIMIT 1"]
277
+ end
267
278
  end
@@ -108,6 +108,11 @@ describe "Sequel::Postgres::JSONOp" do
108
108
  @l[@jb.array_elements_text].must_equal "jsonb_array_elements_text(j)"
109
109
  end
110
110
 
111
+ it "should have #strip_nulls use the json_strip_nulls function" do
112
+ @l[@j.strip_nulls].must_equal "json_strip_nulls(j)"
113
+ @l[@jb.strip_nulls].must_equal "jsonb_strip_nulls(j)"
114
+ end
115
+
111
116
  it "should have #typeof use the json_typeof function" do
112
117
  @l[@j.typeof].must_equal "json_typeof(j)"
113
118
  @l[@jb.typeof].must_equal "jsonb_typeof(j)"
@@ -173,6 +178,47 @@ describe "Sequel::Postgres::JSONOp" do
173
178
  @l[@jb.contained_by([1, 2])].must_equal "(j <@ '[1,2]'::jsonb)"
174
179
  end
175
180
 
181
+ it "#concat should use the || operator" do
182
+ @l[@jb.concat(:h1)].must_equal "(j || h1)"
183
+ end
184
+
185
+ it "#concat should handle hashes" do
186
+ @l[@jb.concat('a'=>'b')].must_equal "(j || '{\"a\":\"b\"}'::jsonb)"
187
+ end
188
+
189
+ it "#concat should handle arrays" do
190
+ @l[@jb.concat([1, 2])].must_equal "(j || '[1,2]'::jsonb)"
191
+ end
192
+
193
+ it "#set should use the jsonb_set function" do
194
+ @l[@jb.set(:a, :h)].must_equal "jsonb_set(j, a, h, true)"
195
+ @l[@jb.set(:a, :h, false)].must_equal "jsonb_set(j, a, h, false)"
196
+ end
197
+
198
+ it "#set should handle hashes" do
199
+ @l[@jb.set(:a, 'a'=>'b')].must_equal "jsonb_set(j, a, '{\"a\":\"b\"}'::jsonb, true)"
200
+ end
201
+
202
+ it "#set should handle arrays" do
203
+ @l[@jb.set(%w'a b', [1, 2])].must_equal "jsonb_set(j, ARRAY['a','b'], '[1,2]'::jsonb, true)"
204
+ end
205
+
206
+ it "#pretty should use the jsonb_pretty function" do
207
+ @l[@jb.pretty].must_equal "jsonb_pretty(j)"
208
+ end
209
+
210
+ it "#- should use the - operator" do
211
+ @l[@jb - 1].must_equal "(j - 1)"
212
+ end
213
+
214
+ it "#delete_path should use the #- operator" do
215
+ @l[@jb.delete_path(:a)].must_equal "(j #- a)"
216
+ end
217
+
218
+ it "#delete_path should handle arrays" do
219
+ @l[@jb.delete_path(['a'])].must_equal "(j #- ARRAY['a'])"
220
+ end
221
+
176
222
  it "#has_key? and aliases should use the ? operator" do
177
223
  @l[@jb.has_key?('a')].must_equal "(j ? 'a')"
178
224
  @l[@jb.include?('a')].must_equal "(j ? 'a')"