sequel 4.11.0 → 4.12.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 +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)
|