sequel 5.85.0 → 5.93.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/lib/sequel/adapters/ado.rb +1 -1
- data/lib/sequel/adapters/ibmdb.rb +1 -0
- data/lib/sequel/adapters/jdbc/db2.rb +2 -2
- data/lib/sequel/adapters/jdbc/derby.rb +2 -2
- data/lib/sequel/adapters/jdbc/h2.rb +2 -2
- data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -2
- data/lib/sequel/adapters/jdbc/jtds.rb +2 -2
- data/lib/sequel/adapters/jdbc/mysql.rb +1 -1
- data/lib/sequel/adapters/jdbc/oracle.rb +5 -5
- data/lib/sequel/adapters/jdbc/postgresql.rb +5 -5
- data/lib/sequel/adapters/jdbc/sqlanywhere.rb +6 -6
- data/lib/sequel/adapters/jdbc/sqlite.rb +2 -2
- data/lib/sequel/adapters/jdbc/sqlserver.rb +2 -2
- data/lib/sequel/adapters/jdbc.rb +8 -8
- data/lib/sequel/adapters/mysql2.rb +8 -1
- data/lib/sequel/adapters/oracle.rb +16 -0
- data/lib/sequel/adapters/shared/access.rb +1 -0
- data/lib/sequel/adapters/shared/mssql.rb +4 -3
- data/lib/sequel/adapters/shared/mysql.rb +8 -4
- data/lib/sequel/adapters/shared/oracle.rb +1 -0
- data/lib/sequel/adapters/shared/postgres.rb +140 -9
- data/lib/sequel/adapters/sqlite.rb +4 -0
- data/lib/sequel/adapters/trilogy.rb +1 -2
- data/lib/sequel/core.rb +15 -0
- data/lib/sequel/database/dataset_defaults.rb +3 -3
- data/lib/sequel/database/misc.rb +17 -4
- data/lib/sequel/database/query.rb +11 -11
- data/lib/sequel/database/schema_generator.rb +8 -0
- data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +1 -1
- data/lib/sequel/dataset/prepared_statements.rb +70 -25
- data/lib/sequel/dataset/query.rb +9 -5
- data/lib/sequel/dataset/sql.rb +19 -9
- data/lib/sequel/extensions/connection_validator.rb +15 -10
- data/lib/sequel/extensions/migration.rb +23 -3
- data/lib/sequel/extensions/null_dataset.rb +2 -2
- data/lib/sequel/extensions/pg_auto_parameterize.rb +6 -1
- data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +93 -10
- data/lib/sequel/extensions/pg_enum.rb +3 -3
- data/lib/sequel/extensions/pg_row.rb +3 -1
- data/lib/sequel/extensions/pg_schema_caching.rb +90 -0
- data/lib/sequel/extensions/query_blocker.rb +172 -0
- data/lib/sequel/extensions/schema_caching.rb +24 -9
- data/lib/sequel/extensions/schema_dumper.rb +16 -4
- data/lib/sequel/extensions/sqlite_json_ops.rb +1 -1
- data/lib/sequel/extensions/string_agg.rb +2 -2
- data/lib/sequel/extensions/virtual_row_method_block.rb +1 -0
- data/lib/sequel/model/associations.rb +28 -3
- data/lib/sequel/model/base.rb +67 -18
- data/lib/sequel/plugins/composition.rb +1 -1
- data/lib/sequel/plugins/enum.rb +1 -1
- data/lib/sequel/plugins/forbid_lazy_load.rb +14 -1
- data/lib/sequel/plugins/inspect_pk.rb +44 -0
- data/lib/sequel/plugins/instance_filters.rb +4 -1
- data/lib/sequel/plugins/inverted_subsets.rb +1 -0
- data/lib/sequel/plugins/lazy_attributes.rb +1 -1
- data/lib/sequel/plugins/nested_attributes.rb +10 -5
- data/lib/sequel/plugins/paged_operations.rb +5 -2
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +6 -1
- data/lib/sequel/plugins/pg_auto_validate_enums.rb +88 -0
- data/lib/sequel/plugins/pg_eager_any_typed_array.rb +95 -0
- data/lib/sequel/plugins/rcte_tree.rb +1 -1
- data/lib/sequel/plugins/serialization.rb +11 -5
- data/lib/sequel/plugins/sql_comments.rb +7 -2
- data/lib/sequel/plugins/static_cache_cache.rb +50 -13
- data/lib/sequel/plugins/subset_conditions.rb +85 -5
- data/lib/sequel/plugins/subset_static_cache.rb +263 -0
- data/lib/sequel/sql.rb +15 -6
- data/lib/sequel/version.rb +1 -1
- metadata +9 -6
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The pg_auto_validate_enums plugin implements automatic validations for
|
6
|
+
# enum columns, ensuring that enum columns have a valid value. With this
|
7
|
+
# plugin, trying to save with an invalid enum value results in
|
8
|
+
# Sequel::ValidationFailed before saving, instead of Sequel::DatabaseError
|
9
|
+
# (wrapping PG::InvalidTextRepresentation or similar exception) during saving.
|
10
|
+
#
|
11
|
+
# class Person < Sequel::Model
|
12
|
+
# # assume state enum column with allowed values active and inactive
|
13
|
+
# plugin :pg_auto_validate_enums
|
14
|
+
# end
|
15
|
+
# p = Person.new(state: "active").valid? # => true
|
16
|
+
# p = Person.new(state: "inactive").valid? # => true
|
17
|
+
# p = Person.new(state: "other").valid? # => false
|
18
|
+
#
|
19
|
+
# While you can load this into individual model classes, typical use would
|
20
|
+
# be to load it into Sequel::Model or the appropriate model base class,
|
21
|
+
# and have all models that inherit from that class automatically pick it up.
|
22
|
+
#
|
23
|
+
# This plugin depends on the validation_helpers plugin.
|
24
|
+
module PgAutoValidateEnums
|
25
|
+
# Load the validation_helpers plugin.
|
26
|
+
def self.apply(model, opts=OPTS)
|
27
|
+
model.plugin(:validation_helpers)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Load the pg_enum extension into the database, and reload the schema
|
31
|
+
# if it is already loaded. The opts given are used for the validates_includes
|
32
|
+
# validations (with allow_nil: true and from: :values enabled by default,
|
33
|
+
# to avoid issues with nullable enum columns and cases where the column
|
34
|
+
# method has been overridden.
|
35
|
+
def self.configure(model, opts=OPTS)
|
36
|
+
model.instance_exec do
|
37
|
+
db.extension(:pg_enum) unless @db.instance_variable_get(:@enum_labels)
|
38
|
+
if @db_schema
|
39
|
+
get_db_schema(true)
|
40
|
+
_get_pg_pg_auto_validate_enums_metadata
|
41
|
+
end
|
42
|
+
@pg_auto_validate_enums_opts = {allow_nil: true, from: :values}.merge!(opts).freeze
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module ClassMethods
|
47
|
+
# Hash with enum column symbol values and arrays of valid string values.
|
48
|
+
attr_reader :pg_auto_validate_enums_metadata
|
49
|
+
|
50
|
+
# Options to pass to the validates_includes calls used by the plugin.
|
51
|
+
attr_reader :pg_auto_validate_enums_opts
|
52
|
+
|
53
|
+
Plugins.after_set_dataset(self, :_get_pg_pg_auto_validate_enums_metadata)
|
54
|
+
|
55
|
+
Plugins.inherited_instance_variables(self,
|
56
|
+
:@pg_auto_validate_enums_metadata=>nil,
|
57
|
+
:@pg_auto_validate_enums_opts=>nil)
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# Parse the column schema to find columns with :enum_values entries,
|
62
|
+
# which will be used to setup validations.
|
63
|
+
def _get_pg_pg_auto_validate_enums_metadata
|
64
|
+
metadata = {}
|
65
|
+
@db_schema.each do |key, sch|
|
66
|
+
if enum_values = sch[:enum_values]
|
67
|
+
metadata[key] = enum_values
|
68
|
+
end
|
69
|
+
end
|
70
|
+
@pg_auto_validate_enums_metadata = metadata.freeze
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module InstanceMethods
|
75
|
+
# Validate that all of the model's enum columns have valid values.
|
76
|
+
def validate
|
77
|
+
super
|
78
|
+
|
79
|
+
klass = self.class
|
80
|
+
opts = klass.pg_auto_validate_enums_opts
|
81
|
+
klass.pg_auto_validate_enums_metadata.each do |column, values|
|
82
|
+
validates_includes(values, column, opts)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The pg_eager_any_typed_array plugin automatically converts
|
6
|
+
# the predicate expressions used for eager loading from:
|
7
|
+
#
|
8
|
+
# table.column IN (value_list)
|
9
|
+
#
|
10
|
+
# to:
|
11
|
+
#
|
12
|
+
# table.column = ANY(array_expr::type[])
|
13
|
+
#
|
14
|
+
# This makes it easier to use the pg_auto_parameterize_in_array
|
15
|
+
# extension with the :treat_string_list_as_text_array option,
|
16
|
+
# when using foreign keys with non-text database types that are represented
|
17
|
+
# by Ruby strings, such as enum and uuid types.
|
18
|
+
#
|
19
|
+
# Most association types that ship with Sequel have their predicate
|
20
|
+
# expressions converted by this plugin. Here are the exceptions:
|
21
|
+
#
|
22
|
+
# * associations using composite predicate keys
|
23
|
+
# * many_to_pg_array associations
|
24
|
+
# * many_to_many/one_through_one associations using :join_table_db option
|
25
|
+
# * many_through_many/one_through_many associations using
|
26
|
+
# :separate_table_per_query option
|
27
|
+
#
|
28
|
+
# To avoid predicate conversion for particular associations, set the
|
29
|
+
# :eager_loading_predicate_transform association option to nil/false.
|
30
|
+
#
|
31
|
+
# This plugin loads the pg_array extension into the model's Database.
|
32
|
+
module PgEagerAnyTypedArray
|
33
|
+
# Add the pg_array extension to the database
|
34
|
+
def self.apply(model)
|
35
|
+
model.db.extension(:pg_array)
|
36
|
+
end
|
37
|
+
|
38
|
+
module ClassMethods
|
39
|
+
TRANSFORM = proc do |values, ref|
|
40
|
+
type = ref.send(:cached_fetch, :_pg_eager_any_typed_array_type) do
|
41
|
+
key = ref.predicate_key
|
42
|
+
next if key.is_a?(Array)
|
43
|
+
|
44
|
+
while key.is_a?(SQL::QualifiedIdentifier)
|
45
|
+
key = key.column
|
46
|
+
end
|
47
|
+
|
48
|
+
# :nocov:
|
49
|
+
# many_to_pg_array association type does not need changes, as it
|
50
|
+
# already converts the values to a typed postgres array, it does
|
51
|
+
# not call the code that uses :eager_loading_predicate_transform.
|
52
|
+
#
|
53
|
+
# No association type that ships with Sequel can reach this code
|
54
|
+
# unless it is one of these types, but external association types
|
55
|
+
# could potentially reach it.
|
56
|
+
sch = case ref[:type]
|
57
|
+
# :nocov:
|
58
|
+
when :many_to_one, :one_to_one, :one_to_many, :pg_array_to_many
|
59
|
+
ref.associated_class.db_schema
|
60
|
+
when :many_to_many, :one_through_one
|
61
|
+
# Not compatible with the :join_table_db option, but that option
|
62
|
+
# does not call into this code.
|
63
|
+
Hash[ref.associated_class.db.schema(ref.join_table_source)]
|
64
|
+
when :many_through_many, :one_through_many
|
65
|
+
# Not compatible with the :separate_query_per_table option, but
|
66
|
+
# that option does not call into this code.
|
67
|
+
Hash[ref.associated_class.db.schema(ref[:through][0][:table])]
|
68
|
+
end
|
69
|
+
|
70
|
+
if sch && (sch = sch[key])
|
71
|
+
sch[:db_type]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
if type
|
76
|
+
Sequel.function(:ANY, Sequel.pg_array(values, type))
|
77
|
+
else
|
78
|
+
values
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Set the :eager_loading_predicate_transform option if not already set
|
83
|
+
def associate(type, name, opts = OPTS, &block)
|
84
|
+
res = super
|
85
|
+
|
86
|
+
unless res.has_key?(:eager_loading_predicate_transform)
|
87
|
+
res[:eager_loading_predicate_transform] = TRANSFORM
|
88
|
+
end
|
89
|
+
|
90
|
+
res
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -81,7 +81,7 @@ module Sequel
|
|
81
81
|
|
82
82
|
opts = opts.dup
|
83
83
|
opts[:class] = model
|
84
|
-
opts[:methods_module] = Module.new
|
84
|
+
opts[:methods_module] = Sequel.set_temp_name(Module.new){"#{model.name}::_rcte_tree[:methods_module]"}
|
85
85
|
opts[:union_all] = opts[:union_all].nil? ? true : opts[:union_all]
|
86
86
|
model.send(:include, opts[:methods_module])
|
87
87
|
|
@@ -37,7 +37,8 @@ module Sequel
|
|
37
37
|
#
|
38
38
|
# # Register custom serializer/deserializer pair, if desired
|
39
39
|
# require 'sequel/plugins/serialization'
|
40
|
-
#
|
40
|
+
# require 'base64'
|
41
|
+
# Sequel::Plugins::Serialization.register_format(:base64, Base64.method(:encode64), Base64.method(:decode64))
|
41
42
|
#
|
42
43
|
# class User < Sequel::Model
|
43
44
|
# # Built-in format support when loading the plugin
|
@@ -48,10 +49,10 @@ module Sequel
|
|
48
49
|
# serialize_attributes :marshal, :permissions
|
49
50
|
#
|
50
51
|
# # Use custom registered serialization format just like built-in format
|
51
|
-
# serialize_attributes :
|
52
|
+
# serialize_attributes :base64, :password
|
52
53
|
#
|
53
54
|
# # Use a custom serializer/deserializer pair without registering
|
54
|
-
# serialize_attributes [:
|
55
|
+
# serialize_attributes [ Base64.method(:encode64), Base64.method(:decode64)], :password
|
55
56
|
# end
|
56
57
|
# user = User.create
|
57
58
|
# user.permissions = {global: 'read-only'}
|
@@ -123,7 +124,12 @@ module Sequel
|
|
123
124
|
end
|
124
125
|
|
125
126
|
# Create instance level reader that deserializes column values on request,
|
126
|
-
# and instance level writer that stores new deserialized values.
|
127
|
+
# and instance level writer that stores new deserialized values. If +format+
|
128
|
+
# is a symbol, it should correspond to a previously-registered format using +register_format+.
|
129
|
+
# Otherwise, +format+ is expected to be a 2-element array of callables,
|
130
|
+
# with the first element being the serializer, used to convert the value used by the application
|
131
|
+
# to the value that will be stored in the database, and the second element being the deserializer,
|
132
|
+
# used to convert the value stored the database to the value used by the application.
|
127
133
|
def serialize_attributes(format, *columns)
|
128
134
|
if format.is_a?(Symbol)
|
129
135
|
unless format = Sequel.synchronize{REGISTERED_FORMATS[format]}
|
@@ -140,7 +146,7 @@ module Sequel
|
|
140
146
|
# Add serializated attribute acessor methods to the serialization_module
|
141
147
|
def define_serialized_attribute_accessor(serializer, deserializer, *columns)
|
142
148
|
m = self
|
143
|
-
include(@serialization_module ||= Module.new) unless @serialization_module
|
149
|
+
include(@serialization_module ||= Sequel.set_temp_name(Module.new){"#{name}::@serialization_module"}) unless @serialization_module
|
144
150
|
@serialization_module.class_eval do
|
145
151
|
columns.each do |column|
|
146
152
|
m.serialization_map[column] = serializer
|
@@ -40,7 +40,8 @@ module Sequel
|
|
40
40
|
# Album.sql_comments_dataset_methods :to_csv # csv_serializer plugin
|
41
41
|
#
|
42
42
|
# In order for the sql_comments plugin to work, the sql_comments
|
43
|
-
# Database extension must be loaded into the model's database
|
43
|
+
# Database extension must be loaded into the model's database, so
|
44
|
+
# loading the plugin does this automatically.
|
44
45
|
#
|
45
46
|
# Note that in order to make sure SQL comments are included, some
|
46
47
|
# optimizations are disabled if this plugin is loaded.
|
@@ -67,6 +68,10 @@ module Sequel
|
|
67
68
|
# :nocov:
|
68
69
|
end
|
69
70
|
|
71
|
+
def self.apply(model)
|
72
|
+
model.db.extension(:sql_comments)
|
73
|
+
end
|
74
|
+
|
70
75
|
def self.configure(model)
|
71
76
|
model.send(:reset_fast_pk_lookup_sql)
|
72
77
|
end
|
@@ -85,7 +90,7 @@ module Sequel
|
|
85
90
|
# Use automatic SQL comments for the given dataset methods.
|
86
91
|
def sql_comments_dataset_methods(*meths)
|
87
92
|
unless @_sql_comments_dataset_module
|
88
|
-
dataset_module(@_sql_comments_dataset_module = Module.new)
|
93
|
+
dataset_module(@_sql_comments_dataset_module = Sequel.set_temp_name(Module.new){"#{name}::@_sql_comments_dataset_module"})
|
89
94
|
end
|
90
95
|
_sql_comments_methods(@_sql_comments_dataset_module, :dataset, meths)
|
91
96
|
end
|
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
module Sequel
|
4
4
|
module Plugins
|
5
|
-
# The static_cache_cache plugin allows for caching the row content for
|
6
|
-
# that use the
|
7
|
-
# can avoid the need to query the database every time loading
|
8
|
-
#
|
9
|
-
# plugin.
|
5
|
+
# The static_cache_cache plugin allows for caching the row content for the current
|
6
|
+
# class and subclasses that use the static_cache or subset_static_cache plugins.
|
7
|
+
# Using this plugin can avoid the need to query the database every time loading
|
8
|
+
# the static_cache plugin into a model (static_cache plugin) or using the
|
9
|
+
# cache_subset method (subset_static_cache plugin).
|
10
10
|
#
|
11
11
|
# Usage:
|
12
12
|
#
|
@@ -26,11 +26,7 @@ module Sequel
|
|
26
26
|
module ClassMethods
|
27
27
|
# Dump the in-memory cached rows to the cache file.
|
28
28
|
def dump_static_cache_cache
|
29
|
-
|
30
|
-
@static_cache_cache.sort.each do |k, v|
|
31
|
-
static_cache_cache[k] = v
|
32
|
-
end
|
33
|
-
File.open(@static_cache_cache_file, 'wb'){|f| f.write(Marshal.dump(static_cache_cache))}
|
29
|
+
File.open(@static_cache_cache_file, 'wb'){|f| f.write(Marshal.dump(sort_static_cache_hash(@static_cache_cache)))}
|
34
30
|
nil
|
35
31
|
end
|
36
32
|
|
@@ -38,16 +34,57 @@ module Sequel
|
|
38
34
|
|
39
35
|
private
|
40
36
|
|
37
|
+
# Sort the given static cache hash in a deterministic way, so that
|
38
|
+
# the same static cache values will result in the same marshal file.
|
39
|
+
def sort_static_cache_hash(cache)
|
40
|
+
cache = cache.sort do |a, b|
|
41
|
+
a, = a
|
42
|
+
b, = b
|
43
|
+
if a.is_a?(Array)
|
44
|
+
if b.is_a?(Array)
|
45
|
+
a_name, a_meth = a
|
46
|
+
b_name, b_meth = b
|
47
|
+
x = a_name <=> b_name
|
48
|
+
if x.zero?
|
49
|
+
x = a_meth <=> b_meth
|
50
|
+
end
|
51
|
+
x
|
52
|
+
else
|
53
|
+
1
|
54
|
+
end
|
55
|
+
elsif b.is_a?(Array)
|
56
|
+
-1
|
57
|
+
else
|
58
|
+
a <=> b
|
59
|
+
end
|
60
|
+
end
|
61
|
+
Hash[cache]
|
62
|
+
end
|
63
|
+
|
41
64
|
# Load the rows for the model from the cache if available.
|
42
65
|
# If not available, load the rows from the database, and
|
43
66
|
# then update the cache with the raw rows.
|
44
67
|
def load_static_cache_rows
|
45
|
-
|
68
|
+
_load_static_cache_rows(dataset, name)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Load the rows for the subset from the cache if available.
|
72
|
+
# If not available, load the rows from the database, and
|
73
|
+
# then update the cache with the raw rows.
|
74
|
+
def load_subset_static_cache_rows(ds, meth)
|
75
|
+
_load_static_cache_rows(ds, [name, meth].freeze)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Check the cache first for the key, and return rows without a database
|
79
|
+
# query if present. Otherwise, get all records in the provided dataset,
|
80
|
+
# and update the cache with them.
|
81
|
+
def _load_static_cache_rows(ds, key)
|
82
|
+
if rows = Sequel.synchronize{@static_cache_cache[key]}
|
46
83
|
rows.map{|row| call(row)}.freeze
|
47
84
|
else
|
48
|
-
rows =
|
85
|
+
rows = ds.all.freeze
|
49
86
|
raw_rows = rows.map(&:values)
|
50
|
-
Sequel.synchronize{@static_cache_cache[
|
87
|
+
Sequel.synchronize{@static_cache_cache[key] = raw_rows}
|
51
88
|
rows
|
52
89
|
end
|
53
90
|
end
|
@@ -3,18 +3,31 @@
|
|
3
3
|
module Sequel
|
4
4
|
module Plugins
|
5
5
|
# The subset_conditions plugin creates an additional *_conditions method
|
6
|
-
# for every subset
|
7
|
-
#
|
8
|
-
# filter or combine them with OR.
|
6
|
+
# for every `subset`, `where`, and `exclude` method call in a dataset_module
|
7
|
+
# block. This method returns the filter conditions, which can be useful if
|
8
|
+
# you want to use the conditions for a separate filter or combine them with OR.
|
9
|
+
# It also supports where_all and where_any dataset_module methods for
|
10
|
+
# combining multiple dataset method filters with AND or OR.
|
9
11
|
#
|
10
12
|
# Usage:
|
11
13
|
#
|
12
14
|
# # Add subset_conditions in the Album class
|
13
15
|
# Album.plugin :subset_conditions
|
14
16
|
#
|
15
|
-
# # This will now create a published_conditions method
|
16
17
|
# Album.dataset_module do
|
17
|
-
#
|
18
|
+
# # This will now create a published_conditions method
|
19
|
+
# where :published, published: true
|
20
|
+
#
|
21
|
+
# # This will now create a not_bad_conditions method
|
22
|
+
# exclude :not_bad, :bad
|
23
|
+
#
|
24
|
+
# # This will create good_and_available and
|
25
|
+
# # good_and_available_conditions methods
|
26
|
+
# where_all :good_and_available, :published, :not_bad
|
27
|
+
#
|
28
|
+
# # This will create good_or_available and
|
29
|
+
# # good_or_available_conditions methods
|
30
|
+
# where_any :good_or_available, :published, :not_bad
|
18
31
|
# end
|
19
32
|
#
|
20
33
|
# Album.where(Album.published_conditions).sql
|
@@ -25,10 +38,17 @@ module Sequel
|
|
25
38
|
#
|
26
39
|
# Album.where(Album.published_conditions | {ready: true}).sql
|
27
40
|
# # SELECT * FROM albums WHERE ((published IS TRUE) OR (ready IS TRUE))
|
41
|
+
#
|
42
|
+
# Album.good_and_available.sql
|
43
|
+
# SELECT * FROM albums WHERE ((published IS TRUE) AND NOT bad)
|
44
|
+
#
|
45
|
+
# Album.good_or_available.sql
|
46
|
+
# SELECT * FROM albums WHERE ((published IS TRUE) OR NOT bad)
|
28
47
|
module SubsetConditions
|
29
48
|
def self.apply(model, &block)
|
30
49
|
model.instance_exec do
|
31
50
|
@dataset_module_class = Class.new(@dataset_module_class) do
|
51
|
+
Sequel.set_temp_name(self){"#{model.name}::@dataset_module_class(SubsetConditions)"}
|
32
52
|
include DatasetModuleMethods
|
33
53
|
end
|
34
54
|
end
|
@@ -42,6 +62,66 @@ module Sequel
|
|
42
62
|
cond = cond.first if cond.size == 1
|
43
63
|
define_method(:"#{name}_conditions"){filter_expr(cond, &block)}
|
44
64
|
end
|
65
|
+
|
66
|
+
# Also create a method that returns the conditions the filter uses.
|
67
|
+
def exclude(name, *args, &block)
|
68
|
+
super
|
69
|
+
cond = args
|
70
|
+
cond = cond.first if cond.size == 1
|
71
|
+
define_method(:"#{name}_conditions"){Sequel.~(filter_expr(cond, &block))}
|
72
|
+
end
|
73
|
+
|
74
|
+
# Create a method that combines filters from already registered
|
75
|
+
# dataset methods, and filters for rows where all of the conditions
|
76
|
+
# are satisfied.
|
77
|
+
#
|
78
|
+
# Employee.dataset_module do
|
79
|
+
# where :active, active: true
|
80
|
+
# where :started, Sequel::CURRENT_DATE <= :start_date
|
81
|
+
# where_all(:active_and_started, :active, :started)
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# Employee.active_and_started.sql
|
85
|
+
# # SELECT * FROM employees WHERE ((active IS TRUE) AND (CURRENT_DATE <= start_date))
|
86
|
+
def where_all(name, *args)
|
87
|
+
_where_any_all(:&, name, args)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Create a method that combines filters from already registered
|
91
|
+
# dataset methods, and filters for rows where any of the conditions
|
92
|
+
# are satisfied.
|
93
|
+
#
|
94
|
+
# Employee.dataset_module do
|
95
|
+
# where :active, active: true
|
96
|
+
# where :started, Sequel::CURRENT_DATE <= :start_date
|
97
|
+
# where_any(:active_or_started, :active, :started)
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# Employee.active_or_started.sql
|
101
|
+
# # SELECT * FROM employees WHERE ((active IS TRUE) OR (CURRENT_DATE <= start_date))
|
102
|
+
def where_any(name, *args)
|
103
|
+
_where_any_all(:|, name, args)
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
if RUBY_VERSION >= '2'
|
109
|
+
# Backbone of #where_any and #where_all
|
110
|
+
def _where_any_all(meth, name, args)
|
111
|
+
ds = model.dataset
|
112
|
+
# #bind used here because the dataset module may not yet be included in the model's dataset
|
113
|
+
where(name, Sequel.send(meth, *args.map{|a| self.instance_method(:"#{a}_conditions").bind(ds).call}))
|
114
|
+
end
|
115
|
+
else
|
116
|
+
# Cannot bind module method to arbitrary objects in Ruby 1.9.
|
117
|
+
# :nocov:
|
118
|
+
def _where_any_all(meth, name, args)
|
119
|
+
ds = model.dataset.clone
|
120
|
+
ds.extend(self)
|
121
|
+
where(name, Sequel.send(meth, *args.map{|a| ds.send(:"#{a}_conditions")}))
|
122
|
+
end
|
123
|
+
# :nocov:
|
124
|
+
end
|
45
125
|
end
|
46
126
|
end
|
47
127
|
end
|