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
@@ -12,7 +12,7 @@ module Sequel
12
12
  << [] all avg count columns columns! delete each
13
13
  empty? fetch_rows first first! get import insert interval last
14
14
  map max min multi_insert paged_each range select_hash select_hash_groups select_map select_order_map
15
- single_record single_value sum to_hash to_hash_groups truncate update
15
+ single_record single_record! single_value single_value! sum to_hash to_hash_groups truncate update
16
16
  METHS
17
17
 
18
18
  # Inserts the given argument into the database. Returns self so it
@@ -637,22 +637,51 @@ module Sequel
637
637
  _select_map(column, true, &block)
638
638
  end
639
639
 
640
- # Returns the first record in the dataset, or nil if the dataset
641
- # has no records. Users should probably use +first+ instead of
642
- # this method.
640
+ # Limits the dataset to one record, and returns the first record in the dataset,
641
+ # or nil if the dataset has no records. Users should probably use +first+ instead of
642
+ # this method. Example:
643
+ #
644
+ # DB[:test].single_record # SELECT * FROM test LIMIT 1
645
+ # # => {:column_name=>'value'}
643
646
  def single_record
644
- clone(:limit=>1).each{|r| return r}
645
- nil
647
+ clone(:limit=>1).single_record!
648
+ end
649
+
650
+ # Returns the first record in dataset, without limiting the dataset. Returns nil if
651
+ # the dataset has no records. Users should probably use +first+ instead of this method.
652
+ # This should only be used if you know the dataset is already limited to a single record.
653
+ # This method may be desirable to use for performance reasons, as it does not clone the
654
+ # receiver. Example:
655
+ #
656
+ # DB[:test].single_record! # SELECT * FROM test
657
+ # # => {:column_name=>'value'}
658
+ def single_record!
659
+ with_sql_first(select_sql)
646
660
  end
647
661
 
648
662
  # Returns the first value of the first record in the dataset.
649
663
  # Returns nil if dataset is empty. Users should generally use
650
- # +get+ instead of this method.
664
+ # +get+ instead of this method. Example:
665
+ #
666
+ # DB[:test].single_value # SELECT * FROM test LIMIT 1
667
+ # # => 'value'
651
668
  def single_value
652
669
  if r = ungraphed.naked.single_record
653
- r.values.first
670
+ r.each{|_, v| return v}
654
671
  end
655
672
  end
673
+
674
+ # Returns the first value of the first record in the dataset, without limiting the dataset.
675
+ # Returns nil if the dataset is empty. Users should generally use +get+ instead of this
676
+ # method. Should not be used on graphed datasets or datasets that have row_procs that
677
+ # don't return hashes. This method may be desirable to use for performance reasons, as
678
+ # it does not clone the receiver.
679
+ #
680
+ # DB[:test].single_value! # SELECT * FROM test
681
+ # # => 'value'
682
+ def single_value!
683
+ with_sql_single_value(select_sql)
684
+ end
656
685
 
657
686
  # Returns the sum for the given column/expression.
658
687
  # Uses a virtual row block if no column is given.
@@ -818,7 +847,7 @@ module Sequel
818
847
  # only a single value. See with_sql_each.
819
848
  def with_sql_single_value(sql)
820
849
  if r = with_sql_first(sql)
821
- r.values.first
850
+ r.each{|_, v| return v}
822
851
  end
823
852
  end
824
853
 
@@ -46,12 +46,11 @@ module Sequel
46
46
  end
47
47
 
48
48
  if values.is_a?(Array) && values.empty? && !insert_supports_empty_values?
49
- columns = [columns().last]
50
- values = [DEFAULT]
49
+ columns, values = insert_empty_columns_values
51
50
  end
52
51
  clone(:columns=>columns, :values=>values).send(:_insert_sql)
53
52
  end
54
-
53
+
55
54
  # Append a literal representation of a value to the given SQL string.
56
55
  #
57
56
  # If an unsupported object is given, an +Error+ is raised.
@@ -1116,6 +1115,12 @@ module Sequel
1116
1115
  end
1117
1116
  end
1118
1117
 
1118
+ # The columns and values to use for an empty insert if the database doesn't support
1119
+ # INSERT with DEFAULT VALUES.
1120
+ def insert_empty_columns_values
1121
+ [[columns.last], [DEFAULT]]
1122
+ end
1123
+
1119
1124
  def insert_insert_sql(sql)
1120
1125
  sql << INSERT
1121
1126
  end
