sequel 3.47.0 → 3.48.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 +230 -0
- data/README.rdoc +31 -40
- data/Rakefile +1 -14
- data/doc/active_record.rdoc +29 -29
- data/doc/association_basics.rdoc +4 -13
- data/doc/cheat_sheet.rdoc +8 -6
- data/doc/code_order.rdoc +89 -0
- data/doc/core_extensions.rdoc +3 -3
- data/doc/dataset_basics.rdoc +7 -8
- data/doc/dataset_filtering.rdoc +7 -2
- data/doc/mass_assignment.rdoc +2 -3
- data/doc/migration.rdoc +8 -8
- data/doc/model_hooks.rdoc +11 -7
- data/doc/object_model.rdoc +2 -2
- data/doc/opening_databases.rdoc +5 -14
- data/doc/prepared_statements.rdoc +5 -9
- data/doc/querying.rdoc +23 -28
- data/doc/reflection.rdoc +11 -0
- data/doc/release_notes/3.48.0.txt +477 -0
- data/doc/schema_modification.rdoc +12 -5
- data/doc/security.rdoc +2 -2
- data/doc/sharding.rdoc +1 -2
- data/doc/sql.rdoc +10 -13
- data/doc/testing.rdoc +8 -4
- data/doc/transactions.rdoc +2 -2
- data/doc/validations.rdoc +40 -17
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/adapters/ado.rb +25 -20
- data/lib/sequel/adapters/ado/access.rb +1 -0
- data/lib/sequel/adapters/ado/mssql.rb +1 -0
- data/lib/sequel/adapters/db2.rb +9 -7
- data/lib/sequel/adapters/dbi.rb +16 -16
- data/lib/sequel/adapters/do.rb +17 -18
- data/lib/sequel/adapters/do/mysql.rb +1 -0
- data/lib/sequel/adapters/do/postgres.rb +2 -0
- data/lib/sequel/adapters/do/sqlite.rb +1 -0
- data/lib/sequel/adapters/firebird.rb +5 -7
- data/lib/sequel/adapters/ibmdb.rb +23 -20
- data/lib/sequel/adapters/informix.rb +8 -2
- data/lib/sequel/adapters/jdbc.rb +39 -35
- data/lib/sequel/adapters/jdbc/as400.rb +1 -0
- data/lib/sequel/adapters/jdbc/cubrid.rb +1 -0
- data/lib/sequel/adapters/jdbc/db2.rb +1 -0
- data/lib/sequel/adapters/jdbc/derby.rb +1 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +1 -0
- data/lib/sequel/adapters/jdbc/h2.rb +1 -0
- data/lib/sequel/adapters/jdbc/hsqldb.rb +1 -0
- data/lib/sequel/adapters/jdbc/informix.rb +1 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +1 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +1 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +1 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +1 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +2 -0
- data/lib/sequel/adapters/jdbc/progress.rb +1 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +1 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +1 -0
- data/lib/sequel/adapters/mock.rb +30 -31
- data/lib/sequel/adapters/mysql.rb +6 -7
- data/lib/sequel/adapters/mysql2.rb +5 -6
- data/lib/sequel/adapters/odbc.rb +22 -20
- data/lib/sequel/adapters/odbc/mssql.rb +1 -0
- data/lib/sequel/adapters/openbase.rb +4 -1
- data/lib/sequel/adapters/oracle.rb +10 -8
- data/lib/sequel/adapters/postgres.rb +12 -10
- data/lib/sequel/adapters/shared/access.rb +6 -0
- data/lib/sequel/adapters/shared/cubrid.rb +2 -0
- data/lib/sequel/adapters/shared/db2.rb +2 -0
- data/lib/sequel/adapters/shared/firebird.rb +2 -0
- data/lib/sequel/adapters/shared/informix.rb +2 -0
- data/lib/sequel/adapters/shared/mssql.rb +14 -8
- data/lib/sequel/adapters/shared/mysql.rb +6 -0
- data/lib/sequel/adapters/shared/oracle.rb +2 -0
- data/lib/sequel/adapters/shared/postgres.rb +14 -4
- data/lib/sequel/adapters/shared/progress.rb +1 -0
- data/lib/sequel/adapters/shared/sqlite.rb +4 -3
- data/lib/sequel/adapters/sqlite.rb +6 -7
- data/lib/sequel/adapters/swift.rb +20 -21
- data/lib/sequel/adapters/swift/mysql.rb +1 -0
- data/lib/sequel/adapters/swift/postgres.rb +2 -0
- data/lib/sequel/adapters/swift/sqlite.rb +1 -0
- data/lib/sequel/adapters/tinytds.rb +5 -6
- data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +68 -0
- data/lib/sequel/connection_pool.rb +1 -1
- data/lib/sequel/core.rb +57 -50
- data/lib/sequel/database/connecting.rb +9 -10
- data/lib/sequel/database/dataset.rb +11 -6
- data/lib/sequel/database/dataset_defaults.rb +61 -69
- data/lib/sequel/database/features.rb +21 -0
- data/lib/sequel/database/misc.rb +23 -3
- data/lib/sequel/database/query.rb +13 -7
- data/lib/sequel/database/schema_methods.rb +6 -6
- data/lib/sequel/database/transactions.rb +1 -0
- data/lib/sequel/dataset/actions.rb +51 -38
- data/lib/sequel/dataset/features.rb +1 -0
- data/lib/sequel/dataset/graph.rb +9 -33
- data/lib/sequel/dataset/misc.rb +30 -5
- data/lib/sequel/dataset/mutation.rb +2 -3
- data/lib/sequel/dataset/prepared_statements.rb +1 -1
- data/lib/sequel/dataset/query.rb +91 -27
- data/lib/sequel/dataset/sql.rb +40 -6
- data/lib/sequel/deprecated.rb +74 -0
- data/lib/sequel/deprecated_core_extensions.rb +135 -0
- data/lib/sequel/extensions/columns_introspection.rb +1 -5
- data/lib/sequel/extensions/core_extensions.rb +10 -3
- data/lib/sequel/extensions/date_arithmetic.rb +1 -0
- data/lib/sequel/extensions/empty_array_ignore_nulls.rb +33 -0
- data/lib/sequel/extensions/filter_having.rb +58 -0
- data/lib/sequel/extensions/graph_each.rb +63 -0
- data/lib/sequel/extensions/hash_aliases.rb +44 -0
- data/lib/sequel/extensions/looser_typecasting.rb +14 -3
- data/lib/sequel/extensions/migration.rb +2 -3
- data/lib/sequel/extensions/named_timezones.rb +14 -1
- data/lib/sequel/extensions/null_dataset.rb +7 -1
- data/lib/sequel/extensions/pagination.rb +15 -5
- data/lib/sequel/extensions/pg_auto_parameterize.rb +1 -0
- data/lib/sequel/extensions/pg_hstore_ops.rb +48 -14
- data/lib/sequel/extensions/pg_json.rb +7 -7
- data/lib/sequel/extensions/pg_range_ops.rb +8 -2
- data/lib/sequel/extensions/pg_statement_cache.rb +1 -0
- data/lib/sequel/extensions/pretty_table.rb +13 -4
- data/lib/sequel/extensions/query.rb +21 -4
- data/lib/sequel/extensions/ruby18_symbol_extensions.rb +22 -0
- data/lib/sequel/extensions/schema_caching.rb +10 -7
- data/lib/sequel/extensions/schema_dumper.rb +35 -48
- data/lib/sequel/extensions/select_remove.rb +13 -4
- data/lib/sequel/extensions/sequel_3_dataset_methods.rb +117 -0
- data/lib/sequel/extensions/set_overrides.rb +43 -0
- data/lib/sequel/extensions/to_dot.rb +6 -0
- data/lib/sequel/model.rb +12 -6
- data/lib/sequel/model/associations.rb +80 -38
- data/lib/sequel/model/base.rb +137 -52
- data/lib/sequel/model/errors.rb +7 -2
- data/lib/sequel/plugins/active_model.rb +13 -0
- data/lib/sequel/plugins/after_initialize.rb +43 -0
- data/lib/sequel/plugins/association_proxies.rb +63 -7
- data/lib/sequel/plugins/auto_validations.rb +56 -16
- data/lib/sequel/plugins/blacklist_security.rb +63 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +9 -0
- data/lib/sequel/plugins/constraint_validations.rb +50 -8
- data/lib/sequel/plugins/dataset_associations.rb +2 -0
- data/lib/sequel/plugins/hook_class_methods.rb +7 -1
- data/lib/sequel/plugins/identity_map.rb +4 -0
- data/lib/sequel/plugins/json_serializer.rb +32 -13
- data/lib/sequel/plugins/optimistic_locking.rb +1 -1
- data/lib/sequel/plugins/rcte_tree.rb +4 -4
- data/lib/sequel/plugins/scissors.rb +33 -0
- data/lib/sequel/plugins/serialization.rb +1 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +6 -0
- data/lib/sequel/plugins/tree.rb +5 -1
- data/lib/sequel/plugins/validation_class_methods.rb +2 -1
- data/lib/sequel/plugins/validation_helpers.rb +15 -11
- data/lib/sequel/plugins/xml_serializer.rb +12 -3
- data/lib/sequel/sql.rb +12 -2
- data/lib/sequel/timezones.rb +1 -1
- data/lib/sequel/version.rb +1 -1
- data/lib/sequel_core.rb +1 -0
- data/lib/sequel_model.rb +1 -0
- data/spec/adapters/mssql_spec.rb +24 -57
- data/spec/adapters/postgres_spec.rb +27 -55
- data/spec/adapters/spec_helper.rb +1 -1
- data/spec/adapters/sqlite_spec.rb +1 -1
- data/spec/bin_spec.rb +251 -0
- data/spec/core/database_spec.rb +46 -32
- data/spec/core/dataset_spec.rb +233 -181
- data/spec/core/deprecated_spec.rb +78 -0
- data/spec/core/expression_filters_spec.rb +3 -4
- data/spec/core/mock_adapter_spec.rb +9 -9
- data/spec/core/object_graph_spec.rb +9 -19
- data/spec/core/schema_spec.rb +3 -1
- data/spec/core/spec_helper.rb +19 -0
- data/spec/core_extensions_spec.rb +80 -30
- data/spec/extensions/after_initialize_spec.rb +24 -0
- data/spec/extensions/association_proxies_spec.rb +37 -1
- data/spec/extensions/auto_validations_spec.rb +20 -4
- data/spec/extensions/blacklist_security_spec.rb +87 -0
- data/spec/extensions/boolean_readers_spec.rb +2 -1
- data/spec/extensions/class_table_inheritance_spec.rb +7 -0
- data/spec/extensions/columns_introspection_spec.rb +3 -3
- data/spec/extensions/constraint_validations_plugin_spec.rb +83 -5
- data/spec/extensions/core_refinements_spec.rb +7 -7
- data/spec/extensions/dataset_associations_spec.rb +2 -2
- data/spec/extensions/date_arithmetic_spec.rb +1 -1
- data/spec/extensions/defaults_setter_spec.rb +2 -1
- data/spec/extensions/empty_array_ignore_nulls_spec.rb +24 -0
- data/spec/extensions/filter_having_spec.rb +40 -0
- data/spec/extensions/graph_each_spec.rb +109 -0
- data/spec/extensions/hash_aliases_spec.rb +16 -0
- data/spec/extensions/hook_class_methods_spec.rb +2 -2
- data/spec/extensions/identity_map_spec.rb +3 -3
- data/spec/extensions/json_serializer_spec.rb +19 -19
- data/spec/extensions/lazy_attributes_spec.rb +1 -0
- data/spec/extensions/list_spec.rb +13 -13
- data/spec/extensions/looser_typecasting_spec.rb +10 -3
- data/spec/extensions/many_through_many_spec.rb +1 -1
- data/spec/extensions/migration_spec.rb +7 -7
- data/spec/extensions/named_timezones_spec.rb +6 -0
- data/spec/extensions/nested_attributes_spec.rb +2 -2
- data/spec/extensions/null_dataset_spec.rb +1 -1
- data/spec/extensions/pagination_spec.rb +2 -2
- data/spec/extensions/pg_hstore_ops_spec.rb +75 -0
- data/spec/extensions/pg_range_ops_spec.rb +4 -2
- data/spec/extensions/pg_row_plugin_spec.rb +1 -1
- data/spec/extensions/pretty_table_spec.rb +1 -1
- data/spec/extensions/query_literals_spec.rb +1 -1
- data/spec/extensions/query_spec.rb +3 -3
- data/spec/extensions/schema_caching_spec.rb +3 -3
- data/spec/extensions/schema_dumper_spec.rb +27 -2
- data/spec/extensions/schema_spec.rb +2 -2
- data/spec/extensions/scissors_spec.rb +26 -0
- data/spec/extensions/select_remove_spec.rb +1 -1
- data/spec/extensions/sequel_3_dataset_methods_spec.rb +102 -0
- data/spec/extensions/set_overrides_spec.rb +45 -0
- data/spec/extensions/single_table_inheritance_spec.rb +10 -0
- data/spec/extensions/spec_helper.rb +24 -1
- data/spec/extensions/static_cache_spec.rb +1 -1
- data/spec/extensions/string_stripper_spec.rb +2 -1
- data/spec/extensions/to_dot_spec.rb +1 -1
- data/spec/extensions/typecast_on_load_spec.rb +3 -2
- data/spec/extensions/update_primary_key_spec.rb +2 -2
- data/spec/extensions/validation_class_methods_spec.rb +19 -19
- data/spec/extensions/validation_helpers_spec.rb +30 -21
- data/spec/extensions/xml_serializer_spec.rb +5 -5
- data/spec/integration/associations_test.rb +10 -30
- data/spec/integration/dataset_test.rb +20 -24
- data/spec/integration/eager_loader_test.rb +5 -5
- data/spec/integration/model_test.rb +3 -3
- data/spec/integration/plugin_test.rb +7 -39
- data/spec/integration/schema_test.rb +4 -38
- data/spec/integration/spec_helper.rb +2 -1
- data/spec/model/association_reflection_spec.rb +70 -5
- data/spec/model/associations_spec.rb +11 -11
- data/spec/model/base_spec.rb +25 -8
- data/spec/model/class_dataset_methods_spec.rb +143 -0
- data/spec/model/dataset_methods_spec.rb +1 -1
- data/spec/model/eager_loading_spec.rb +25 -25
- data/spec/model/hooks_spec.rb +1 -1
- data/spec/model/model_spec.rb +22 -7
- data/spec/model/plugins_spec.rb +1 -6
- data/spec/model/record_spec.rb +37 -29
- data/spec/model/spec_helper.rb +23 -1
- data/spec/model/validations_spec.rb +15 -17
- metadata +32 -3
data/lib/sequel/model/errors.rb
CHANGED
|
@@ -10,14 +10,19 @@ module Sequel
|
|
|
10
10
|
# to add new error messages, and +on+ to check existing
|
|
11
11
|
# error messages.
|
|
12
12
|
def [](k)
|
|
13
|
-
has_key?(k)
|
|
13
|
+
if has_key?(k)
|
|
14
|
+
super
|
|
15
|
+
else
|
|
16
|
+
Sequel::Deprecation.deprecate('Model::Errors#[] autovivification', 'Please switch to Model::Errors#add to add errors, and Model::Errors#on to get errors')
|
|
17
|
+
self[k] = []
|
|
18
|
+
end
|
|
14
19
|
end
|
|
15
20
|
|
|
16
21
|
# Adds an error for the given attribute.
|
|
17
22
|
#
|
|
18
23
|
# errors.add(:name, 'is not valid') if name == 'invalid'
|
|
19
24
|
def add(att, msg)
|
|
20
|
-
self[att] << msg
|
|
25
|
+
fetch(att){self[att] = []} << msg
|
|
21
26
|
end
|
|
22
27
|
|
|
23
28
|
# Return the total number of error messages.
|
|
@@ -16,6 +16,14 @@ module Sequel
|
|
|
16
16
|
# # Make the Album class active_model compliant
|
|
17
17
|
# Album.plugin :active_model
|
|
18
18
|
module ActiveModel
|
|
19
|
+
# ActiveModel compliant error class
|
|
20
|
+
class Errors < Sequel::Model::Errors
|
|
21
|
+
# Add autovivification so that #[] always returns an array.
|
|
22
|
+
def [](k)
|
|
23
|
+
fetch(k){self[k] = []}
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
19
27
|
module ClassMethods
|
|
20
28
|
include ::ActiveModel::Naming
|
|
21
29
|
|
|
@@ -70,6 +78,11 @@ module Sequel
|
|
|
70
78
|
end
|
|
71
79
|
|
|
72
80
|
private
|
|
81
|
+
|
|
82
|
+
# Use ActiveModel compliant errors class.
|
|
83
|
+
def errors_class
|
|
84
|
+
Errors
|
|
85
|
+
end
|
|
73
86
|
|
|
74
87
|
# The string to use to join composite primary key param strings.
|
|
75
88
|
def to_param_joiner
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# Adds an after_initialize hook to models, called after initializing
|
|
4
|
+
# both new objects and ones loaded from the database.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
#
|
|
8
|
+
# # Make all model subclasses support the after_initialize hook
|
|
9
|
+
# Sequel::Model.plugin :after_initialize
|
|
10
|
+
#
|
|
11
|
+
# # Make the Album class support the after_initialize hook
|
|
12
|
+
# Album.plugin :after_initialize
|
|
13
|
+
module AfterInitialize
|
|
14
|
+
module ClassMethods
|
|
15
|
+
# Call after_initialize for model objects loaded from the database.
|
|
16
|
+
#def call
|
|
17
|
+
# v = super
|
|
18
|
+
# v.after_initialize
|
|
19
|
+
# v
|
|
20
|
+
#end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
# REMOVE40
|
|
25
|
+
def check_deprecated_after_initialize(meths)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
module InstanceMethods
|
|
30
|
+
# Call after_initialize for new model objects.
|
|
31
|
+
#def initialize(h={})
|
|
32
|
+
# super
|
|
33
|
+
# after_initialize
|
|
34
|
+
#end
|
|
35
|
+
|
|
36
|
+
# An empty after_initialize hook, so that plugins that use this
|
|
37
|
+
# can always call super to get the default behavior.
|
|
38
|
+
#def after_initialize
|
|
39
|
+
#end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -6,6 +6,40 @@ module Sequel
|
|
|
6
6
|
# that will load the association and call a method on the association array if sent
|
|
7
7
|
# an array method, and otherwise send the method to the association's dataset.
|
|
8
8
|
#
|
|
9
|
+
# You can override which methods to forward to the dataset by passing a block to the plugin:
|
|
10
|
+
#
|
|
11
|
+
# plugin :association_proxies do |opts|
|
|
12
|
+
# [:find, :where, :create].include?(opts[:method])
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# If the block returns false or nil, the method is sent to the array of associated
|
|
16
|
+
# objects. Otherwise, the method is sent to the association dataset. Here are the entries
|
|
17
|
+
# in the hash passed to the block:
|
|
18
|
+
#
|
|
19
|
+
# :method :: The name of the method
|
|
20
|
+
# :arguments :: The arguments to the method
|
|
21
|
+
# :block :: The block given to the method
|
|
22
|
+
# :instance :: The model instance related to the call
|
|
23
|
+
# :reflection :: The reflection for the association related to the call
|
|
24
|
+
# :proxy_argument :: The argument given to the association method call
|
|
25
|
+
# :proxy_block :: The block given to the association method call
|
|
26
|
+
#
|
|
27
|
+
# For example, in a call like:
|
|
28
|
+
#
|
|
29
|
+
# artist.albums(1){|ds| ds}.foo(2){|x| 3}
|
|
30
|
+
#
|
|
31
|
+
# The opts passed to the block would be:
|
|
32
|
+
#
|
|
33
|
+
# {
|
|
34
|
+
# :method => :foo,
|
|
35
|
+
# :arguments => [2],
|
|
36
|
+
# :block => {|x| 3},
|
|
37
|
+
# :instance => artist,
|
|
38
|
+
# :reflection => {:name=>:albums, ...},
|
|
39
|
+
# :proxy_argument => 1,
|
|
40
|
+
# :proxy_block => {|ds| ds}
|
|
41
|
+
# }
|
|
42
|
+
#
|
|
9
43
|
# Usage:
|
|
10
44
|
#
|
|
11
45
|
# # Use association proxies in all model subclasses (called before loading subclasses)
|
|
@@ -14,34 +48,56 @@ module Sequel
|
|
|
14
48
|
# # Use association proxies in a specific model subclass
|
|
15
49
|
# Album.plugin :association_proxies
|
|
16
50
|
module AssociationProxies
|
|
51
|
+
def self.configure(model, &block)
|
|
52
|
+
model.instance_eval do
|
|
53
|
+
@association_proxy_to_dataset = block if block
|
|
54
|
+
@association_proxy_to_dataset ||= AssociationProxy::DEFAULT_PROXY_TO_DATASET
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
17
58
|
# A proxy for the association. Calling an array method will load the
|
|
18
59
|
# associated objects and call the method on the associated object array.
|
|
19
60
|
# Calling any other method will call that method on the association's dataset.
|
|
20
61
|
class AssociationProxy < BasicObject
|
|
21
|
-
|
|
22
|
-
|
|
62
|
+
array = []
|
|
63
|
+
|
|
64
|
+
# Default proc used to determine whether to sent the method to the dataset.
|
|
65
|
+
# If the array would respond to it, sends it to the array instead of the dataset.
|
|
66
|
+
DEFAULT_PROXY_TO_DATASET = proc{|opts| !array.respond_to?(opts[:method])}
|
|
23
67
|
|
|
24
68
|
# Set the association reflection to use, and whether the association should be
|
|
25
69
|
# reloaded if an array method is called.
|
|
26
|
-
def initialize(instance, reflection,
|
|
70
|
+
def initialize(instance, reflection, proxy_argument, &proxy_block)
|
|
27
71
|
@instance = instance
|
|
28
72
|
@reflection = reflection
|
|
29
|
-
@
|
|
73
|
+
@proxy_argument = proxy_argument
|
|
74
|
+
@proxy_block = proxy_block
|
|
30
75
|
end
|
|
31
76
|
|
|
32
77
|
# Call the method given on the array of associated objects if the method
|
|
33
78
|
# is an array method, otherwise call the method on the association's dataset.
|
|
34
79
|
def method_missing(meth, *args, &block)
|
|
35
|
-
|
|
36
|
-
send(
|
|
80
|
+
v = if @instance.model.association_proxy_to_dataset.call(:method=>meth, :arguments=>args, :block=>block, :instance=>@instance, :reflection=>@reflection, :proxy_argument=>@proxy_argument, :proxy_block=>@proxy_block)
|
|
81
|
+
@instance.send(@reflection.dataset_method)
|
|
82
|
+
else
|
|
83
|
+
@instance.send(:load_associated_objects, @reflection, @proxy_argument, &@proxy_block)
|
|
84
|
+
end
|
|
85
|
+
v.send(meth, *args, &block)
|
|
37
86
|
end
|
|
38
87
|
end
|
|
39
88
|
|
|
40
89
|
module ClassMethods
|
|
90
|
+
# Proc that accepts a method name, array of arguments, and block and
|
|
91
|
+
# should return a truthy value to send the method to the dataset instead of the
|
|
92
|
+
# array of associated objects.
|
|
93
|
+
attr_reader :association_proxy_to_dataset
|
|
94
|
+
|
|
95
|
+
Plugins.inherited_instance_variables(self, :@association_proxy_to_dataset=>nil)
|
|
96
|
+
|
|
41
97
|
# Changes the association method to return a proxy instead of the associated objects
|
|
42
98
|
# directly.
|
|
43
99
|
def def_association_method(opts)
|
|
44
|
-
opts.returns_array? ? association_module_def(opts.association_method, opts){|*r| AssociationProxy.new(self, opts, r[0])} : super
|
|
100
|
+
opts.returns_array? ? association_module_def(opts.association_method, opts){|*r, &block| AssociationProxy.new(self, opts, r[0], &block)} : super
|
|
45
101
|
end
|
|
46
102
|
end
|
|
47
103
|
end
|
|
@@ -4,10 +4,10 @@ module Sequel
|
|
|
4
4
|
# for your model columns:
|
|
5
5
|
#
|
|
6
6
|
# 1. type validations for all columns
|
|
7
|
-
# 2.
|
|
7
|
+
# 2. not_null validations on NOT NULL columns (optionally, presence validations)
|
|
8
8
|
# 3. unique validations on columns or sets of columns with unique indexes
|
|
9
9
|
#
|
|
10
|
-
# To determine the columns to use for the
|
|
10
|
+
# To determine the columns to use for the not_null validations and the types for the type validations,
|
|
11
11
|
# the plugin looks at the database schema for the model's table. To determine
|
|
12
12
|
# the unique validations, Sequel looks at the indexes on the table. In order
|
|
13
13
|
# for this plugin to be fully functional, the underlying database adapter needs
|
|
@@ -20,13 +20,21 @@ module Sequel
|
|
|
20
20
|
#
|
|
21
21
|
# You can skip certain types of validations from being automatically added via:
|
|
22
22
|
#
|
|
23
|
-
# Model.skip_auto_validations(:
|
|
23
|
+
# Model.skip_auto_validations(:not_null)
|
|
24
24
|
#
|
|
25
25
|
# If you want to skip all auto validations (only useful if loading the plugin
|
|
26
26
|
# in a superclass):
|
|
27
27
|
#
|
|
28
28
|
# Model.skip_auto_validations(:all)
|
|
29
29
|
#
|
|
30
|
+
# By default, the plugin uses a not_null validation for NOT NULL columns, but that
|
|
31
|
+
# can be changed to a presence validation using an option:
|
|
32
|
+
#
|
|
33
|
+
# Model.plugin :auto_validations, :not_null=>:presence
|
|
34
|
+
#
|
|
35
|
+
# This is useful if you want to enforce that NOT NULL string columns do not
|
|
36
|
+
# allow empty values.
|
|
37
|
+
#
|
|
30
38
|
# Usage:
|
|
31
39
|
#
|
|
32
40
|
# # Make all model subclass use auto validations (called before loading subclasses)
|
|
@@ -35,43 +43,61 @@ module Sequel
|
|
|
35
43
|
# # Make the Album class use auto validations
|
|
36
44
|
# Album.plugin :auto_validations
|
|
37
45
|
module AutoValidations
|
|
38
|
-
|
|
39
|
-
def self.apply(model)
|
|
46
|
+
def self.apply(model, opts={})
|
|
40
47
|
model.instance_eval do
|
|
41
48
|
plugin :validation_helpers
|
|
42
|
-
@
|
|
49
|
+
@auto_validate_presence = false
|
|
50
|
+
@auto_validate_not_null_columns = []
|
|
51
|
+
@auto_validate_explicit_not_null_columns = []
|
|
43
52
|
@auto_validate_unique_columns = []
|
|
44
53
|
@auto_validate_types = true
|
|
45
54
|
end
|
|
46
55
|
end
|
|
47
56
|
|
|
48
57
|
# Setup auto validations for the model if it has a dataset.
|
|
49
|
-
def self.configure(model)
|
|
58
|
+
def self.configure(model, opts={})
|
|
50
59
|
model.instance_eval do
|
|
51
60
|
setup_auto_validations if @dataset
|
|
61
|
+
if opts[:not_null] == :presence
|
|
62
|
+
@auto_validate_presence = true
|
|
63
|
+
end
|
|
52
64
|
end
|
|
53
65
|
end
|
|
54
66
|
|
|
55
67
|
module ClassMethods
|
|
56
|
-
# The columns with automatic
|
|
57
|
-
attr_reader :
|
|
68
|
+
# The columns with automatic not_null validations
|
|
69
|
+
attr_reader :auto_validate_not_null_columns
|
|
70
|
+
|
|
71
|
+
# The columns with automatic not_null validations for columns present in the values.
|
|
72
|
+
attr_reader :auto_validate_explicit_not_null_columns
|
|
58
73
|
|
|
59
74
|
# The columns or sets of columns with automatic unique validations
|
|
60
75
|
attr_reader :auto_validate_unique_columns
|
|
61
76
|
|
|
62
|
-
Plugins.inherited_instance_variables(self, :@auto_validate_types=>nil, :@
|
|
77
|
+
Plugins.inherited_instance_variables(self, :@auto_validate_presence=>nil, :@auto_validate_types=>nil, :@auto_validate_not_null_columns=>:dup, :@auto_validate_explicit_not_null_columns=>:dup, :@auto_validate_unique_columns=>:dup)
|
|
63
78
|
Plugins.after_set_dataset(self, :setup_auto_validations)
|
|
64
79
|
|
|
80
|
+
# REMOVE40
|
|
81
|
+
def auto_validate_presence_columns
|
|
82
|
+
Sequel::Deprecation.deprecate('Model.auto_validate_presence_columns', 'Please switch to auto_validate_not_null_columns')
|
|
83
|
+
auto_validate_not_null_columns
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Whether to use a presence validation for not null columns
|
|
87
|
+
def auto_validate_presence?
|
|
88
|
+
@auto_validate_presence
|
|
89
|
+
end
|
|
90
|
+
|
|
65
91
|
# Whether to automatically validate schema types for all columns
|
|
66
92
|
def auto_validate_types?
|
|
67
93
|
@auto_validate_types
|
|
68
94
|
end
|
|
69
95
|
|
|
70
|
-
# Skip automatic validations for the given validation type (:
|
|
96
|
+
# Skip automatic validations for the given validation type (:not_null, :types, :unique).
|
|
71
97
|
# If :all is given as the type, skip all auto validations.
|
|
72
98
|
def skip_auto_validations(type)
|
|
73
99
|
if type == :all
|
|
74
|
-
[:
|
|
100
|
+
[:not_null, :types, :unique].each{|v| skip_auto_validations(v)}
|
|
75
101
|
elsif type == :types
|
|
76
102
|
@auto_validate_types = false
|
|
77
103
|
else
|
|
@@ -83,8 +109,11 @@ module Sequel
|
|
|
83
109
|
|
|
84
110
|
# Parse the database schema and indexes and record the columns to automatically validate.
|
|
85
111
|
def setup_auto_validations
|
|
86
|
-
|
|
87
|
-
@
|
|
112
|
+
not_null_cols, explicit_not_null_cols = db_schema.select{|col, sch| sch[:allow_null] == false}.partition{|col, sch| sch[:ruby_default].nil?}.map{|cs| cs.map{|col, sch| col}}
|
|
113
|
+
@auto_validate_not_null_columns = not_null_cols - Array(primary_key)
|
|
114
|
+
explicit_not_null_cols += Array(primary_key)
|
|
115
|
+
@auto_validate_explicit_not_null_columns = explicit_not_null_cols.uniq
|
|
116
|
+
@auto_validate_unique_columns = if db.supports_index_parsing?
|
|
88
117
|
db.indexes(dataset.first_source_table).select{|name, idx| idx[:unique] == true}.map{|name, idx| idx[:columns]}
|
|
89
118
|
else
|
|
90
119
|
[]
|
|
@@ -96,8 +125,19 @@ module Sequel
|
|
|
96
125
|
# Validate the model's auto validations columns
|
|
97
126
|
def validate
|
|
98
127
|
super
|
|
99
|
-
|
|
100
|
-
|
|
128
|
+
unless (not_null_columns = model.auto_validate_not_null_columns).empty?
|
|
129
|
+
if model.auto_validate_presence?
|
|
130
|
+
validates_presence(not_null_columns)
|
|
131
|
+
else
|
|
132
|
+
validates_not_null(not_null_columns)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
unless (not_null_columns = model.auto_validate_explicit_not_null_columns).empty?
|
|
136
|
+
if model.auto_validate_presence?
|
|
137
|
+
validates_presence(not_null_columns, :allow_missing=>true)
|
|
138
|
+
else
|
|
139
|
+
validates_not_null(not_null_columns, :allow_missing=>true)
|
|
140
|
+
end
|
|
101
141
|
end
|
|
102
142
|
|
|
103
143
|
validates_schema_types if model.auto_validate_types?
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# The blacklist_security plugin contains blacklist-based support for
|
|
4
|
+
# mass assignment, specifying which columns to not allow mass assignment for,
|
|
5
|
+
# implicitly allowing mass assignment for columns not listed. This is only
|
|
6
|
+
# for backwards compatibility, it should not be used by new code.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
#
|
|
10
|
+
# # Make all model subclasses support the blacklist security features.
|
|
11
|
+
# Sequel::Model.plugin :blacklist_security
|
|
12
|
+
#
|
|
13
|
+
# # Make the Album class support the blacklist security features.
|
|
14
|
+
# Album.plugin :blacklist_security
|
|
15
|
+
module BlacklistSecurity
|
|
16
|
+
module ClassMethods
|
|
17
|
+
# Which columns are specifically restricted in a call to set/update/new/etc.
|
|
18
|
+
# (default: not set). Some columns are restricted regardless of
|
|
19
|
+
# this setting, such as the primary key column and columns in Model::RESTRICTED_SETTER_METHODS.
|
|
20
|
+
attr_reader :restricted_columns
|
|
21
|
+
|
|
22
|
+
# Set the columns to restrict when using mass assignment (e.g. +set+). Using this means that
|
|
23
|
+
# attempts to call setter methods for the columns listed here will cause an
|
|
24
|
+
# exception or be silently skipped (based on the +strict_param_setting+ setting).
|
|
25
|
+
# If you have any virtual setter methods (methods that end in =) that you
|
|
26
|
+
# want not to be used during mass assignment, they need to be listed here as well (without the =).
|
|
27
|
+
#
|
|
28
|
+
# It's generally a bad idea to rely on a blacklist approach for security. Using a whitelist
|
|
29
|
+
# approach such as set_allowed_columns or the instance level set_only or set_fields methods
|
|
30
|
+
# is usually a better choice. So use of this method is generally a bad idea.
|
|
31
|
+
#
|
|
32
|
+
# Artist.set_restricted_columns(:records_sold)
|
|
33
|
+
# Artist.set(:name=>'Bob', :hometown=>'Sactown') # No Error
|
|
34
|
+
# Artist.set(:name=>'Bob', :records_sold=>30000) # Error
|
|
35
|
+
def set_restricted_columns(*cols)
|
|
36
|
+
clear_setter_methods_cache
|
|
37
|
+
@restricted_columns = cols
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
module InstanceMethods
|
|
42
|
+
# Set all values using the entries in the hash, except for the keys
|
|
43
|
+
# given in except. You should probably use +set_fields+ or +set_only+
|
|
44
|
+
# instead of this method, as blacklist approaches to security are a bad idea.
|
|
45
|
+
#
|
|
46
|
+
# artist.set_except({:name=>'Jim'}, :hometown)
|
|
47
|
+
# artist.name # => 'Jim'
|
|
48
|
+
def set_except(hash, *except)
|
|
49
|
+
set_restricted(hash, false, except.flatten)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Update all values using the entries in the hash, except for the keys
|
|
53
|
+
# given in except. You should probably use +update_fields+ or +update_only+
|
|
54
|
+
# instead of this method, as blacklist approaches to security are a bad idea.
|
|
55
|
+
#
|
|
56
|
+
# artist.update_except({:name=>'Jim'}, :hometown) # UPDATE artists SET name = 'Jim' WHERE (id = 1)
|
|
57
|
+
def update_except(hash, *except)
|
|
58
|
+
update_restricted(hash, false, except.flatten)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -175,6 +175,15 @@ module Sequel
|
|
|
175
175
|
def table_name
|
|
176
176
|
self == cti_base_model ? super : cti_tables.last
|
|
177
177
|
end
|
|
178
|
+
|
|
179
|
+
private
|
|
180
|
+
|
|
181
|
+
# If calling set_dataset manually, make sure to set the dataset
|
|
182
|
+
# row proc to one that handles inheritance correctly.
|
|
183
|
+
def set_dataset_row_proc(ds)
|
|
184
|
+
ds.row_proc = @dataset.row_proc if @dataset
|
|
185
|
+
end
|
|
186
|
+
|
|
178
187
|
end
|
|
179
188
|
|
|
180
189
|
module InstanceMethods
|
|
@@ -33,7 +33,11 @@ module Sequel
|
|
|
33
33
|
|
|
34
34
|
# Automatically load the validation_helpers plugin to run the actual validations.
|
|
35
35
|
def self.apply(model, opts={})
|
|
36
|
-
model.
|
|
36
|
+
model.instance_eval do
|
|
37
|
+
plugin :validation_helpers
|
|
38
|
+
@constraint_validations_table = DEFAULT_CONSTRAINT_VALIDATIONS_TABLE
|
|
39
|
+
@constraint_validation_options = {}
|
|
40
|
+
end
|
|
37
41
|
end
|
|
38
42
|
|
|
39
43
|
# Parse the constraint validations metadata from the database. Options:
|
|
@@ -41,9 +45,25 @@ module Sequel
|
|
|
41
45
|
# metadata table. Should only be used if the table
|
|
42
46
|
# name was overridden when creating the constraint
|
|
43
47
|
# validations.
|
|
48
|
+
# :validation_options :: Override/augment the options stored in the database with the
|
|
49
|
+
# given options. Keys should be validation type symbols (e.g.
|
|
50
|
+
# :presence) and values should be hashes of options specific
|
|
51
|
+
# to that validation type.
|
|
44
52
|
def self.configure(model, opts={})
|
|
45
|
-
model.
|
|
46
|
-
|
|
53
|
+
model.instance_eval do
|
|
54
|
+
if table = opts[:constraint_validations_table]
|
|
55
|
+
@constraint_validations_table = table
|
|
56
|
+
end
|
|
57
|
+
if vos = opts[:validation_options]
|
|
58
|
+
vos.each do |k, v|
|
|
59
|
+
if existing_options = @constraint_validation_options[k]
|
|
60
|
+
v = existing_options.merge(v)
|
|
61
|
+
end
|
|
62
|
+
@constraint_validation_options[k] = v
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
parse_constraint_validations
|
|
66
|
+
end
|
|
47
67
|
end
|
|
48
68
|
|
|
49
69
|
module DatabaseMethods
|
|
@@ -58,10 +78,17 @@ module Sequel
|
|
|
58
78
|
# is splatted to send to perform a validation via validation_helpers.
|
|
59
79
|
attr_reader :constraint_validations
|
|
60
80
|
|
|
81
|
+
# A hash of reflections of constraint validations. Keys are type name
|
|
82
|
+
# symbols. Each value is an array of pairs, with the first element being
|
|
83
|
+
# the validation type symbol (e.g. :presence) and the second element being
|
|
84
|
+
# options for the validation. If the validation takes an argument, it appears
|
|
85
|
+
# as the :argument entry in the validation option hash.
|
|
86
|
+
attr_reader :constraint_validation_reflections
|
|
87
|
+
|
|
61
88
|
# The name of the table containing the constraint validations metadata.
|
|
62
89
|
attr_reader :constraint_validations_table
|
|
63
90
|
|
|
64
|
-
Plugins.inherited_instance_variables(self, :@constraint_validations_table=>nil)
|
|
91
|
+
Plugins.inherited_instance_variables(self, :@constraint_validations_table=>nil, :@constraint_validation_options=>:hash_dup)
|
|
65
92
|
Plugins.after_set_dataset(self, :parse_constraint_validations)
|
|
66
93
|
|
|
67
94
|
private
|
|
@@ -78,7 +105,7 @@ module Sequel
|
|
|
78
105
|
unless hash = Sequel.synchronize{db.constraint_validations}
|
|
79
106
|
hash = {}
|
|
80
107
|
db.from(constraint_validations_table).each do |r|
|
|
81
|
-
(hash[r[:table]] ||= []) <<
|
|
108
|
+
(hash[r[:table]] ||= []) << r
|
|
82
109
|
end
|
|
83
110
|
Sequel.synchronize{db.constraint_validations = hash}
|
|
84
111
|
end
|
|
@@ -86,14 +113,16 @@ module Sequel
|
|
|
86
113
|
if @dataset
|
|
87
114
|
ds = @dataset.clone
|
|
88
115
|
ds.quote_identifiers = false
|
|
89
|
-
table_name = ds.literal(
|
|
90
|
-
|
|
116
|
+
table_name = ds.literal(ds.first_source_table)
|
|
117
|
+
reflections = {}
|
|
118
|
+
@constraint_validations = (Sequel.synchronize{hash[table_name]} || []).map{|r| constraint_validation_array(r, reflections)}
|
|
119
|
+
@constraint_validation_reflections = reflections
|
|
91
120
|
end
|
|
92
121
|
end
|
|
93
122
|
|
|
94
123
|
# Given a specific database constraint validation metadata row hash, transform
|
|
95
124
|
# it in an validation method call array suitable for splatting to send.
|
|
96
|
-
def constraint_validation_array(r)
|
|
125
|
+
def constraint_validation_array(r, reflections)
|
|
97
126
|
opts = {}
|
|
98
127
|
opts[:message] = r[:message] if r[:message]
|
|
99
128
|
opts[:allow_nil] = true if db.typecast_value(:boolean, r[:allow_nil])
|
|
@@ -131,14 +160,27 @@ module Sequel
|
|
|
131
160
|
column.to_sym
|
|
132
161
|
end
|
|
133
162
|
|
|
163
|
+
if type_opts = @constraint_validation_options[type]
|
|
164
|
+
opts = opts.merge(type_opts)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
reflection_opts = opts
|
|
134
168
|
a = [:"validates_#{type}"]
|
|
169
|
+
|
|
135
170
|
if arg
|
|
136
171
|
a << arg
|
|
172
|
+
reflection_opts = reflection_opts.merge(:argument=>arg)
|
|
137
173
|
end
|
|
138
174
|
a << column
|
|
139
175
|
unless opts.empty?
|
|
140
176
|
a << opts
|
|
141
177
|
end
|
|
178
|
+
|
|
179
|
+
if column.is_a?(Array) && column.length == 1
|
|
180
|
+
column = column.first
|
|
181
|
+
end
|
|
182
|
+
(reflections[column] ||= []) << [type, reflection_opts]
|
|
183
|
+
|
|
142
184
|
a
|
|
143
185
|
end
|
|
144
186
|
|