sequel 4.11.0 → 4.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +32 -0
- data/Rakefile +1 -5
- data/doc/opening_databases.rdoc +5 -1
- data/doc/release_notes/4.12.0.txt +105 -0
- data/lib/sequel/adapters/jdbc.rb +1 -0
- data/lib/sequel/adapters/oracle.rb +1 -0
- data/lib/sequel/adapters/postgres.rb +17 -8
- data/lib/sequel/adapters/shared/cubrid.rb +2 -1
- data/lib/sequel/adapters/shared/db2.rb +1 -0
- data/lib/sequel/adapters/shared/mssql.rb +1 -0
- data/lib/sequel/adapters/shared/postgres.rb +23 -2
- data/lib/sequel/adapters/shared/sqlanywhere.rb +1 -0
- data/lib/sequel/adapters/sqlite.rb +11 -5
- data/lib/sequel/database/query.rb +14 -1
- data/lib/sequel/dataset/prepared_statements.rb +2 -1
- data/lib/sequel/dataset/query.rb +48 -37
- data/lib/sequel/dataset/sql.rb +0 -39
- data/lib/sequel/extensions/pg_interval.rb +1 -1
- data/lib/sequel/extensions/pg_static_cache_updater.rb +11 -5
- data/lib/sequel/model/associations.rb +2 -2
- data/lib/sequel/plugins/auto_validations.rb +16 -4
- data/lib/sequel/plugins/hook_class_methods.rb +1 -1
- data/lib/sequel/plugins/nested_attributes.rb +72 -59
- data/lib/sequel/plugins/prepared_statements.rb +16 -5
- data/lib/sequel/plugins/prepared_statements_associations.rb +14 -0
- data/lib/sequel/sql.rb +2 -43
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +49 -20
- data/spec/bin_spec.rb +2 -2
- data/spec/core/dataset_spec.rb +18 -6
- data/spec/core/schema_spec.rb +2 -1
- data/spec/extensions/auto_validations_spec.rb +23 -2
- data/spec/extensions/nested_attributes_spec.rb +32 -1
- data/spec/extensions/pg_static_cache_updater_spec.rb +12 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +17 -17
- data/spec/extensions/prepared_statements_spec.rb +11 -8
- data/spec/integration/plugin_test.rb +43 -0
- data/spec/integration/schema_test.rb +7 -0
- data/spec/model/eager_loading_spec.rb +9 -0
- metadata +4 -2
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -525,11 +525,6 @@ module Sequel
|
|
525
525
|
end
|
526
526
|
end
|
527
527
|
|
528
|
-
# REMOVE411
|
529
|
-
def emulated_function_sql_append(sql, f)
|
530
|
-
_function_sql_append(sql, native_function_name(f.f), f.args)
|
531
|
-
end
|
532
|
-
|
533
528
|
# Append literalization of function call to SQL string.
|
534
529
|
def function_sql_append(sql, f)
|
535
530
|
name = f.name
|
@@ -827,14 +822,6 @@ module Sequel
|
|
827
822
|
sql << PAREN_CLOSE
|
828
823
|
end
|
829
824
|
|
830
|
-
# REMOVE411
|
831
|
-
def window_function_sql_append(sql, function, window)
|
832
|
-
Deprecation.deprecate("Dataset#window_function_sql_append", "Please use Sequel::SQL::Function.new(name, *args).over(...) to create an SQL window function")
|
833
|
-
literal_append(sql, function)
|
834
|
-
sql << OVER
|
835
|
-
literal_append(sql, window)
|
836
|
-
end
|
837
|
-
|
838
825
|
protected
|
839
826
|
|
840
827
|
# Return a from_self dataset if an order or limit is specified, so it works as expected
|
@@ -845,32 +832,6 @@ module Sequel
|
|
845
832
|
|
846
833
|
private
|
847
834
|
|
848
|
-
# REMOVE411
|
849
|
-
def _function_sql_append(sql, name, args)
|
850
|
-
Deprecation.deprecate("Dataset#emulated_function_sql_append and #_function_sql_append", "Please use Sequel::SQL::Function.new!(name, args, :emulate=>true) to create an emulated SQL function")
|
851
|
-
case name
|
852
|
-
when SQL::Identifier
|
853
|
-
if supports_quoted_function_names?
|
854
|
-
literal_append(sql, name)
|
855
|
-
else
|
856
|
-
sql << name.value.to_s
|
857
|
-
end
|
858
|
-
when SQL::QualifiedIdentifier
|
859
|
-
if supports_quoted_function_names?
|
860
|
-
literal_append(sql, name)
|
861
|
-
else
|
862
|
-
sql << split_qualifiers(name).join(DOT)
|
863
|
-
end
|
864
|
-
else
|
865
|
-
sql << name.to_s
|
866
|
-
end
|
867
|
-
if args.empty?
|
868
|
-
sql << FUNCTION_EMPTY
|
869
|
-
else
|
870
|
-
literal_append(sql, args)
|
871
|
-
end
|
872
|
-
end
|
873
|
-
|
874
835
|
# Formats the truncate statement. Assumes the table given has already been
|
875
836
|
# literalized.
|
876
837
|
def _truncate_sql(table)
|
@@ -67,7 +67,7 @@ module Sequel
|
|
67
67
|
# Creates callable objects that convert strings into ActiveSupport::Duration instances.
|
68
68
|
class Parser
|
69
69
|
# Regexp that parses the full range of PostgreSQL interval type output.
|
70
|
-
PARSER = /\A([+-]?\d+ years?\s?)?([+-]?\d+ mons?\s?)?([+-]?\d+ days?\s?)?(?:(?:([+-])?(\d
|
70
|
+
PARSER = /\A([+-]?\d+ years?\s?)?([+-]?\d+ mons?\s?)?([+-]?\d+ days?\s?)?(?:(?:([+-])?(\d{2,10}):(\d\d):(\d\d(\.\d+)?))|([+-]?\d+ hours?\s?)?([+-]?\d+ mins?\s?)?([+-]?\d+(\.\d+)? secs?\s?)?)?\z/o
|
71
71
|
|
72
72
|
# Parse the interval input string into an ActiveSupport::Duration instance.
|
73
73
|
def call(string)
|
@@ -98,15 +98,17 @@ SQL
|
|
98
98
|
end
|
99
99
|
|
100
100
|
# Listen on the notification channel for changes to any of tables for
|
101
|
-
# the models given. If notified about a change to one of the tables,
|
101
|
+
# the models given in a new thread. If notified about a change to one of the tables,
|
102
102
|
# reload the cache for the related model. Options given are also
|
103
103
|
# passed to Database#listen.
|
104
104
|
#
|
105
|
-
# Note that this implementation does not currently support
|
105
|
+
# Note that this implementation does not currently support multiple
|
106
106
|
# models that use the same underlying table.
|
107
107
|
#
|
108
108
|
# Options:
|
109
109
|
# :channel_name :: Override the channel name to use.
|
110
|
+
# :before_thread_exit :: An object that responds to +call+ that is called before the
|
111
|
+
# the created thread exits.
|
110
112
|
def listen_for_static_cache_updates(models, opts=OPTS)
|
111
113
|
raise Error, "this database object does not respond to listen, use the postgres adapter with the pg driver" unless respond_to?(:listen)
|
112
114
|
models = [models] unless models.is_a?(Array)
|
@@ -119,10 +121,14 @@ SQL
|
|
119
121
|
end
|
120
122
|
|
121
123
|
Thread.new do
|
122
|
-
|
123
|
-
|
124
|
-
model.
|
124
|
+
begin
|
125
|
+
listen(opts[:channel_name]||default_static_cache_update_name, {:loop=>true}.merge(opts)) do |_, _, oid|
|
126
|
+
if model = oid_map[oid.to_i]
|
127
|
+
model.send(:load_cache)
|
128
|
+
end
|
125
129
|
end
|
130
|
+
ensure
|
131
|
+
opts[:before_thread_exit].call if opts[:before_thread_exit]
|
126
132
|
end
|
127
133
|
end
|
128
134
|
end
|
@@ -248,7 +248,6 @@ module Sequel
|
|
248
248
|
elsif strategy == :union
|
249
249
|
objects = []
|
250
250
|
ds = associated_dataset
|
251
|
-
ds = self[:eager_block].call(ds) if self[:eager_block]
|
252
251
|
loader = union_eager_loader
|
253
252
|
joiner = " UNION ALL "
|
254
253
|
eo[:id_map].keys.each_slice(subqueries_per_union).each do |slice|
|
@@ -695,6 +694,7 @@ module Sequel
|
|
695
694
|
def union_eager_loader
|
696
695
|
cached_fetch(:union_eager_loader) do
|
697
696
|
Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
|
697
|
+
ds = self[:eager_block].call(ds) if self[:eager_block]
|
698
698
|
keys = predicate_keys
|
699
699
|
ds = ds.where(keys.map{pl.arg}.zip(keys))
|
700
700
|
if eager_loading_use_associated_key?
|
@@ -1206,7 +1206,7 @@ module Sequel
|
|
1206
1206
|
|
1207
1207
|
# The columns to select when loading the association, associated_class.table_name.* by default.
|
1208
1208
|
def select
|
1209
|
-
|
1209
|
+
cached_fetch(:select){Sequel::SQL::ColumnAll.new(associated_class.table_name)}
|
1210
1210
|
end
|
1211
1211
|
|
1212
1212
|
private
|
@@ -1,13 +1,14 @@
|
|
1
1
|
module Sequel
|
2
2
|
module Plugins
|
3
|
-
# The auto_validations plugin automatically sets up
|
3
|
+
# The auto_validations plugin automatically sets up the following types of validations
|
4
4
|
# for your model columns:
|
5
5
|
#
|
6
6
|
# 1. type validations for all columns
|
7
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
|
+
# 4. max length validations on string columns
|
9
10
|
#
|
10
|
-
# To determine the columns to use for the not_null validations
|
11
|
+
# To determine the columns to use for the type/not_null/max_length validations,
|
11
12
|
# the plugin looks at the database schema for the model's table. To determine
|
12
13
|
# the unique validations, Sequel looks at the indexes on the table. In order
|
13
14
|
# for this plugin to be fully functional, the underlying database adapter needs
|
@@ -49,6 +50,7 @@ module Sequel
|
|
49
50
|
@auto_validate_presence = false
|
50
51
|
@auto_validate_not_null_columns = []
|
51
52
|
@auto_validate_explicit_not_null_columns = []
|
53
|
+
@auto_validate_max_length_columns = []
|
52
54
|
@auto_validate_unique_columns = []
|
53
55
|
@auto_validate_types = true
|
54
56
|
end
|
@@ -71,10 +73,14 @@ module Sequel
|
|
71
73
|
# The columns with automatic not_null validations for columns present in the values.
|
72
74
|
attr_reader :auto_validate_explicit_not_null_columns
|
73
75
|
|
76
|
+
# The columns or sets of columns with automatic max_length validations, as an array of
|
77
|
+
# pairs, with the first entry being the column name and second entry being the maximum length.
|
78
|
+
attr_reader :auto_validate_max_length_columns
|
79
|
+
|
74
80
|
# The columns or sets of columns with automatic unique validations
|
75
81
|
attr_reader :auto_validate_unique_columns
|
76
82
|
|
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)
|
83
|
+
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_max_length_columns=>:dup, :@auto_validate_unique_columns=>:dup)
|
78
84
|
Plugins.after_set_dataset(self, :setup_auto_validations)
|
79
85
|
|
80
86
|
# Whether to use a presence validation for not null columns
|
@@ -91,7 +97,7 @@ module Sequel
|
|
91
97
|
# If :all is given as the type, skip all auto validations.
|
92
98
|
def skip_auto_validations(type)
|
93
99
|
if type == :all
|
94
|
-
[:not_null, :types, :unique].each{|v| skip_auto_validations(v)}
|
100
|
+
[:not_null, :types, :unique, :max_length].each{|v| skip_auto_validations(v)}
|
95
101
|
elsif type == :types
|
96
102
|
@auto_validate_types = false
|
97
103
|
else
|
@@ -107,6 +113,7 @@ module Sequel
|
|
107
113
|
@auto_validate_not_null_columns = not_null_cols - Array(primary_key)
|
108
114
|
explicit_not_null_cols += Array(primary_key)
|
109
115
|
@auto_validate_explicit_not_null_columns = explicit_not_null_cols.uniq
|
116
|
+
@auto_validate_max_length_columns = db_schema.select{|col, sch| sch[:type] == :string && sch[:max_length].is_a?(Integer)}.map{|col, sch| [col, sch[:max_length]]}
|
110
117
|
table = dataset.first_source_table
|
111
118
|
@auto_validate_unique_columns = if db.supports_index_parsing? && [Symbol, SQL::QualifiedIdentifier, SQL::Identifier, String].any?{|c| table.is_a?(c)}
|
112
119
|
db.indexes(table).select{|name, idx| idx[:unique] == true}.map{|name, idx| idx[:columns]}
|
@@ -134,6 +141,11 @@ module Sequel
|
|
134
141
|
validates_not_null(not_null_columns, :allow_missing=>true)
|
135
142
|
end
|
136
143
|
end
|
144
|
+
unless (max_length_columns = model.auto_validate_max_length_columns).empty?
|
145
|
+
max_length_columns.each do |col, len|
|
146
|
+
validates_max_length(len, col, :allow_nil=>true)
|
147
|
+
end
|
148
|
+
end
|
137
149
|
|
138
150
|
validates_schema_types if model.auto_validate_types?
|
139
151
|
|
@@ -82,29 +82,27 @@ module Sequel
|
|
82
82
|
attr_accessor :nested_attributes_module
|
83
83
|
|
84
84
|
# Allow nested attributes to be set for the given associations. Options:
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
99
|
-
#
|
100
|
-
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
# key, :ignore to ignore the record, or :raise to raise an error.
|
107
|
-
# The default is :raise.
|
85
|
+
# :destroy :: Allow destruction of nested records.
|
86
|
+
# :fields :: If provided, should be an Array or proc. If it is an array,
|
87
|
+
# restricts the fields allowed to be modified through the
|
88
|
+
# association_attributes= method to the specific fields given. If it is
|
89
|
+
# a proc, it will be called with the associated object and should return an
|
90
|
+
# array of the allowable fields.
|
91
|
+
# :limit :: For *_to_many associations, a limit on the number of records
|
92
|
+
# that will be processed, to prevent denial of service attacks.
|
93
|
+
# :reject_if :: A proc that is given each attribute hash before it is
|
94
|
+
# passed to its associated object. If the proc returns a truthy
|
95
|
+
# value, the attribute hash is ignored.
|
96
|
+
# :remove :: Allow disassociation of nested records (can remove the associated
|
97
|
+
# object from the parent object, but not destroy the associated object).
|
98
|
+
# :transform :: A proc to transform attribute hashes before they are
|
99
|
+
# passed to associated object. Takes two arguments, the parent object and
|
100
|
+
# the attribute hash. Uses the return value as the new attribute hash.
|
101
|
+
# :unmatched_pk :: Specify the action to be taken if a primary key is
|
102
|
+
# provided in a record, but it doesn't match an existing associated
|
103
|
+
# object. Set to :create to create a new object with that primary
|
104
|
+
# key, :ignore to ignore the record, or :raise to raise an error.
|
105
|
+
# The default is :raise.
|
108
106
|
#
|
109
107
|
# If a block is provided, it is used to set the :reject_if option.
|
110
108
|
def nested_attributes(*associations, &block)
|
@@ -125,25 +123,35 @@ module Sequel
|
|
125
123
|
# class.
|
126
124
|
def def_nested_attribute_method(reflection)
|
127
125
|
nested_attributes_module.class_eval do
|
128
|
-
|
129
|
-
|
130
|
-
nested_attributes_list_setter(reflection, array)
|
131
|
-
end
|
132
|
-
else
|
133
|
-
define_method("#{reflection[:name]}_attributes=") do |h|
|
134
|
-
nested_attributes_setter(reflection, h)
|
135
|
-
end
|
126
|
+
define_method("#{reflection[:name]}_attributes=") do |v|
|
127
|
+
set_nested_attributes(reflection[:name], v)
|
136
128
|
end
|
137
129
|
end
|
138
130
|
end
|
139
131
|
end
|
140
132
|
|
141
133
|
module InstanceMethods
|
134
|
+
# Set the nested attributes for the given association. obj should be an enumerable of multiple objects
|
135
|
+
# for plural associations. The opts hash can be used to override any of the default options set by
|
136
|
+
# the class-level nested_attributes call.
|
137
|
+
def set_nested_attributes(assoc, obj, opts=OPTS)
|
138
|
+
raise(Error, "no association named #{assoc} for #{model.inspect}") unless ref = model.association_reflection(assoc)
|
139
|
+
raise(Error, "nested attributes are not enabled for association #{assoc} for #{model.inspect}") unless meta = ref[:nested_attributes]
|
140
|
+
meta = meta.merge(opts)
|
141
|
+
meta[:reflection] = ref
|
142
|
+
if ref.returns_array?
|
143
|
+
nested_attributes_list_setter(meta, obj)
|
144
|
+
else
|
145
|
+
nested_attributes_setter(meta, obj)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
142
149
|
private
|
143
150
|
|
144
151
|
# Check that the keys related to the association are not modified inside the block. Does
|
145
152
|
# not use an ensure block, so callers should be careful.
|
146
|
-
def nested_attributes_check_key_modifications(
|
153
|
+
def nested_attributes_check_key_modifications(meta, obj)
|
154
|
+
reflection = meta[:reflection]
|
147
155
|
keys = reflection.associated_object_keys.map{|x| obj.send(x)}
|
148
156
|
yield
|
149
157
|
unless keys == reflection.associated_object_keys.map{|x| obj.send(x)}
|
@@ -154,10 +162,11 @@ module Sequel
|
|
154
162
|
# Create a new associated object with the given attributes, validate
|
155
163
|
# it when the parent is validated, and save it when the object is saved.
|
156
164
|
# Returns the object created.
|
157
|
-
def nested_attributes_create(
|
165
|
+
def nested_attributes_create(meta, attributes)
|
166
|
+
reflection = meta[:reflection]
|
158
167
|
obj = reflection.associated_class.new
|
159
|
-
nested_attributes_set_attributes(
|
160
|
-
after_validation_hook{validate_associated_object(
|
168
|
+
nested_attributes_set_attributes(meta, obj, attributes)
|
169
|
+
after_validation_hook{validate_associated_object(meta, obj)}
|
161
170
|
if reflection.returns_array?
|
162
171
|
send(reflection[:name]) << obj
|
163
172
|
after_save_hook{send(reflection.add_method, obj)}
|
@@ -177,6 +186,7 @@ module Sequel
|
|
177
186
|
after_save_hook{send(reflection.setter_method, obj)}
|
178
187
|
end
|
179
188
|
end
|
189
|
+
add_reciprocal_object(reflection, obj)
|
180
190
|
obj
|
181
191
|
end
|
182
192
|
|
@@ -184,19 +194,20 @@ module Sequel
|
|
184
194
|
# If a hash is provided it, sort it by key and then use the values.
|
185
195
|
# If there is a limit on the nested attributes for this association,
|
186
196
|
# make sure the length of the attributes_list is not greater than the limit.
|
187
|
-
def nested_attributes_list_setter(
|
197
|
+
def nested_attributes_list_setter(meta, attributes_list)
|
188
198
|
attributes_list = attributes_list.sort_by{|x| x.to_s}.map{|k,v| v} if attributes_list.is_a?(Hash)
|
189
|
-
if (limit =
|
199
|
+
if (limit = meta[:limit]) && attributes_list.length > limit
|
190
200
|
raise(Error, "number of nested attributes (#{attributes_list.length}) exceeds the limit (#{limit})")
|
191
201
|
end
|
192
|
-
attributes_list.each{|a| nested_attributes_setter(
|
202
|
+
attributes_list.each{|a| nested_attributes_setter(meta, a)}
|
193
203
|
end
|
194
204
|
|
195
205
|
# Remove the given associated object from the current object. If the
|
196
206
|
# :destroy option is given, destroy the object after disassociating it
|
197
207
|
# (unless destroying the object would automatically disassociate it).
|
198
208
|
# Returns the object removed.
|
199
|
-
def nested_attributes_remove(
|
209
|
+
def nested_attributes_remove(meta, obj, opts=OPTS)
|
210
|
+
reflection = meta[:reflection]
|
200
211
|
if !opts[:destroy] || reflection.remove_before_destroy?
|
201
212
|
before_save_hook do
|
202
213
|
if reflection.returns_array?
|
@@ -212,8 +223,8 @@ module Sequel
|
|
212
223
|
|
213
224
|
# Set the fields in the obj based on the association, only allowing
|
214
225
|
# specific :fields if configured.
|
215
|
-
def nested_attributes_set_attributes(
|
216
|
-
if fields =
|
226
|
+
def nested_attributes_set_attributes(meta, obj, attributes)
|
227
|
+
if fields = meta[:fields]
|
217
228
|
fields = fields.call(obj) if fields.respond_to?(:call)
|
218
229
|
obj.set_only(attributes, fields)
|
219
230
|
else
|
@@ -231,12 +242,13 @@ module Sequel
|
|
231
242
|
# * If a primary key exists in the attributes hash but it does not match an associated object,
|
232
243
|
# either raise an error, create a new object or ignore the hash, depending on the :unmatched_pk option.
|
233
244
|
# * If no primary key exists in the attributes hash, create a new object.
|
234
|
-
def nested_attributes_setter(
|
235
|
-
if a =
|
245
|
+
def nested_attributes_setter(meta, attributes)
|
246
|
+
if a = meta[:transform]
|
236
247
|
attributes = a.call(self, attributes)
|
237
248
|
end
|
238
|
-
return if (b =
|
249
|
+
return if (b = meta[:reject_if]) && b.call(attributes)
|
239
250
|
modified!
|
251
|
+
reflection = meta[:reflection]
|
240
252
|
klass = reflection.associated_class
|
241
253
|
sym_keys = Array(klass.primary_key)
|
242
254
|
str_keys = sym_keys.map{|k| k.to_s}
|
@@ -246,28 +258,28 @@ module Sequel
|
|
246
258
|
end
|
247
259
|
if obj
|
248
260
|
attributes = attributes.dup.delete_if{|k,v| str_keys.include? k.to_s}
|
249
|
-
if
|
250
|
-
nested_attributes_remove(
|
251
|
-
elsif
|
252
|
-
nested_attributes_remove(
|
261
|
+
if meta[:destroy] && klass.db.send(:typecast_value_boolean, attributes.delete(:_delete) || attributes.delete('_delete'))
|
262
|
+
nested_attributes_remove(meta, obj, :destroy=>true)
|
263
|
+
elsif meta[:remove] && klass.db.send(:typecast_value_boolean, attributes.delete(:_remove) || attributes.delete('_remove'))
|
264
|
+
nested_attributes_remove(meta, obj)
|
253
265
|
else
|
254
|
-
nested_attributes_update(
|
266
|
+
nested_attributes_update(meta, obj, attributes)
|
255
267
|
end
|
256
|
-
elsif pk.all? &&
|
257
|
-
if
|
268
|
+
elsif pk.all? && meta[:unmatched_pk] != :create
|
269
|
+
if meta[:unmatched_pk] == :raise
|
258
270
|
raise(Error, "no matching associated object with given primary key (association: #{reflection[:name]}, pk: #{pk})")
|
259
271
|
end
|
260
272
|
else
|
261
|
-
nested_attributes_create(
|
273
|
+
nested_attributes_create(meta, attributes)
|
262
274
|
end
|
263
275
|
end
|
264
276
|
|
265
277
|
# Update the given object with the attributes, validating it when the
|
266
278
|
# parent object is validated and saving it when the parent is saved.
|
267
279
|
# Returns the object updated.
|
268
|
-
def nested_attributes_update(
|
269
|
-
nested_attributes_update_attributes(
|
270
|
-
after_validation_hook{validate_associated_object(
|
280
|
+
def nested_attributes_update(meta, obj, attributes)
|
281
|
+
nested_attributes_update_attributes(meta, obj, attributes)
|
282
|
+
after_validation_hook{validate_associated_object(meta, obj)}
|
271
283
|
# Don't need to validate the object twice if :validate association option is not false
|
272
284
|
# and don't want to validate it at all if it is false.
|
273
285
|
after_save_hook{obj.save_changes(:validate=>false)}
|
@@ -275,15 +287,16 @@ module Sequel
|
|
275
287
|
end
|
276
288
|
|
277
289
|
# Update the attributes for the given object related to the current object through the association.
|
278
|
-
def nested_attributes_update_attributes(
|
279
|
-
nested_attributes_check_key_modifications(
|
280
|
-
nested_attributes_set_attributes(
|
290
|
+
def nested_attributes_update_attributes(meta, obj, attributes)
|
291
|
+
nested_attributes_check_key_modifications(meta, obj) do
|
292
|
+
nested_attributes_set_attributes(meta, obj, attributes)
|
281
293
|
end
|
282
294
|
end
|
283
295
|
|
284
296
|
# Validate the given associated object, adding any validation error messages from the
|
285
297
|
# given object to the parent object.
|
286
|
-
def validate_associated_object(
|
298
|
+
def validate_associated_object(meta, obj)
|
299
|
+
reflection = meta[:reflection]
|
287
300
|
return if reflection[:validate] == false
|
288
301
|
association = reflection[:name]
|
289
302
|
if (reflection[:type] == :one_to_many || reflection[:type] == :one_to_one) && (key = reflection[:key]).is_a?(Symbol) && !(pk_val = obj.values[key])
|
@@ -49,6 +49,19 @@ module Sequel
|
|
49
49
|
|
50
50
|
private
|
51
51
|
|
52
|
+
# Create a prepared statement, but modify the SQL used so that the model's columns are explicitly
|
53
|
+
# selected instead of using *, assuming that the dataset selects from a single table.
|
54
|
+
def prepare_explicit_statement(ds, type, vals=OPTS)
|
55
|
+
f = ds.opts[:from]
|
56
|
+
meth = type == :insert_select ? :returning : :select
|
57
|
+
s = ds.opts[meth]
|
58
|
+
if f && f.length == 1 && !ds.opts[:join] && (!s || s.empty?)
|
59
|
+
ds = ds.send(meth, *columns.map{|c| Sequel.identifier(c)})
|
60
|
+
end
|
61
|
+
|
62
|
+
prepare_statement(ds, type, vals)
|
63
|
+
end
|
64
|
+
|
52
65
|
# Create a prepared statement based on the given dataset with a unique name for the given
|
53
66
|
# type of query and values.
|
54
67
|
def prepare_statement(ds, type, vals=OPTS)
|
@@ -76,18 +89,18 @@ module Sequel
|
|
76
89
|
# and return that column values for the row created.
|
77
90
|
def prepared_insert_select(cols)
|
78
91
|
if dataset.supports_insert_select?
|
79
|
-
cached_prepared_statement(:insert_select, prepared_columns(cols)){
|
92
|
+
cached_prepared_statement(:insert_select, prepared_columns(cols)){prepare_explicit_statement(naked.clone(:server=>dataset.opts.fetch(:server, :default)), :insert_select, prepared_statement_key_hash(cols))}
|
80
93
|
end
|
81
94
|
end
|
82
95
|
|
83
96
|
# Return a prepared statement that can be used to lookup a row solely based on the primary key.
|
84
97
|
def prepared_lookup
|
85
|
-
cached_prepared_statement(:fixed, :lookup){
|
98
|
+
cached_prepared_statement(:fixed, :lookup){prepare_explicit_statement(filter(prepared_statement_key_array(primary_key)), :first)}
|
86
99
|
end
|
87
100
|
|
88
101
|
# Return a prepared statement that can be used to refresh a row to get new column values after insertion.
|
89
102
|
def prepared_refresh
|
90
|
-
cached_prepared_statement(:fixed, :refresh){
|
103
|
+
cached_prepared_statement(:fixed, :refresh){prepare_explicit_statement(naked.clone(:server=>dataset.opts.fetch(:server, :default)).filter(prepared_statement_key_array(primary_key)), :first)}
|
91
104
|
end
|
92
105
|
|
93
106
|
# Return an array of two element arrays with the column symbol as the first entry and the
|
@@ -122,8 +135,6 @@ module Sequel
|
|
122
135
|
prepared_lookup.call(primary_key_hash(pk))
|
123
136
|
end
|
124
137
|
|
125
|
-
private
|
126
|
-
|
127
138
|
# If a prepared statement has already been cached for the given type and subtype,
|
128
139
|
# return it. Otherwise, yield to the block to get the prepared statement, and cache it.
|
129
140
|
def cached_prepared_statement(type, subtype)
|