@@ -66,6 +66,9 @@ module Sequel
66
66
 
67
67
  # Append the SQL fragment for the DateAdd expression to the SQL query.
68
68
  def date_add_sql_append(sql, da)
69
+ if defined?(super)
70
+ return super
71
+ end
69
72
  h = da.interval
70
73
  expr = da.expr
71
74
  cast = case db_type = db.database_type
@@ -50,6 +50,7 @@
50
50
  # j.each_text # json_each_text(json_column)
51
51
  # j.keys # json_object_keys(json_column)
52
52
  # j.typeof # json_typeof(json_column)
53
+ # j.strip_nulls # json_strip_nulls(json_column)
53
54
  #
54
55
  # j.populate(:a) # json_populate_record(:a, json_column)
55
56
  # j.populate_set(:a) # json_populate_recordset(:a, json_column)
@@ -58,11 +59,16 @@
58
59
  #
59
60
  # There are additional methods are are only supported on JSONBOp instances:
60
61
  #
61
- # j.contain_all(:a) # (jsonb_column ?& a)
62
- # j.contain_any(:a) # (jsonb_column ?| a)
63
- # j.contains(:h) # (jsonb_column @> h)
64
- # j.contained_by(:h) # (jsonb_column <@ h)
65
- # j.has_key?('a') # (jsonb_column ? 'a')
62
+ # j - 1 # (jsonb_column - 1)
63
+ # j.concat(:h) # (jsonb_column || h)
64
+ # j.contain_all(:a) # (jsonb_column ?& a)
65
+ # j.contain_any(:a) # (jsonb_column ?| a)
66
+ # j.contains(:h) # (jsonb_column @> h)
67
+ # j.contained_by(:h) # (jsonb_column <@ h)
68
+ # j.delete_path(%w'0 a') # (jsonb_column #- ARRAY['0','a'])
69
+ # j.has_key?('a') # (jsonb_column ? 'a')
70
+ # j.pretty # jsonb_pretty(jsonb_column)
71
+ # j.set(%w'0 a', :h) # jsonb_set(jsonb_column, ARRAY['0','a'], h, true)
66
72
  #
67
73
  # If you are also using the pg_json extension, you should load it before
68
74
  # loading this extension. Doing so will allow you to use the #op method on
@@ -192,6 +198,13 @@ module Sequel
192
198
  SQL::Function.new(function_name(:populate_recordset), arg, self)
193
199
  end
194
200
 
201
+ # Returns a json value stripped of all internal null values.
202
+ #
203
+ # json_op.strip_nulls # json_strip_nulls(json)
204
+ def strip_nulls
205
+ self.class.new(function(:strip_nulls))
206
+ end
207
+
195
208
  # Builds arbitrary record from json object. You need to define the
196
209
  # structure of the record using #as on the resulting object:
197
210
  #
@@ -266,12 +279,30 @@ module Sequel
266
279
  #
267
280
  # jsonb_op = Sequel.pg_jsonb(:jsonb)
268
281
  class JSONBOp < JSONBaseOp
282
+ CONCAT = ["(".freeze, " || ".freeze, ")".freeze].freeze
269
283
  CONTAIN_ALL = ["(".freeze, " ?& ".freeze, ")".freeze].freeze
270
284
  CONTAIN_ANY = ["(".freeze, " ?| ".freeze, ")".freeze].freeze
271
285
  CONTAINS = ["(".freeze, " @> ".freeze, ")".freeze].freeze
272
286
  CONTAINED_BY = ["(".freeze, " <@ ".freeze, ")".freeze].freeze
287
+ DELETE_PATH = ["(".freeze, " #- ".freeze, ")".freeze].freeze
273
288
  HAS_KEY = ["(".freeze, " ? ".freeze, ")".freeze].freeze
274
289
 
290
+ # jsonb expression for deletion of the given argument from the
291
+ # current jsonb.
292
+ #
293
+ # jsonb_op - "a" # (jsonb - 'a')
294
+ def -(other)
295
+ self.class.new(super)
296
+ end
297
+
298
+ # jsonb expression for concatenation of the given jsonb into
299
+ # the current jsonb.
300
+ #
301
+ # jsonb_op.concat(:h) # (jsonb || h)
302
+ def concat(other)
303
+ json_op(CONCAT, wrap_input_jsonb(other))
304
+ end
305
+
275
306
  # Check if the receiver contains all of the keys in the given array:
276
307
  #
