sequel 5.33.0 → 5.58.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +318 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +40 -9
- data/doc/association_basics.rdoc +77 -13
- data/doc/cheat_sheet.rdoc +13 -5
- data/doc/code_order.rdoc +0 -12
- data/doc/dataset_filtering.rdoc +2 -2
- data/doc/fork_safety.rdoc +84 -0
- data/doc/migration.rdoc +12 -6
- data/doc/model_plugins.rdoc +1 -1
- data/doc/opening_databases.rdoc +15 -3
- data/doc/postgresql.rdoc +9 -1
- data/doc/querying.rdoc +7 -5
- data/doc/release_notes/5.34.0.txt +40 -0
- data/doc/release_notes/5.35.0.txt +56 -0
- data/doc/release_notes/5.36.0.txt +60 -0
- data/doc/release_notes/5.37.0.txt +30 -0
- data/doc/release_notes/5.38.0.txt +28 -0
- data/doc/release_notes/5.39.0.txt +19 -0
- data/doc/release_notes/5.40.0.txt +40 -0
- data/doc/release_notes/5.41.0.txt +25 -0
- data/doc/release_notes/5.42.0.txt +136 -0
- data/doc/release_notes/5.43.0.txt +98 -0
- data/doc/release_notes/5.44.0.txt +32 -0
- data/doc/release_notes/5.45.0.txt +34 -0
- data/doc/release_notes/5.46.0.txt +87 -0
- data/doc/release_notes/5.47.0.txt +59 -0
- data/doc/release_notes/5.48.0.txt +14 -0
- data/doc/release_notes/5.49.0.txt +59 -0
- data/doc/release_notes/5.50.0.txt +78 -0
- data/doc/release_notes/5.51.0.txt +47 -0
- data/doc/release_notes/5.52.0.txt +87 -0
- data/doc/release_notes/5.53.0.txt +23 -0
- data/doc/release_notes/5.54.0.txt +27 -0
- data/doc/release_notes/5.55.0.txt +21 -0
- data/doc/release_notes/5.56.0.txt +51 -0
- data/doc/release_notes/5.57.0.txt +23 -0
- data/doc/release_notes/5.58.0.txt +31 -0
- data/doc/sql.rdoc +14 -2
- data/doc/testing.rdoc +10 -1
- data/doc/transactions.rdoc +0 -8
- data/doc/validations.rdoc +1 -1
- data/doc/virtual_rows.rdoc +1 -1
- data/lib/sequel/adapters/ado/access.rb +1 -1
- data/lib/sequel/adapters/ado.rb +17 -17
- data/lib/sequel/adapters/amalgalite.rb +3 -5
- data/lib/sequel/adapters/ibmdb.rb +2 -2
- data/lib/sequel/adapters/jdbc/derby.rb +8 -0
- data/lib/sequel/adapters/jdbc/h2.rb +60 -10
- data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +4 -4
- data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
- data/lib/sequel/adapters/jdbc.rb +29 -19
- data/lib/sequel/adapters/mysql.rb +80 -67
- data/lib/sequel/adapters/mysql2.rb +54 -49
- data/lib/sequel/adapters/odbc.rb +8 -6
- data/lib/sequel/adapters/oracle.rb +5 -4
- data/lib/sequel/adapters/postgres.rb +27 -29
- data/lib/sequel/adapters/shared/access.rb +2 -0
- data/lib/sequel/adapters/shared/db2.rb +30 -0
- data/lib/sequel/adapters/shared/mssql.rb +84 -7
- data/lib/sequel/adapters/shared/mysql.rb +33 -2
- data/lib/sequel/adapters/shared/oracle.rb +82 -7
- data/lib/sequel/adapters/shared/postgres.rb +158 -20
- data/lib/sequel/adapters/shared/sqlanywhere.rb +3 -0
- data/lib/sequel/adapters/shared/sqlite.rb +102 -10
- data/lib/sequel/adapters/sqlanywhere.rb +1 -1
- data/lib/sequel/adapters/sqlite.rb +60 -18
- data/lib/sequel/adapters/tinytds.rb +2 -1
- data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +2 -1
- data/lib/sequel/ast_transformer.rb +6 -0
- data/lib/sequel/connection_pool/sharded_single.rb +9 -8
- data/lib/sequel/connection_pool/sharded_threaded.rb +10 -10
- data/lib/sequel/connection_pool/single.rb +7 -9
- data/lib/sequel/connection_pool/threaded.rb +1 -1
- data/lib/sequel/core.rb +33 -24
- data/lib/sequel/database/connecting.rb +3 -4
- data/lib/sequel/database/misc.rb +37 -12
- data/lib/sequel/database/query.rb +3 -1
- data/lib/sequel/database/schema_generator.rb +50 -53
- data/lib/sequel/database/schema_methods.rb +45 -23
- data/lib/sequel/database/transactions.rb +9 -6
- data/lib/sequel/dataset/actions.rb +61 -8
- data/lib/sequel/dataset/features.rb +15 -0
- data/lib/sequel/dataset/placeholder_literalizer.rb +3 -7
- data/lib/sequel/dataset/prepared_statements.rb +2 -0
- data/lib/sequel/dataset/query.rb +114 -11
- data/lib/sequel/dataset/sql.rb +172 -46
- data/lib/sequel/deprecated.rb +3 -1
- data/lib/sequel/exceptions.rb +2 -0
- data/lib/sequel/extensions/_pretty_table.rb +1 -2
- data/lib/sequel/extensions/any_not_empty.rb +1 -1
- data/lib/sequel/extensions/async_thread_pool.rb +438 -0
- data/lib/sequel/extensions/blank.rb +8 -0
- data/lib/sequel/extensions/columns_introspection.rb +1 -2
- data/lib/sequel/extensions/core_refinements.rb +38 -11
- data/lib/sequel/extensions/date_arithmetic.rb +36 -24
- data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
- data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
- data/lib/sequel/extensions/duplicate_columns_handler.rb +3 -1
- data/lib/sequel/extensions/eval_inspect.rb +2 -0
- data/lib/sequel/extensions/inflector.rb +9 -1
- data/lib/sequel/extensions/is_distinct_from.rb +139 -0
- data/lib/sequel/extensions/migration.rb +13 -2
- data/lib/sequel/extensions/named_timezones.rb +5 -1
- data/lib/sequel/extensions/pagination.rb +1 -1
- data/lib/sequel/extensions/pg_array.rb +1 -0
- data/lib/sequel/extensions/pg_array_ops.rb +6 -2
- data/lib/sequel/extensions/pg_enum.rb +3 -1
- data/lib/sequel/extensions/pg_extended_date_support.rb +2 -2
- data/lib/sequel/extensions/pg_hstore.rb +1 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +55 -3
- data/lib/sequel/extensions/pg_inet.rb +2 -0
- data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
- data/lib/sequel/extensions/pg_interval.rb +35 -8
- data/lib/sequel/extensions/pg_json.rb +3 -5
- data/lib/sequel/extensions/pg_json_ops.rb +119 -4
- data/lib/sequel/extensions/pg_loose_count.rb +3 -1
- data/lib/sequel/extensions/pg_multirange.rb +372 -0
- data/lib/sequel/extensions/pg_range.rb +7 -19
- data/lib/sequel/extensions/pg_range_ops.rb +39 -9
- data/lib/sequel/extensions/pg_row.rb +1 -1
- data/lib/sequel/extensions/pg_row_ops.rb +25 -1
- data/lib/sequel/extensions/query.rb +3 -0
- data/lib/sequel/extensions/run_transaction_hooks.rb +1 -1
- data/lib/sequel/extensions/s.rb +4 -1
- data/lib/sequel/extensions/schema_dumper.rb +16 -5
- data/lib/sequel/extensions/server_block.rb +8 -12
- data/lib/sequel/extensions/sql_comments.rb +110 -3
- data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
- data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
- data/lib/sequel/extensions/string_agg.rb +1 -1
- data/lib/sequel/extensions/string_date_time.rb +19 -23
- data/lib/sequel/extensions/symbol_aref_refinement.rb +2 -0
- data/lib/sequel/extensions/symbol_as_refinement.rb +2 -0
- data/lib/sequel/extensions/to_dot.rb +9 -3
- data/lib/sequel/model/associations.rb +342 -114
- data/lib/sequel/model/base.rb +45 -24
- data/lib/sequel/model/errors.rb +10 -1
- data/lib/sequel/model/inflections.rb +1 -1
- data/lib/sequel/model/plugins.rb +8 -3
- data/lib/sequel/model.rb +3 -1
- data/lib/sequel/plugins/association_pks.rb +60 -18
- data/lib/sequel/plugins/association_proxies.rb +3 -0
- data/lib/sequel/plugins/async_thread_pool.rb +39 -0
- data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
- data/lib/sequel/plugins/auto_validations.rb +39 -5
- data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
- data/lib/sequel/plugins/blacklist_security.rb +1 -2
- data/lib/sequel/plugins/class_table_inheritance.rb +3 -8
- data/lib/sequel/plugins/column_encryption.rb +728 -0
- data/lib/sequel/plugins/composition.rb +8 -2
- data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
- data/lib/sequel/plugins/constraint_validations.rb +2 -1
- data/lib/sequel/plugins/csv_serializer.rb +2 -0
- data/lib/sequel/plugins/dataset_associations.rb +4 -1
- data/lib/sequel/plugins/dirty.rb +44 -0
- data/lib/sequel/plugins/enum.rb +124 -0
- data/lib/sequel/plugins/forbid_lazy_load.rb +2 -0
- data/lib/sequel/plugins/insert_conflict.rb +4 -0
- data/lib/sequel/plugins/instance_specific_default.rb +113 -0
- data/lib/sequel/plugins/json_serializer.rb +39 -24
- data/lib/sequel/plugins/lazy_attributes.rb +4 -1
- data/lib/sequel/plugins/many_through_many.rb +108 -9
- data/lib/sequel/plugins/nested_attributes.rb +8 -3
- data/lib/sequel/plugins/pg_array_associations.rb +58 -41
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +2 -0
- data/lib/sequel/plugins/prepared_statements.rb +15 -12
- data/lib/sequel/plugins/prepared_statements_safe.rb +1 -3
- data/lib/sequel/plugins/rcte_tree.rb +37 -35
- data/lib/sequel/plugins/serialization.rb +9 -3
- data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +7 -0
- data/lib/sequel/plugins/sql_comments.rb +189 -0
- data/lib/sequel/plugins/static_cache.rb +1 -1
- data/lib/sequel/plugins/string_stripper.rb +1 -1
- data/lib/sequel/plugins/subclasses.rb +28 -11
- data/lib/sequel/plugins/tactical_eager_loading.rb +8 -2
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/tree.rb +9 -4
- data/lib/sequel/plugins/unused_associations.rb +521 -0
- data/lib/sequel/plugins/update_or_create.rb +1 -1
- data/lib/sequel/plugins/validation_class_methods.rb +5 -1
- data/lib/sequel/plugins/validation_helpers.rb +18 -11
- data/lib/sequel/plugins/xml_serializer.rb +1 -1
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/timezones.rb +20 -17
- data/lib/sequel/version.rb +1 -1
- metadata +93 -39
@@ -143,10 +143,14 @@ module Sequel
|
|
143
143
|
compositions[name] = send(composer_meth)
|
144
144
|
end
|
145
145
|
end
|
146
|
-
|
146
|
+
alias_method(name, name)
|
147
|
+
|
148
|
+
meth = :"#{name}="
|
149
|
+
define_method(meth) do |v|
|
147
150
|
modified!
|
148
151
|
compositions[name] = v
|
149
152
|
end
|
153
|
+
alias_method(meth, meth)
|
150
154
|
end
|
151
155
|
end
|
152
156
|
|
@@ -167,8 +171,10 @@ module Sequel
|
|
167
171
|
|
168
172
|
# Freeze compositions hash when freezing model instance.
|
169
173
|
def freeze
|
170
|
-
compositions
|
174
|
+
compositions
|
171
175
|
super
|
176
|
+
compositions.freeze
|
177
|
+
self
|
172
178
|
end
|
173
179
|
|
174
180
|
# For each composition, set the columns in the model class based
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
extension 'async_thread_pool'
|
5
|
+
|
6
|
+
module Plugins
|
7
|
+
# The concurrent_eager_loading plugin allows for eager loading multiple associations
|
8
|
+
# concurrently in separate threads. You must load the async_thread_pool Database
|
9
|
+
# extension into the Database object the model class uses in order for this plugin
|
10
|
+
# to work.
|
11
|
+
#
|
12
|
+
# By default in Sequel, eager loading happens in a serial manner. If you have code
|
13
|
+
# such as:
|
14
|
+
#
|
15
|
+
# Album.eager(:artist, :genre, :tracks)
|
16
|
+
#
|
17
|
+
# Sequel will load the albums, then the artists for the albums, then
|
18
|
+
# the genres for the albums, then the tracks for the albums.
|
19
|
+
#
|
20
|
+
# With the concurrent_eager_loading plugin, you can use the +eager_load_concurrently+
|
21
|
+
# method to allow for concurrent eager loading:
|
22
|
+
#
|
23
|
+
# Album.eager_load_concurrently.eager(:artist, :genre, :tracks)
|
24
|
+
#
|
25
|
+
# This will load the albums, first, since it needs to load the albums to know
|
26
|
+
# which artists, genres, and tracks to eagerly load. However, it will load the
|
27
|
+
# artists, genres, and tracks for the albums concurrently in separate threads.
|
28
|
+
# This can significantly improve performance, especially if there is significant
|
29
|
+
# latency between the application and the database. Note that using separate threads
|
30
|
+
# is only used in the case where there are multiple associations to eagerly load.
|
31
|
+
# With only a single association to eagerly load, there is no reason to use a
|
32
|
+
# separate thread, since it would not improve performance.
|
33
|
+
#
|
34
|
+
# If you want to make concurrent eager loading the default, you can load the
|
35
|
+
# plugin with the +:always+ option. In this case, all eager loads will be
|
36
|
+
# concurrent. If you want to force a non-concurrent eager load, you can use
|
37
|
+
# +eager_load_serially+:
|
38
|
+
#
|
39
|
+
# Album.eager_load_serially.eager(:artist, :genre, :tracks)
|
40
|
+
#
|
41
|
+
# Note that making concurrent eager loading the default is probably a bad idea
|
42
|
+
# if you are eager loading inside transactions and want the eager load to
|
43
|
+
# reflect changes made inside the transaction, unless you plan to use
|
44
|
+
# +eager_load_serially+ for such cases. See the async_thread_pool
|
45
|
+
# Database extension documentation for more general caveats regarding its use.
|
46
|
+
#
|
47
|
+
# The default eager loaders for all of the association types that ship with Sequel
|
48
|
+
# support safe concurrent eager loading. However, if you are specifying a custom
|
49
|
+
# +:eager_loader+ for an association, it may not work safely unless it it modified to
|
50
|
+
# support concurrent eager loading. Taking this example from the
|
51
|
+
# {Advanced Associations guide}[rdoc-ref:doc/advanced_associations.rdoc]
|
52
|
+
#
|
53
|
+
# Album.many_to_one :artist, :eager_loader=>(proc do |eo_opts|
|
54
|
+
# eo_opts[:rows].each{|album| album.associations[:artist] = nil}
|
55
|
+
# id_map = eo_opts[:id_map]
|
56
|
+
# Artist.where(:id=>id_map.keys).all do |artist|
|
57
|
+
# if albums = id_map[artist.id]
|
58
|
+
# albums.each do |album|
|
59
|
+
# album.associations[:artist] = artist
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
# end)
|
64
|
+
#
|
65
|
+
# This would not support concurrent eager loading safely. To support safe
|
66
|
+
# concurrent eager loading, you need to make sure you are not modifying
|
67
|
+
# the associations for objects concurrently by separate threads. This is
|
68
|
+
# implemented using a mutex, which you can access via <tt>eo_opts[:mutex]</tt>.
|
69
|
+
# To keep things simple, you can use +Sequel.synchronize_with+ to only
|
70
|
+
# use this mutex if it is available. You want to use the mutex around the
|
71
|
+
# code that initializes the associations (usually to +nil+ or <tt>[]</tt>),
|
72
|
+
# and also around the code that sets the associatied objects appropriately
|
73
|
+
# after they have been retreived. You do not want to use the mutex around
|
74
|
+
# the code that loads the objects, since that will prevent concurrent loading.
|
75
|
+
# So after the changes, the custom eager loader would look like this:
|
76
|
+
#
|
77
|
+
# Album.many_to_one :artist, :eager_loader=>(proc do |eo_opts|
|
78
|
+
# Sequel.synchronize_with(eo[:mutex]) do
|
79
|
+
# eo_opts[:rows].each{|album| album.associations[:artist] = nil}
|
80
|
+
# end
|
81
|
+
# id_map = eo_opts[:id_map]
|
82
|
+
# rows = Artist.where(:id=>id_map.keys).all
|
83
|
+
# Sequel.synchronize_with(eo[:mutex]) do
|
84
|
+
# rows.each do |artist|
|
85
|
+
# if albums = id_map[artist.id]
|
86
|
+
# albums.each do |album|
|
87
|
+
# album.associations[:artist] = artist
|
88
|
+
# end
|
89
|
+
# end
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
# end)
|
93
|
+
#
|
94
|
+
# Usage:
|
95
|
+
#
|
96
|
+
# # Make all model subclass datasets support concurrent eager loading
|
97
|
+
# Sequel::Model.plugin :concurrent_eager_loading
|
98
|
+
#
|
99
|
+
# # Make the Album class datasets support concurrent eager loading
|
100
|
+
# Album.plugin :concurrent_eager_loading
|
101
|
+
#
|
102
|
+
# # Make all model subclass datasets concurrently eager load by default
|
103
|
+
# Sequel::Model.plugin :concurrent_eager_loading, always: true
|
104
|
+
module ConcurrentEagerLoading
|
105
|
+
def self.configure(mod, opts=OPTS)
|
106
|
+
if opts.has_key?(:always)
|
107
|
+
mod.instance_variable_set(:@always_eager_load_concurrently, opts[:always])
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
module ClassMethods
|
112
|
+
Plugins.inherited_instance_variables(self, :@always_eager_load_concurrently => nil)
|
113
|
+
Plugins.def_dataset_methods(self, [:eager_load_concurrently, :eager_load_serially])
|
114
|
+
|
115
|
+
# Whether datasets for this class should eager load concurrently by default.
|
116
|
+
def always_eager_load_concurrently?
|
117
|
+
@always_eager_load_concurrently
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
module DatasetMethods
|
122
|
+
# Return a cloned dataset that will eager load associated results concurrently
|
123
|
+
# using the async thread pool.
|
124
|
+
def eager_load_concurrently
|
125
|
+
cached_dataset(:_eager_load_concurrently) do
|
126
|
+
clone(:eager_load_concurrently=>true)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Return a cloned dataset that will noteager load associated results concurrently
|
131
|
+
# using the async thread pool. Only useful if the current dataset has been marked
|
132
|
+
# as loading concurrently, or loading concurrently is the model's default behavior.
|
133
|
+
def eager_load_serially
|
134
|
+
cached_dataset(:_eager_load_serially) do
|
135
|
+
clone(:eager_load_concurrently=>false)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
# Whether this particular dataset will eager load results concurrently.
|
142
|
+
def eager_load_concurrently?
|
143
|
+
v = @opts[:eager_load_concurrently]
|
144
|
+
v.nil? ? model.always_eager_load_concurrently? : v
|
145
|
+
end
|
146
|
+
|
147
|
+
# If performing eager loads concurrently, and at least 2 associations are being
|
148
|
+
# eagerly loaded, create a single mutex used for all eager loads. After the
|
149
|
+
# eager loads have been performed, force loading of any async results, so that
|
150
|
+
# all eager loads will have been completed before this method returns.
|
151
|
+
def perform_eager_loads(eager_load_data)
|
152
|
+
return super if !eager_load_concurrently? || eager_load_data.length < 2
|
153
|
+
|
154
|
+
mutex = Mutex.new
|
155
|
+
eager_load_data.each_value do |eo|
|
156
|
+
eo[:mutex] = mutex
|
157
|
+
end
|
158
|
+
|
159
|
+
super.each do |v|
|
160
|
+
if Sequel::Database::AsyncThreadPool::BaseProxy === v
|
161
|
+
v.__value
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# If performing eager loads concurrently, perform this eager load using the
|
167
|
+
# async thread pool.
|
168
|
+
def perform_eager_load(loader, eo)
|
169
|
+
eo[:mutex] ? db.send(:async_run){super} : super
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -62,7 +62,10 @@ module Sequel
|
|
62
62
|
ret = super
|
63
63
|
r = association_reflection(name)
|
64
64
|
meth = r.returns_array? ? name : pluralize(name).to_sym
|
65
|
-
dataset_module
|
65
|
+
dataset_module do
|
66
|
+
define_method(meth){associated(name)}
|
67
|
+
alias_method(meth, meth)
|
68
|
+
end
|
66
69
|
ret
|
67
70
|
end
|
68
71
|
|
data/lib/sequel/plugins/dirty.rb
CHANGED
@@ -41,6 +41,15 @@ module Sequel
|
|
41
41
|
# artist.column_changes # => {}
|
42
42
|
# artist.previous_changes # => {:name=>['Foo', 'Bar']}
|
43
43
|
#
|
44
|
+
# artist.column_previously_was(:name)
|
45
|
+
# # => 'Foo'
|
46
|
+
# artist.column_previously_changed?(:name)
|
47
|
+
# # => true
|
48
|
+
# artist.column_previously_changed?(:name, from: 'Foo', to: 'Bar')
|
49
|
+
# # => true
|
50
|
+
# artist.column_previously_changed?(:name, from: 'Foo', to: 'Baz')
|
51
|
+
# # => false
|
52
|
+
#
|
44
53
|
# There is one caveat; when used with a column that also uses the
|
45
54
|
# serialization plugin, setting the column back to its original value
|
46
55
|
# after changing it is not correctly detected and will leave an entry
|
@@ -105,6 +114,41 @@ module Sequel
|
|
105
114
|
initial_values.has_key?(column)
|
106
115
|
end
|
107
116
|
|
117
|
+
# Whether the column was previously changed.
|
118
|
+
# Options:
|
119
|
+
# :from :: If given, the previous initial value of the column must match this
|
120
|
+
# :to :: If given, the previous changed value of the column must match this
|
121
|
+
#
|
122
|
+
# update(name: 'Current')
|
123
|
+
# previous_changes # => {:name=>['Initial', 'Current']}
|
124
|
+
# column_previously_changed?(:name) # => true
|
125
|
+
# column_previously_changed?(:id) # => false
|
126
|
+
# column_previously_changed?(:name, from: 'Initial', to: 'Current') # => true
|
127
|
+
# column_previously_changed?(:name, from: 'Foo', to: 'Current') # => false
|
128
|
+
def column_previously_changed?(column, opts=OPTS)
|
129
|
+
return false unless (pc = @previous_changes) && (val = pc[column])
|
130
|
+
|
131
|
+
if opts.has_key?(:from)
|
132
|
+
return false unless val[0] == opts[:from]
|
133
|
+
end
|
134
|
+
|
135
|
+
if opts.has_key?(:to)
|
136
|
+
return false unless val[1] == opts[:to]
|
137
|
+
end
|
138
|
+
|
139
|
+
true
|
140
|
+
end
|
141
|
+
|
142
|
+
# The previous value of the column, which is the initial value of
|
143
|
+
# the column before the object was previously saved.
|
144
|
+
#
|
145
|
+
# initial_value(:name) # => 'Initial'
|
146
|
+
# update(name: 'Current')
|
147
|
+
# column_previously_was(:name) # => 'Initial'
|
148
|
+
def column_previously_was(column)
|
149
|
+
(pc = @previous_changes) && (val = pc[column]) && val[0]
|
150
|
+
end
|
151
|
+
|
108
152
|
# Freeze internal data structures
|
109
153
|
def freeze
|
110
154
|
initial_values.freeze
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The enum plugin allows for easily adding methods to modify the value of
|
6
|
+
# a column. It allows treating the column itself as an enum, returning a
|
7
|
+
# symbol for the related enum value. It also allows for setting up dataset
|
8
|
+
# methods to easily find records having or not having each enum value.
|
9
|
+
#
|
10
|
+
# After loading the plugin, you can call the +enum+ method to define the
|
11
|
+
# methods. The +enum+ method accepts a symbol for the underlying
|
12
|
+
# database column, and a hash with symbol keys for the enum values.
|
13
|
+
# For example, the following call:
|
14
|
+
#
|
15
|
+
# Album.enum :status_id, good: 1, bad: 2
|
16
|
+
#
|
17
|
+
# Will define the following instance methods:
|
18
|
+
#
|
19
|
+
# Album#good! :: Change +status_id+ to +1+ (does not save the receiver)
|
20
|
+
# Album#bad! :: Change +status_id+ to +2+ (does not save the receiver)
|
21
|
+
# Album#good? :: Return whether +status_id+ is +1+
|
22
|
+
# Album#bad? :: Return whether +status_id+ is +2+
|
23
|
+
#
|
24
|
+
# It will override the following instance methods:
|
25
|
+
#
|
26
|
+
# Album#status_id :: Return +:good+/+:bad+ instead of +1+/+2+ (other values returned as-is)
|
27
|
+
# Album#status_id= :: Allow calling with +:good+/+:bad+ to set +status_id+ to +1+/+2+ (other values,
|
28
|
+
# such as <tt>'good'</tt>/<tt>'bad'</tt> set as-is)
|
29
|
+
#
|
30
|
+
# If will define the following dataset methods:
|
31
|
+
#
|
32
|
+
# Album.dataset.good :: Return a dataset filtered to rows where +status_id+ is +1+
|
33
|
+
# Album.dataset.not_good :: Return a dataset filtered to rows where +status_id+ is not +1+
|
34
|
+
# Album.dataset.bad:: Return a dataset filtered to rows where +status_id+ is +2+
|
35
|
+
# Album.dataset.not_bad:: Return a dataset filtered to rows where +status_id+ is not +2+
|
36
|
+
#
|
37
|
+
# When calling +enum+, you can also provide the following options:
|
38
|
+
#
|
39
|
+
# :prefix :: Use a prefix for methods defined for each enum value. If +true+ is provided at the value, use the column name as the prefix.
|
40
|
+
# For example, with <tt>prefix: 'status'</tt>, the instance methods defined above would be +status_good?+, +status_bad?+,
|
41
|
+
# +status_good!+, and +status_bad!+, and the dataset methods defined would be +status_good+, +status_not_good+, +status_bad+,
|
42
|
+
# and +status_not_bad+.
|
43
|
+
# :suffix :: Use a suffix for methods defined for each enum value. If +true+ is provided at the value, use the column name as the suffix.
|
44
|
+
# For example, with <tt>suffix: 'status'</tt>, the instance methods defined above would be +good_status?+, +bad_status?+,
|
45
|
+
# +good_status!+, and +bad_status!+, and the dataset methods defined would be +good_status+, +not_good_status+, +bad_status+,
|
46
|
+
# and +not_bad_status+.
|
47
|
+
# :override_accessors :: Set to +false+ to not override the column accessor methods.
|
48
|
+
# :dataset_methods :: Set to +false+ to not define dataset methods.
|
49
|
+
#
|
50
|
+
# Note that this does not use a true enum column in the database. If you are
|
51
|
+
# looking for enum support in the database, and your are using PostgreSQL,
|
52
|
+
# Sequel supports that via the pg_enum Database extension.
|
53
|
+
#
|
54
|
+
# Usage:
|
55
|
+
#
|
56
|
+
# # Make all model subclasses handle enums
|
57
|
+
# Sequel::Model.plugin :enum
|
58
|
+
#
|
59
|
+
# # Make the Album class handle enums
|
60
|
+
# Album.plugin :enum
|
61
|
+
module Enum
|
62
|
+
module ClassMethods
|
63
|
+
# Define instance and dataset methods in this class to treat column
|
64
|
+
# as a enum. See Enum documentation for usage.
|
65
|
+
def enum(column, values, opts=OPTS)
|
66
|
+
raise Sequel::Error, "enum column must be a symbol" unless column.is_a?(Symbol)
|
67
|
+
raise Sequel::Error, "enum values must be provided as a hash with symbol keys" unless values.is_a?(Hash) && values.all?{|k,| k.is_a?(Symbol)}
|
68
|
+
|
69
|
+
if prefix = opts[:prefix]
|
70
|
+
prefix = column if prefix == true
|
71
|
+
prefix = "#{prefix}_"
|
72
|
+
end
|
73
|
+
|
74
|
+
if suffix = opts[:suffix]
|
75
|
+
suffix = column if suffix == true
|
76
|
+
suffix = "_#{suffix}"
|
77
|
+
end
|
78
|
+
|
79
|
+
values = Hash[values].freeze
|
80
|
+
inverted = values.invert.freeze
|
81
|
+
|
82
|
+
unless @enum_methods
|
83
|
+
@enum_methods = Module.new
|
84
|
+
include @enum_methods
|
85
|
+
end
|
86
|
+
|
87
|
+
@enum_methods.module_eval do
|
88
|
+
unless opts[:override_accessors] == false
|
89
|
+
define_method(column) do
|
90
|
+
v = super()
|
91
|
+
inverted.fetch(v, v)
|
92
|
+
end
|
93
|
+
|
94
|
+
define_method(:"#{column}=") do |v|
|
95
|
+
super(values.fetch(v, v))
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
values.each do |key, value|
|
100
|
+
define_method(:"#{prefix}#{key}#{suffix}!") do
|
101
|
+
self[column] = value
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
define_method(:"#{prefix}#{key}#{suffix}?") do
|
106
|
+
self[column] == value
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
unless opts[:dataset_methods] == false
|
112
|
+
dataset_module do
|
113
|
+
values.each do |key, value|
|
114
|
+
cond = Sequel[column=>value]
|
115
|
+
where :"#{prefix}#{key}#{suffix}", cond
|
116
|
+
where :"#{prefix}not_#{key}#{suffix}", ~cond
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -111,7 +111,9 @@ module Sequel
|
|
111
111
|
# an association, allow lazy loading that association, since the
|
112
112
|
# lazy association load will use a hash table lookup and not a query.
|
113
113
|
def allow_lazy_load_for_static_cache_associations
|
114
|
+
# :nocov:
|
114
115
|
if defined?(::Sequel::Plugins::StaticCache::ClassMethods)
|
116
|
+
# :nocov:
|
115
117
|
@association_reflections.each_value do |ref|
|
116
118
|
if ref.associated_class.is_a?(::Sequel::Plugins::StaticCache::ClassMethods)
|
117
119
|
ref[:forbid_lazy_load] = false
|
@@ -22,6 +22,10 @@ module Sequel
|
|
22
22
|
# for that album. See the PostgreSQL and SQLite adapter documention for
|
23
23
|
# the options you can pass to the insert_conflict method.
|
24
24
|
#
|
25
|
+
# You should not attempt to use this plugin to ignore conflicts when
|
26
|
+
# inserting, you should only use it to turn insert conflicts into updates.
|
27
|
+
# Any usage to ignore conflicts is not recommended or supported.
|
28
|
+
#
|
25
29
|
# Usage:
|
26
30
|
#
|
27
31
|
# # Make all model subclasses support insert_conflict
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The instance_specific_default plugin exists to make it easier to use a
|
6
|
+
# global :instance_specific association option, or to warn or raise when Sequel
|
7
|
+
# has to guess which value to use :instance_specific option (Sequel defaults to
|
8
|
+
# guessing true as that is the conservative setting). It is helpful to
|
9
|
+
# use this plugin, particularly with the :warn or :raise settings, to determine
|
10
|
+
# which associations should have :instance_specific set. Setting the
|
11
|
+
# :instance_specific to false for associations that are not instance specific
|
12
|
+
# can improve performance.
|
13
|
+
#
|
14
|
+
# Associations are instance-specific if their block calls
|
15
|
+
# a model instance method, or where the value of the block varies
|
16
|
+
# based on runtime state, and the variance is outside of a delayed evaluation.
|
17
|
+
# For example, with the following three associations:
|
18
|
+
#
|
19
|
+
# Album.one_to_one :first_track, class: :Track do |ds|
|
20
|
+
# ds.where(number: 1)
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# Album.one_to_one :last_track, class: :Track do |ds|
|
24
|
+
# ds.where(number: num_tracks)
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# Album.one_to_many :recent_tracks, class: :Track do |ds|
|
28
|
+
# ds.where{date_updated > Date.today - 10}
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# +first_track+ is not instance specific, but +last_track+ and +recent_tracks+ are.
|
32
|
+
# +last_track+ is because the +num_tracks+ call in the block is calling
|
33
|
+
# <tt>Album#num_tracks</tt>. +recent_tracks+ is because the value will change over
|
34
|
+
# time. This plugin allows you to find these cases, and set the :instance_specific
|
35
|
+
# option appropriately for them:
|
36
|
+
#
|
37
|
+
# Album.one_to_one :first_track, class: :Track, instance_specific: false do |ds|
|
38
|
+
# ds.where(number: 1)
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# Album.one_to_one :last_track, class: :Track, instance_specific: true do |ds|
|
42
|
+
# ds.where(number: num_tracks)
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# Album.one_to_many :recent_tracks, class: :Track, instance_specific: true do |ds|
|
46
|
+
# ds.where{date_updated > Date.today - 10}
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# For the +recent_tracks+ association, instead of marking it instance_specific, you
|
50
|
+
# could also use a delayed evaluation, since it doesn't actually contain
|
51
|
+
# instance-specific code:
|
52
|
+
#
|
53
|
+
# Album.one_to_many :recent_tracks, class: :Track, instance_specific: false do |ds|
|
54
|
+
# ds.where{date_updated > Sequel.delay{Date.today - 10}}
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# Possible arguments to provide when loading the plugin:
|
58
|
+
#
|
59
|
+
# true :: Set the :instance_specific option to true
|
60
|
+
# false :: Set the :instance_specific option to false
|
61
|
+
# :default :: Call super to set the :instance_specific option
|
62
|
+
# :warn :: Emit a warning before calling super to set the :instance_specific option
|
63
|
+
# :raise :: Raise a Sequel::Error if an :instance_specific option is not provided and
|
64
|
+
# an association could be instance-specific.
|
65
|
+
#
|
66
|
+
# Note that this plugin only affects associations which could be instance
|
67
|
+
# specific (those with blocks), where the :instance_specific option was not
|
68
|
+
# specified when the association was created.
|
69
|
+
#
|
70
|
+
# Usage:
|
71
|
+
#
|
72
|
+
# # Set how to handle associations that could be instance specific
|
73
|
+
# # but did not specify an :instance_specific option, for all subclasses
|
74
|
+
# # (set before creating subclasses).
|
75
|
+
# Sequel::Model.plugin :instance_specific_default, :warn
|
76
|
+
#
|
77
|
+
# # Set how to handle associations that could be instance specific
|
78
|
+
# # but did not specify an :instance_specific option, for the Album class
|
79
|
+
# Album.plugin :instance_specific_default, :warn
|
80
|
+
module InstanceSpecificDefault
|
81
|
+
# Set how to handle associations that could be instance specific but did
|
82
|
+
# not specify an :instance_specific value.
|
83
|
+
def self.configure(model, default)
|
84
|
+
model.instance_variable_set(:@instance_specific_default, default)
|
85
|
+
end
|
86
|
+
|
87
|
+
module ClassMethods
|
88
|
+
Plugins.inherited_instance_variables(self, :@instance_specific_default=>nil)
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# Return the appropriate :instance_specific value, or warn or raise if
|
93
|
+
# configured.
|
94
|
+
def _association_instance_specific_default(name)
|
95
|
+
case @instance_specific_default
|
96
|
+
when true, false
|
97
|
+
return @instance_specific_default
|
98
|
+
when :default
|
99
|
+
# nothing
|
100
|
+
when :warn
|
101
|
+
warn("possibly instance-specific association without :instance_specific option (class: #{self}, association: #{name})", :uplevel => 3)
|
102
|
+
when :raise
|
103
|
+
raise Sequel::Error, "possibly instance-specific association without :instance_specific option (class: #{self}, association: #{name})"
|
104
|
+
else
|
105
|
+
raise Sequel::Error, "invalid value passed to instance_specific_default plugin: #{@instance_specific_default.inspect}"
|
106
|
+
end
|
107
|
+
|
108
|
+
super
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -133,21 +133,39 @@ module Sequel
|
|
133
133
|
end
|
134
134
|
end
|
135
135
|
|
136
|
-
#
|
137
|
-
#
|
138
|
-
# work by creating instances of this class, which take a
|
139
|
-
# literal JSON string and have +to_json+ return it.
|
136
|
+
# SEQUEL6: Remove
|
137
|
+
# :nocov:
|
140
138
|
class Literal
|
141
|
-
# Store the literal JSON to use
|
142
139
|
def initialize(json)
|
143
140
|
@json = json
|
144
141
|
end
|
145
142
|
|
146
|
-
# Return the literal JSON to use
|
147
143
|
def to_json(*a)
|
148
144
|
@json
|
149
145
|
end
|
150
146
|
end
|
147
|
+
# :nocov:
|
148
|
+
Sequel::Deprecation.deprecate_constant(self, :Literal)
|
149
|
+
|
150
|
+
# Convert the given object to a JSON data structure using the given arguments.
|
151
|
+
def self.object_to_json_data(obj, *args, &block)
|
152
|
+
if obj.is_a?(Array)
|
153
|
+
obj.map{|x| object_to_json_data(x, *args, &block)}
|
154
|
+
else
|
155
|
+
if obj.respond_to?(:to_json_data)
|
156
|
+
obj.to_json_data(*args, &block)
|
157
|
+
else
|
158
|
+
begin
|
159
|
+
Sequel.parse_json(Sequel.object_to_json(obj, *args, &block))
|
160
|
+
# :nocov:
|
161
|
+
rescue Sequel.json_parser_error_class
|
162
|
+
# Support for old Ruby code that only supports parsing JSON object/array
|
163
|
+
Sequel.parse_json(Sequel.object_to_json([obj], *args, &block))[0]
|
164
|
+
# :nocov:
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
151
169
|
|
152
170
|
module ClassMethods
|
153
171
|
# The default opts to use when serializing model objects to JSON.
|
@@ -324,20 +342,7 @@ module Sequel
|
|
324
342
|
end
|
325
343
|
|
326
344
|
v = v.empty? ? [] : [v]
|
327
|
-
|
328
|
-
objs = public_send(k)
|
329
|
-
|
330
|
-
is_array = if r = model.association_reflection(k)
|
331
|
-
r.returns_array?
|
332
|
-
else
|
333
|
-
objs.is_a?(Array)
|
334
|
-
end
|
335
|
-
|
336
|
-
h[key_name] = if is_array
|
337
|
-
objs.map{|obj| Literal.new(Sequel.object_to_json(obj, *v))}
|
338
|
-
else
|
339
|
-
Literal.new(Sequel.object_to_json(objs, *v))
|
340
|
-
end
|
345
|
+
h[key_name] = JsonSerializer.object_to_json_data(public_send(k), *v)
|
341
346
|
end
|
342
347
|
else
|
343
348
|
Array(inc).each do |c|
|
@@ -347,7 +352,8 @@ module Sequel
|
|
347
352
|
else
|
348
353
|
key_name = c.to_s
|
349
354
|
end
|
350
|
-
|
355
|
+
|
356
|
+
h[key_name] = JsonSerializer.object_to_json_data(public_send(c))
|
351
357
|
end
|
352
358
|
end
|
353
359
|
end
|
@@ -359,9 +365,18 @@ module Sequel
|
|
359
365
|
h = {root => h}
|
360
366
|
end
|
361
367
|
|
362
|
-
h = yield h if
|
368
|
+
h = yield h if defined?(yield)
|
363
369
|
Sequel.object_to_json(h, *a)
|
364
370
|
end
|
371
|
+
|
372
|
+
# Convert the receiver to a JSON data structure using the given arguments.
|
373
|
+
def to_json_data(*args, &block)
|
374
|
+
if block
|
375
|
+
to_json(*args){|x| return block.call(x)}
|
376
|
+
else
|
377
|
+
to_json(*args){|x| return x}
|
378
|
+
end
|
379
|
+
end
|
365
380
|
end
|
366
381
|
|
367
382
|
module DatasetMethods
|
@@ -420,13 +435,13 @@ module Sequel
|
|
420
435
|
else
|
421
436
|
all
|
422
437
|
end
|
423
|
-
|
438
|
+
JsonSerializer.object_to_json_data(array, opts, &opts[:instance_block])
|
424
439
|
else
|
425
440
|
all
|
426
441
|
end
|
427
442
|
|
428
443
|
res = {collection_root => res} if collection_root
|
429
|
-
res = yield res if
|
444
|
+
res = yield res if defined?(yield)
|
430
445
|
|
431
446
|
Sequel.object_to_json(res, *a)
|
432
447
|
end
|