sequel 4.11.0 → 4.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +32 -0
  3. data/Rakefile +1 -5
  4. data/doc/opening_databases.rdoc +5 -1
  5. data/doc/release_notes/4.12.0.txt +105 -0
  6. data/lib/sequel/adapters/jdbc.rb +1 -0
  7. data/lib/sequel/adapters/oracle.rb +1 -0
  8. data/lib/sequel/adapters/postgres.rb +17 -8
  9. data/lib/sequel/adapters/shared/cubrid.rb +2 -1
  10. data/lib/sequel/adapters/shared/db2.rb +1 -0
  11. data/lib/sequel/adapters/shared/mssql.rb +1 -0
  12. data/lib/sequel/adapters/shared/postgres.rb +23 -2
  13. data/lib/sequel/adapters/shared/sqlanywhere.rb +1 -0
  14. data/lib/sequel/adapters/sqlite.rb +11 -5
  15. data/lib/sequel/database/query.rb +14 -1
  16. data/lib/sequel/dataset/prepared_statements.rb +2 -1
  17. data/lib/sequel/dataset/query.rb +48 -37
  18. data/lib/sequel/dataset/sql.rb +0 -39
  19. data/lib/sequel/extensions/pg_interval.rb +1 -1
  20. data/lib/sequel/extensions/pg_static_cache_updater.rb +11 -5
  21. data/lib/sequel/model/associations.rb +2 -2
  22. data/lib/sequel/plugins/auto_validations.rb +16 -4
  23. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  24. data/lib/sequel/plugins/nested_attributes.rb +72 -59
  25. data/lib/sequel/plugins/prepared_statements.rb +16 -5
  26. data/lib/sequel/plugins/prepared_statements_associations.rb +14 -0
  27. data/lib/sequel/sql.rb +2 -43
  28. data/lib/sequel/version.rb +1 -1
  29. data/spec/adapters/postgres_spec.rb +49 -20
  30. data/spec/bin_spec.rb +2 -2
  31. data/spec/core/dataset_spec.rb +18 -6
  32. data/spec/core/schema_spec.rb +2 -1
  33. data/spec/extensions/auto_validations_spec.rb +23 -2
  34. data/spec/extensions/nested_attributes_spec.rb +32 -1
  35. data/spec/extensions/pg_static_cache_updater_spec.rb +12 -0
  36. data/spec/extensions/prepared_statements_associations_spec.rb +17 -17
  37. data/spec/extensions/prepared_statements_spec.rb +11 -8
  38. data/spec/integration/plugin_test.rb +43 -0
  39. data/spec/integration/schema_test.rb +7 -0
  40. data/spec/model/eager_loading_spec.rb +9 -0
  41. metadata +4 -2
@@ -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\d):(\d\d):(\d\d(\.\d+)?))|([+-]?\d+ hours?\s?)?([+-]?\d+ mins?\s?)?([+-]?\d+(\.\d+)? secs?\s?)?)?\z/o
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 model
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
- listen(opts[:channel_name]||default_static_cache_update_name, {:loop=>true}.merge(opts)) do |_, _, oid|
123
- if model = oid_map[oid.to_i]
124
- model.send(:load_cache)
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
- cached_fetch(:select){Sequel::SQL::ColumnAll.new(associated_class.table_name)}
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 three types of validations
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 and the types for the type 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
 
@@ -62,7 +62,7 @@ module Sequel
62
62
  # Example of usage:
63
63
  #
64
64
  # class MyModel
65
- # define_hook :before_move_to
65
+ # add_hook_type :before_move_to
66
66
  # before_move_to(:check_move_allowed){|o| o.allow_move?}
67
67
  # def move_to(there)
68
68
  # return if before_move_to == false
@@ -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
- # * :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
- # * :strict - Kept for backward compatibility. Setting it to false is
99
- # equivalent to setting :unmatched_pk to :ignore.
100
- # * :transform - A proc to transform attribute hashes before they are
101
- # passed to associated object. Takes two arguments, the parent object and
102
- # the attribute hash. Uses the return value as the new attribute hash.
103
- # * :unmatched_pk - Specify the action to be taken if a primary key is
104
- # provided in a record, but it doesn't match an existing associated
105
- # object. Set to :create to create a new object with that primary
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
- if reflection.returns_array?
129
- define_method("#{reflection[:name]}_attributes=") do |array|
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(reflection, obj)
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(reflection, attributes)
165
+ def nested_attributes_create(meta, attributes)
166
+ reflection = meta[:reflection]
158
167
  obj = reflection.associated_class.new
159
- nested_attributes_set_attributes(reflection, obj, attributes)
160
- after_validation_hook{validate_associated_object(reflection, obj)}
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(reflection, attributes_list)
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 = reflection[:nested_attributes][:limit]) && attributes_list.length > 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(reflection, a)}
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(reflection, obj, opts=OPTS)
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(reflection, obj, attributes)
216
- if fields = reflection[:nested_attributes][: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(reflection, attributes)
235
- if a = reflection[:nested_attributes][:transform]
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 = reflection[:nested_attributes][:reject_if]) && b.call(attributes)
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 reflection[:nested_attributes][:destroy] && klass.db.send(:typecast_value_boolean, attributes.delete(:_delete) || attributes.delete('_delete'))
250
- nested_attributes_remove(reflection, obj, :destroy=>true)
251
- elsif reflection[:nested_attributes][:remove] && klass.db.send(:typecast_value_boolean, attributes.delete(:_remove) || attributes.delete('_remove'))
252
- nested_attributes_remove(reflection, obj)
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(reflection, obj, attributes)
266
+ nested_attributes_update(meta, obj, attributes)
255
267
  end
256
- elsif pk.all? && reflection[:nested_attributes][:unmatched_pk] != :create
257
- if reflection[:nested_attributes][:unmatched_pk] == :raise
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(reflection, attributes)
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(reflection, obj, attributes)
269
- nested_attributes_update_attributes(reflection, obj, attributes)
270
- after_validation_hook{validate_associated_object(reflection, obj)}
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(reflection, obj, attributes)
279
- nested_attributes_check_key_modifications(reflection, obj) do
280
- nested_attributes_set_attributes(reflection, obj, 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(reflection, obj)
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)){prepare_statement(naked.clone(:server=>dataset.opts.fetch(:server, :default)), :insert_select, prepared_statement_key_hash(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){prepare_statement(filter(prepared_statement_key_array(primary_key)), :first)}
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){prepare_statement(naked.clone(:server=>dataset.opts.fetch(:server, :default)).filter(prepared_statement_key_array(primary_key)), :first)}
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)