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.
- checksums.yaml +4 -4
- data/CHANGELOG +62 -0
- data/README.rdoc +4 -4
- data/bin/sequel +8 -0
- data/doc/opening_databases.rdoc +5 -7
- data/doc/postgresql.rdoc +13 -0
- data/doc/release_notes/4.27.0.txt +78 -0
- data/doc/release_notes/4.28.0.txt +57 -0
- data/doc/release_notes/4.29.0.txt +41 -0
- data/doc/thread_safety.rdoc +1 -1
- data/doc/transactions.rdoc +4 -1
- data/doc/validations.rdoc +1 -1
- data/lib/sequel/adapters/jdbc/postgresql.rb +1 -1
- data/lib/sequel/adapters/oracle.rb +1 -1
- data/lib/sequel/adapters/postgres.rb +9 -3
- data/lib/sequel/adapters/shared/postgres.rb +2 -2
- data/lib/sequel/adapters/sqlanywhere.rb +3 -3
- data/lib/sequel/core.rb +8 -18
- data/lib/sequel/database/query.rb +7 -2
- data/lib/sequel/database/schema_generator.rb +17 -4
- data/lib/sequel/database/transactions.rb +3 -3
- data/lib/sequel/dataset/actions.rb +38 -9
- data/lib/sequel/dataset/sql.rb +8 -3
- data/lib/sequel/extensions/date_arithmetic.rb +3 -0
- data/lib/sequel/extensions/pg_json_ops.rb +58 -5
- data/lib/sequel/extensions/schema_dumper.rb +12 -1
- data/lib/sequel/model/base.rb +32 -16
- data/lib/sequel/plugins/active_model.rb +7 -0
- data/lib/sequel/plugins/before_after_save.rb +48 -0
- data/lib/sequel/plugins/boolean_subsets.rb +56 -0
- data/lib/sequel/plugins/csv_serializer.rb +1 -1
- data/lib/sequel/plugins/defaults_setter.rb +8 -4
- data/lib/sequel/plugins/json_serializer.rb +25 -4
- data/lib/sequel/plugins/list.rb +9 -9
- data/lib/sequel/plugins/subset_conditions.rb +36 -0
- data/lib/sequel/plugins/uuid.rb +72 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +11 -1
- data/spec/bin_spec.rb +4 -0
- data/spec/core/database_spec.rb +35 -0
- data/spec/core/dataset_spec.rb +34 -0
- data/spec/core/schema_generator_spec.rb +13 -0
- data/spec/core/schema_spec.rb +17 -0
- data/spec/extensions/active_model_spec.rb +70 -108
- data/spec/extensions/before_after_save_spec.rb +40 -0
- data/spec/extensions/boolean_subsets_spec.rb +47 -0
- data/spec/extensions/date_arithmetic_spec.rb +17 -0
- data/spec/extensions/json_serializer_spec.rb +7 -0
- data/spec/extensions/list_spec.rb +11 -0
- data/spec/extensions/pg_json_ops_spec.rb +46 -0
- data/spec/extensions/schema_dumper_spec.rb +18 -0
- data/spec/extensions/subset_conditions_spec.rb +38 -0
- data/spec/extensions/uuid_spec.rb +106 -0
- data/spec/integration/dataset_test.rb +14 -0
- data/spec/integration/prepared_statement_test.rb +3 -3
- data/spec/integration/schema_test.rb +7 -1
- data/spec/integration/transaction_test.rb +22 -0
- data/spec/model/class_dataset_methods_spec.rb +4 -0
- data/spec/model/model_spec.rb +1 -1
- data/spec/model/record_spec.rb +7 -1
- 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
|
-
#
|
|
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).
|
|
645
|
-
|
|
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.
|
|
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.
|
|
850
|
+
r.each{|_, v| return v}
|
|
822
851
|
end
|
|
823
852
|
end
|
|
824
853
|
|
data/lib/sequel/dataset/sql.rb
CHANGED
|
@@ -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 =
|
|
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
|
|
@@ -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
|
|
62
|
-
# j.
|
|
63
|
-
# j.
|
|
64
|
-
# j.
|
|
65
|
-
# j.
|
|
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)
|
data/lib/sequel/model/base.rb
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
module Sequel
|
|
2
2
|
module Plugins
|
|
3
|
-
#
|
|
4
|
-
#
|
|
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
|
|
9
|
-
# album
|
|
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
|
-
|
|
261
|
-
|
|
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)
|
data/lib/sequel/plugins/list.rb
CHANGED
|
@@ -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
|