277
308
  # jsonb_op.contain_all(:a) # (jsonb ?& a)
@@ -300,6 +331,13 @@ module Sequel
300
331
  bool_op(CONTAINED_BY, wrap_input_jsonb(other))
301
332
  end
302
333
 
334
+ # Check if the other jsonb contains all entries in the receiver:
335
+ #
336
+ # jsonb_op.delete_path(:h) # (jsonb #- h)
337
+ def delete_path(other)
338
+ json_op(DELETE_PATH, wrap_input_array(other))
339
+ end
340
+
303
341
  # Check if the receiver contains the given key:
304
342
  #
305
343
  # jsonb_op.has_key?('a') # (jsonb ? 'a')
@@ -313,6 +351,21 @@ module Sequel
313
351
  self
314
352
  end
315
353
 
354
+ # Returns a json value for the object at the given path.
355
+ #
356
+ # jsonb_op.pretty # jsonb_pretty(jsonb)
357
+ def pretty
358
+ Sequel::SQL::StringExpression.new(:NOOP, function(:pretty))
359
+ end
360
+
361
+ # Returns a json value for the object at the given path.
362
+ #
363
+ # jsonb_op.set(['a', 'b'], h) # jsonb_set(jsonb, ARRAY['a', 'b'], h, true)
364
+ # jsonb_op.set(['a', 'b'], h, false) # jsonb_set(jsonb, ARRAY['a', 'b'], h, false)
365
+ def set(path, other, create_missing=true)
366
+ self.class.new(function(:set, wrap_input_array(path), wrap_input_jsonb(other), create_missing))
367
+ end
368
+
316
369
  private
317
370
 
318
371
  # Return a placeholder literal with the given str and args, wrapped
@@ -166,6 +166,14 @@ END_MIG
166
166
  type_hash = options[:same_db] ? {:type=>schema[:db_type]} : column_schema_to_ruby_type(schema)
167
167
  [:table, :key, :on_delete, :on_update, :deferrable].each{|f| type_hash[f] = schema[f] if schema[f]}
168
168
  if type_hash == {:type=>Integer} || type_hash == {:type=>"integer"}
169
+ type_hash.delete(:type)
170
+ end
171
+
172
+ unless gen.columns.empty?
173
+ type_hash[:keep_order] = true
174
+ end
175
+
176
+ if type_hash.empty?
169
177
  gen.primary_key(name)
170
178
  else
171
179
  gen.primary_key(name, type_hash)
@@ -378,7 +386,7 @@ END_MIG
378
386
  x.delete(:on_delete) if x[:on_delete] == :no_action
379
387
  x.delete(:on_update) if x[:on_update] == :no_action
380
388
  end
381
- if pkn = primary_key_name
389
+ if (pkn = primary_key_name) && !@primary_key[:keep_order]
382
390
  cols.delete_if{|x| x[:name] == pkn}
383
391
  pk = @primary_key.dup
384
392
  pkname = pk.delete(:name)
@@ -391,6 +399,9 @@ END_MIG
391
399
  strings << if table = c.delete(:table)
392
400
  c.delete(:type) if c[:type] == Integer || c[:type] == 'integer'
393
401
  "foreign_key #{name.inspect}, #{table.inspect}#{opts_inspect(c)}"
402
+ elsif pkn == name
403
+ @db.serial_primary_key_options.each{|k,v| c.delete(k) if v == c[k]}
404
+ "primary_key #{name.inspect}#{opts_inspect(c)}"
394
405
  else
395
406
  type = c.delete(:type)
396
407
  opts = opts_inspect(c)
@@ -1743,6 +1743,32 @@ module Sequel
1743
1743
 
1744
1744
  private
1745
1745
 
1746
+ # Run code directly after the INSERT query, before after_create.
1747
+ # This is only a temporary API, it should not be overridden by external code.
1748
+ def _after_create(pk)
1749
+ @this = nil
1750
+ @new = false
1751
+ @was_new = true
1752
+ end
1753
+
1754
+ # Run code after around_save returns, before calling after_commit.
1755
+ # This is only a temporary API, it should not be overridden by external code.
1756
+ def _after_save(pk)
1757
+ if @was_new
1758
+ @was_new = nil
1759
+ pk ? _save_refresh : changed_columns.clear
1760
+ else
1761
+ @columns_updated = nil
1762
+ end
1763
+ @modified = false
1764
+ end
1765
+
1766
+ # Run code directly after the UPDATE query, before after_update.
1767
+ # This is only a temporary API, it should not be overridden by external code.
1768
+ def _after_update
1769
+ @this = nil
1770
+ end
1771
+
1746
1772
  # Run code before any validation is done, but also run it before saving
