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.
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)