1747
1773
  # even if validation is skipped. This is a private hook. It exists so that
1748
1774
  # plugins can set values automatically before validation (as the values
@@ -1841,7 +1867,7 @@ module Sequel
1841
1867
  # Refresh using a particular dataset, used inside save to make sure the same server
1842
1868
  # is used for reading newly inserted values from the database
1843
1869
  def _refresh(dataset)
1844
- _refresh_set_values(_refresh_get(dataset) || raise(Error, "Record not found"))
1870
+ _refresh_set_values(_refresh_get(dataset) || raise(NoExistingObject, "Record not found"))
1845
1871
  changed_columns.clear
1846
1872
  end
1847
1873
 
@@ -1860,7 +1886,6 @@ module Sequel
1860
1886
  def _save(opts)
1861
1887
  sh = {:server=>this_server}
1862
1888
  db.after_rollback(sh){after_rollback} if uacr = use_after_commit_rollback
1863
- was_new = false
1864
1889
  pk = nil
1865
1890
  called_save = false
1866
1891
  called_cu = false
@@ -1868,14 +1893,11 @@ module Sequel
1868
1893
  called_save = true
1869
1894
  raise_hook_failure(:before_save) if before_save == false
1870
1895
  if new?
1871
- was_new = true
1872
1896
  around_create do
1873
1897
  called_cu = true
1874
1898
  raise_hook_failure(:before_create) if before_create == false
1875
1899
  pk = _insert
1876
- @this = nil
1877
- @new = false
1878
- @was_new = true
1900
+ _after_create(pk)
1879
1901
  after_create
1880
1902
  true
1881
1903
  end
@@ -1898,7 +1920,7 @@ module Sequel
1898
1920
  changed_columns.reject!{|c| columns.include?(c)}
1899
1921
  end
1900
1922
  _update_columns(@columns_updated)
1901
- @this = nil
1923
+ _after_update
1902
1924
  after_update
1903
1925
  true
1904
1926
  end
@@ -1908,22 +1930,16 @@ module Sequel
1908
1930
  true
1909
1931
  end
1910
1932
  raise_hook_failure(:around_save) unless called_save
1911
- if was_new
1912
- @was_new = nil
1913
- pk ? _save_refresh : changed_columns.clear
1914
- else
1915
- @columns_updated = nil
1916
- end
1917
- @modified = false
1933
+ _after_save(pk)
1918
1934
  db.after_commit(sh){after_commit} if uacr
1919
1935
  self
1920
1936
  end
1921
-
1937
+
1922
1938
  # Refresh the object after saving it, used to get
1923
1939
  # default values of all columns. Separated from _save so it
1924
1940
  # can be overridden to avoid the refresh.
1925
1941
  def _save_refresh
1926
- _save_set_values(_refresh_get(this.server?(:default)) || raise(Error, "Record not found"))
1942
+ _save_set_values(_refresh_get(this.server?(:default)) || raise(NoExistingObject, "Record not found"))
1927
1943
  changed_columns.clear
1928
1944
  end
1929
1945
 
@@ -44,6 +44,13 @@ module Sequel
44
44
  @destroyed = true
45
45
  end
46
46
 
47
+ # Mark current instance as destroyed if the transaction in which this
48
+ # instance is created is rolled back.
49
+ def before_create
50
+ db.after_rollback{@destroyed = true}
51
+ super
52
+ end
53
+
47
54
  # Return ::ActiveModel::Name instance for the class.
48
55
  def model_name
49
56
  model.model_name
@@ -0,0 +1,48 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The before_after_save plugin reorders some internal
4
+ # Sequel operations so they happen before after_create,
5
+ # after_update, and after_save are called, instead of
6
+ # after. These operations are:
7
+ #
8
+ # * Resetting the explicit modified flag
9
+ # * Refreshing the model or clearing changed columns after creation
10
+ #
11
+ # This behavior will become the default in Sequel 5.
12
+ #
13
+ # Usage:
14
+ #
15
+ # # Make all model subclasses perform the operations before after_save
16
+ # Sequel::Model.plugin :before_after_save
17
+ #
18
+ # # Make the Album class perform the operations before after_save
19
+ # Album.plugin :before_after_save
20
+ module BeforeAfterSave
21
+ module InstanceMethods
22
+ private
23
+
24
+ # Refresh and reset modified flag right after INSERT query.
25
+ def _after_create(pk)
26
+ super
27
+ @modified = false
28
+ pk ? _save_refresh : changed_columns.clear
29
+ end
30
+
31
+ # Don't refresh or reset modified flag, as it was already done.
32
+ def _after_save(pk)
33
+ if @was_new
34
+ @was_new = nil
35
+ else
36
+ @columns_updated = nil
37
+ end
38
+ end
39
+
40
+ # Refresh and reset modified flag right after UPDATE query.
41
+ def _after_update
42
+ super
43
+ @modified = false
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,56 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The boolean_subsets plugin allows for the automatic creation of subsets for
4
+ # for boolean columns, which can DRY up model classes that define such subsets
5
+ # manually. By default, subsets are created for all columns of type :boolean,
6
+ # with the subset name being the same as column name, and the conditions being
7
+ # <tt>column IS TRUE</tt> (assuming the database supports that syntax).
8
+ #
9
+ # You can provide a block to the plugin, which will be called with column name
10
+ # symbol, and should return an array of arguments to pass to +subset+.
11
+ # Using this, you can change the method name and arguments for each column.
12
+ # This block is executed in the context of the model class.
13
+ #
14
+ # Usage:
15
+ #
16
+ # # Add boolean subset methods for all columns of type :boolean
17
+ # # in all model subclasses (called before loading subclasses)
18
+ # Sequel::Model.plugin :boolean_subsets
19
+ #
20
+ # # Add subsets for all boolean columns in the Album class
21
+ # Album.plugin(:boolean_subsets)
22
+ #
23
+ # # Remove is_ from the front of the column name when creating the subset
24
+ # # method name, and use (column = 'Y') as the filter conditions
25
+ # Sequel::Model.plugin :boolean_subsets do |column|
26
+ # [column.to_s.sub(/\Ais_/, ''), {column=>'Y'}]
27
+ # end
28
+ module BooleanSubsets
29
+ # Add the boolean_attribute? class method to the model, and create
30
+ # attribute? boolean reader methods for the class's columns if the class has a dataset.
31
+ def self.configure(model, &block)
32
+ model.instance_eval do
33
+ (class << self; self; end).send(:define_method, :boolean_subset_args, &block) if block
34
+ send(:create_boolean_subsets) if @dataset
35
+ end
36
+ end
37
+
38
+ module ClassMethods
39
+ Plugins.after_set_dataset(self, :create_boolean_subsets)
40
+
41
+ private
42
+
43
+ # The arguments to use when automatically defining a boolean subset for the given column.
44
+ def boolean_subset_args(c)
45
+ [c, {c=>true}]
46
+ end
47
+
48
+ # Add subset methods for all of the boolean columns in this model.
49
+ def create_boolean_subsets
50
+ cs = columns rescue return
51
+ cs.each{|c| subset(*boolean_subset_args(c)) if db_schema[c][:type] == :boolean}
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -163,7 +163,7 @@ module Sequel
163
163
 
164
164
  CSV.generate(opts) do |csv|
165
165
  items.each do |object|
166
- csv << opts[:headers].map{|header| object[header]}
166
+ csv << opts[:headers].map{|header| object.send(header) }
167
167
  end
168
168
  end
169
169
  end
@@ -1,12 +1,16 @@
1
1
  module Sequel
2
2
  module Plugins
3
- # DefaultsSetter is a simple plugin that sets non-nil/NULL default values upon
4
- # initialize:
3
+ # The defaults_setter plugin makes the column getter methods return the default
4
+ # values for new objects, if the values have not already been set. Example:
5
5
  #
6
6
  # # column a default NULL
7
7
  # # column b default 2
8
- # album = Album.new.values # {:b => 2}
9
- # album = Album.new(:a=>1, :b=>3).values # {:a => 1, :b => 3}
8
+ # album = Album.new
9
+ # album.a # => nil
10
+ # album.b # => 2
11
+ # album = Album.new(:a=>1, :b=>3)
12
+ # album.a # => 1
13
+ # album.b # => 3
10
14
  #
11
15
  # Usage:
12
16
  #
@@ -36,6 +36,12 @@ module Sequel
36
36
  # album.to_json(:root => true)
37
37
  # # => '{"album":{"id":1,"name":"RF","artist_id":2}}'
38
38
  #
39
+ # You can specify JSON serialization options to use later:
40
+ #
41
+ # album.json_serializer_opts(:root => true)
42
+ # [album].to_json
43
+ # # => '[{"album":{"id":1,"name":"RF","artist_id":2}}]'
44
+ #
39
45
  # Additionally, +to_json+ also exists as a class and dataset method, both
40
46
  # of which return all objects in the dataset:
41
47
  #
@@ -239,6 +245,20 @@ module Sequel
239
245
  self
240
246
  end
241
247
 
248
+ # Set the json serialization options that will be used by default
249
+ # in future calls to +to_json+. This is designed for cases where
250
+ # the model object will be used inside another data structure
251
+ # which to_json is called on, and as such will not allow passing
252
+ # of arguments to +to_json+.
253
+ #
254
+ # Example:
255
+ #
256
+ # obj.json_serializer_opts(:only=>:name)
257
+ # [obj].to_json # => '[{"name":"..."}]'
258
+ def json_serializer_opts(opts=OPTS)
259
+ @json_serializer_opts = Hash[@json_serializer_opts||OPTS].merge!(opts)
260
+ end
261
+
242
262
  # Return a string in JSON format. Accepts the following
243
263
  # options:
244
264
  #
@@ -257,12 +277,13 @@ module Sequel
257
277
  # string is given, use the string as the key, otherwise
258
278
  # use an underscored version of the model's name.
259
279
  def to_json(*a)
260
- if opts = a.first.is_a?(Hash)
261
- opts = model.json_serializer_opts.merge(a.first)
280
+ opts = model.json_serializer_opts
281
+ opts = Hash[opts].merge!(@json_serializer_opts) if @json_serializer_opts
282
+ if (arg_opts = a.first).is_a?(Hash)
283
+ opts = Hash[opts].merge!(arg_opts)
262
284
  a = []
263
- else
264
- opts = model.json_serializer_opts
265
285
  end
286
+
266
287
  vals = values
267
288
  cols = if only = opts[:only]
268
289
  Array(only)
@@ -87,15 +87,6 @@ module Sequel
87
87
  list_dataset.first(position_field => p)
88
88
  end
89
89
 
90
- # Set the value of the position_field to the maximum value plus 1 unless the
91
- # position field already has a value.
92
- def before_create
93
- unless get_column_value(position_field)
94
- set_column_value("#{position_field}=", list_dataset.max(position_field).to_i+1)
95
- end
96
- super
97
- end
98
-
99
90
  # When destroying an instance, move all entries after the instance down
100
91
  # one position, so that there aren't any gaps
101
92
  def after_destroy
@@ -179,6 +170,15 @@ module Sequel
179
170
 
180
171
  private
181
172
 
173
+ # Set the value of the position_field to the maximum value plus 1 unless the
174
+ # position field already has a value.
175
+ def _before_validation
176
+ unless get_column_value(position_field)
177
+ set_column_value("#{position_field}=", list_dataset.max(position_field).to_i+1)
178
+ end
179
+ super
180
+ end
181
+
182
182
  # The model's position field, an instance method for ease of use.
183
183
  def position_field
184
184
  model.position_field
@@ -0,0 +1,36 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The subset_conditions plugin creates an additional *_conditions method
4
+ # for every subset created, which returns the filter conditions the subset
5
+ # uses. This can be useful if you want to use the conditions for a separate
6
+ # filter or combine them with OR.
7
+ #
8
+ # Usage:
9
+ #
10
+ # # Add subset_conditions in the Album class
11
+ # Album.plugin :subset_conditions
12
+ #
13
+ # # This will now create a published_conditions method
14
+ # Album.subset :published, :published => true
15
+ #
16
+ # Album.where(Album.published_conditions).sql
17
+ # # SELECT * FROM albums WHERE (published IS TRUE)
18
+ #
19
+ # Album.exclude(Album.published_conditions).sql
20
+ # # SELECT * FROM albums WHERE (published IS NOT TRUE)
21
+ #
22
+ # Album.where(Sequel.|(Album.published_conditions, :ready=>true)).sql
23
+ # # SELECT * FROM albums WHERE ((published IS TRUE) OR (ready IS TRUE))
24
+ module SubsetConditions
25
+ module ClassMethods
26
+ # Also create a method that returns the conditions the filter uses.
27
+ def subset(name, *args, &block)
28
+ super
29
+ cond = args
30
+ cond = cond.first if cond.size == 1
31
+ def_dataset_method(:"#{name}_conditions"){filter_expr(cond, &block)}
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end