activerecord 2.3.5 → 2.3.6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (90) hide show
  1. data/CHANGELOG +33 -0
  2. data/Rakefile +1 -1
  3. data/examples/performance.sql +85 -0
  4. data/lib/active_record.rb +1 -2
  5. data/lib/active_record/association_preload.rb +9 -2
  6. data/lib/active_record/associations.rb +48 -38
  7. data/lib/active_record/associations/association_collection.rb +15 -11
  8. data/lib/active_record/associations/association_proxy.rb +16 -6
  9. data/lib/active_record/associations/belongs_to_association.rb +11 -1
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -10
  11. data/lib/active_record/associations/has_many_association.rb +5 -0
  12. data/lib/active_record/associations/has_many_through_association.rb +5 -5
  13. data/lib/active_record/associations/has_one_association.rb +10 -1
  14. data/lib/active_record/attribute_methods.rb +5 -1
  15. data/lib/active_record/autosave_association.rb +66 -35
  16. data/lib/active_record/base.rb +77 -36
  17. data/lib/active_record/batches.rb +13 -9
  18. data/lib/active_record/calculations.rb +6 -3
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -3
  20. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  21. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +3 -7
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +64 -10
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +2 -0
  26. data/lib/active_record/connection_adapters/mysql_adapter.rb +31 -1
  27. data/lib/active_record/connection_adapters/postgresql_adapter.rb +31 -66
  28. data/lib/active_record/connection_adapters/sqlite_adapter.rb +2 -2
  29. data/lib/active_record/dirty.rb +2 -2
  30. data/lib/active_record/fixtures.rb +1 -0
  31. data/lib/active_record/locking/optimistic.rb +34 -1
  32. data/lib/active_record/migration.rb +5 -0
  33. data/lib/active_record/nested_attributes.rb +64 -52
  34. data/lib/active_record/reflection.rb +66 -1
  35. data/lib/active_record/schema.rb +5 -1
  36. data/lib/active_record/schema_dumper.rb +3 -0
  37. data/lib/active_record/serializers/json_serializer.rb +1 -1
  38. data/lib/active_record/validations.rb +13 -1
  39. data/lib/active_record/version.rb +1 -1
  40. data/test/cases/active_schema_test_mysql.rb +22 -0
  41. data/test/cases/associations/belongs_to_associations_test.rb +13 -0
  42. data/test/cases/associations/eager_load_nested_include_test.rb +8 -7
  43. data/test/cases/associations/eager_test.rb +7 -1
  44. data/test/cases/associations/has_many_associations_test.rb +26 -0
  45. data/test/cases/associations/inverse_associations_test.rb +566 -0
  46. data/test/cases/associations_test.rb +10 -0
  47. data/test/cases/autosave_association_test.rb +86 -10
  48. data/test/cases/base_test.rb +29 -0
  49. data/test/cases/batches_test.rb +20 -0
  50. data/test/cases/calculations_test.rb +2 -3
  51. data/test/cases/encoding_test.rb +6 -0
  52. data/test/cases/finder_test.rb +19 -3
  53. data/test/cases/fixtures_test.rb +5 -0
  54. data/test/cases/json_serialization_test.rb +14 -0
  55. data/test/cases/locking_test.rb +48 -3
  56. data/test/cases/migration_test.rb +115 -0
  57. data/test/cases/modules_test.rb +28 -0
  58. data/test/cases/named_scope_test.rb +1 -1
  59. data/test/cases/nested_attributes_test.rb +239 -7
  60. data/test/cases/query_cache_test.rb +7 -1
  61. data/test/cases/reflection_test.rb +47 -7
  62. data/test/cases/schema_test_postgresql.rb +2 -2
  63. data/test/cases/validations_i18n_test.rb +6 -36
  64. data/test/cases/validations_test.rb +33 -1
  65. data/test/cases/yaml_serialization_test.rb +11 -0
  66. data/test/fixtures/faces.yml +11 -0
  67. data/test/fixtures/fixture_database.sqlite +0 -0
  68. data/test/fixtures/fixture_database.sqlite3 +0 -0
  69. data/test/fixtures/fixture_database_2.sqlite +0 -0
  70. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  71. data/test/fixtures/interests.yml +33 -0
  72. data/test/fixtures/men.yml +5 -0
  73. data/test/fixtures/zines.yml +5 -0
  74. data/test/models/author.rb +3 -0
  75. data/test/models/bird.rb +6 -0
  76. data/test/models/company_in_module.rb +17 -0
  77. data/test/models/event_author.rb +5 -0
  78. data/test/models/face.rb +7 -0
  79. data/test/models/interest.rb +5 -0
  80. data/test/models/invoice.rb +4 -0
  81. data/test/models/line_item.rb +3 -0
  82. data/test/models/man.rb +9 -0
  83. data/test/models/parrot.rb +6 -0
  84. data/test/models/pirate.rb +10 -0
  85. data/test/models/ship.rb +10 -1
  86. data/test/models/ship_part.rb +3 -1
  87. data/test/models/zine.rb +3 -0
  88. data/test/schema/schema.rb +41 -0
  89. metadata +37 -11
  90. data/lib/active_record/i18n_interpolation_deprecation.rb +0 -26
@@ -31,6 +31,8 @@ module ActiveRecord
31
31
  @updated = true
32
32
  end
33
33
 
34
+ set_inverse_instance(record, @owner)
35
+
34
36
  loaded
35
37
  record
36
38
  end
@@ -46,13 +48,15 @@ module ActiveRecord
46
48
  else
47
49
  "find"
48
50
  end
49
- @reflection.klass.send(find_method,
51
+ the_target = @reflection.klass.send(find_method,
50
52
  @owner[@reflection.primary_key_name],
51
53
  :select => @reflection.options[:select],
52
54
  :conditions => conditions,
53
55
  :include => @reflection.options[:include],
54
56
  :readonly => @reflection.options[:readonly]
55
57
  ) if @owner[@reflection.primary_key_name]
58
+ set_inverse_instance(the_target, @owner)
59
+ the_target
56
60
  end
57
61
 
58
62
  def foreign_key_present
@@ -71,6 +75,12 @@ module ActiveRecord
71
75
  @owner[@reflection.primary_key_name]
72
76
  end
73
77
  end
78
+
79
+ # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
80
+ # has_one associations.
81
+ def we_can_set_the_inverse_on_this?(record)
82
+ @reflection.has_inverse? && @reflection.inverse_of.macro == :has_one
83
+ end
74
84
  end
75
85
  end
76
86
  end
@@ -13,6 +13,7 @@ module ActiveRecord
13
13
  @updated = true
14
14
  end
15
15
 
16
+ set_inverse_instance(record, @owner)
16
17
  loaded
17
18
  record
18
19
  end
@@ -22,21 +23,44 @@ module ActiveRecord
22
23
  end
23
24
 
24
25
  private
25
- def find_target
26
- return nil if association_class.nil?
27
26
 
28
- if @reflection.options[:conditions]
29
- association_class.find(
30
- @owner[@reflection.primary_key_name],
31
- :select => @reflection.options[:select],
32
- :conditions => conditions,
33
- :include => @reflection.options[:include]
34
- )
27
+ # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
28
+ # has_one associations.
29
+ def we_can_set_the_inverse_on_this?(record)
30
+ if @reflection.has_inverse?
31
+ inverse_association = @reflection.polymorphic_inverse_of(record.class)
32
+ inverse_association && inverse_association.macro == :has_one
35
33
  else
36
- association_class.find(@owner[@reflection.primary_key_name], :select => @reflection.options[:select], :include => @reflection.options[:include])
34
+ false
35
+ end
36
+ end
37
+
38
+ def set_inverse_instance(record, instance)
39
+ return if record.nil? || !we_can_set_the_inverse_on_this?(record)
40
+ inverse_relationship = @reflection.polymorphic_inverse_of(record.class)
41
+ unless inverse_relationship.nil?
42
+ record.send(:"set_#{inverse_relationship.name}_target", instance)
37
43
  end
38
44
  end
39
45
 
46
+ def find_target
47
+ return nil if association_class.nil?
48
+
49
+ target =
50
+ if @reflection.options[:conditions]
51
+ association_class.find(
52
+ @owner[@reflection.primary_key_name],
53
+ :select => @reflection.options[:select],
54
+ :conditions => conditions,
55
+ :include => @reflection.options[:include]
56
+ )
57
+ else
58
+ association_class.find(@owner[@reflection.primary_key_name], :select => @reflection.options[:select], :include => @reflection.options[:include])
59
+ end
60
+ set_inverse_instance(target, @owner)
61
+ target
62
+ end
63
+
40
64
  def foreign_key_present
41
65
  !@owner[@reflection.primary_key_name].nil?
42
66
  end
@@ -117,6 +117,11 @@ module ActiveRecord
117
117
  :create => create_scoping
118
118
  }
119
119
  end
120
+
121
+ def we_can_set_the_inverse_on_this?(record)
122
+ inverse = @reflection.inverse_of
123
+ return !inverse.nil?
124
+ end
120
125
  end
121
126
  end
122
127
  end
@@ -1,11 +1,6 @@
1
1
  module ActiveRecord
2
2
  module Associations
3
3
  class HasManyThroughAssociation < HasManyAssociation #:nodoc:
4
- def initialize(owner, reflection)
5
- reflection.check_validity!
6
- super
7
- end
8
-
9
4
  alias_method :new, :build
10
5
 
11
6
  def create!(attrs = nil)
@@ -261,6 +256,11 @@ module ActiveRecord
261
256
  def cached_counter_attribute_name
262
257
  "#{@reflection.name}_count"
263
258
  end
259
+
260
+ # NOTE - not sure that we can actually cope with inverses here
261
+ def we_can_set_the_inverse_on_this?(record)
262
+ false
263
+ end
264
264
  end
265
265
  end
266
266
  end
@@ -57,6 +57,7 @@ module ActiveRecord
57
57
  @target = (AssociationProxy === obj ? obj.target : obj)
58
58
  end
59
59
 
60
+ set_inverse_instance(obj, @owner)
60
61
  @loaded = true
61
62
 
62
63
  unless @owner.new_record? or obj.nil? or dont_save
@@ -77,13 +78,15 @@ module ActiveRecord
77
78
 
78
79
  private
79
80
  def find_target
80
- @reflection.klass.find(:first,
81
+ the_target = @reflection.klass.find(:first,
81
82
  :conditions => @finder_sql,
82
83
  :select => @reflection.options[:select],
83
84
  :order => @reflection.options[:order],
84
85
  :include => @reflection.options[:include],
85
86
  :readonly => @reflection.options[:readonly]
86
87
  )
88
+ set_inverse_instance(the_target, @owner)
89
+ the_target
87
90
  end
88
91
 
89
92
  def construct_sql
@@ -118,6 +121,7 @@ module ActiveRecord
118
121
  else
119
122
  record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
120
123
  self.target = record
124
+ set_inverse_instance(record, @owner)
121
125
  end
122
126
 
123
127
  record
@@ -128,6 +132,11 @@ module ActiveRecord
128
132
  attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
129
133
  attrs
130
134
  end
135
+
136
+ def we_can_set_the_inverse_on_this?(record)
137
+ inverse = @reflection.inverse_of
138
+ return !inverse.nil?
139
+ end
131
140
  end
132
141
  end
133
142
  end
@@ -208,7 +208,7 @@ module ActiveRecord
208
208
  end
209
209
 
210
210
  begin
211
- class_eval(method_definition, __FILE__, __LINE__)
211
+ class_eval(method_definition, __FILE__)
212
212
  rescue SyntaxError => err
213
213
  generated_methods.delete(attr_name)
214
214
  if logger
@@ -230,6 +230,10 @@ module ActiveRecord
230
230
  # It's also possible to instantiate related objects, so a Client class belonging to the clients
231
231
  # table with a +master_id+ foreign key can instantiate master through Client#master.
232
232
  def method_missing(method_id, *args, &block)
233
+ if method_id == :to_ary || method_id == :to_str
234
+ raise NoMethodError, "undefined method `#{method_id}' for #{inspect}:#{self.class}"
235
+ end
236
+
233
237
  method_name = method_id.to_s
234
238
 
235
239
  if self.class.private_method_defined?(method_name)
@@ -156,38 +156,42 @@ module ActiveRecord
156
156
 
157
157
  # Adds a validate and save callback for the association as specified by
158
158
  # the +reflection+.
159
+ #
160
+ # For performance reasons, we don't check whether to validate at runtime,
161
+ # but instead only define the method and callback when needed. However,
162
+ # this can change, for instance, when using nested attributes, which is
163
+ # called _after_ the association has been defined. Since we don't want
164
+ # the callbacks to get defined multiple times, there are guards that
165
+ # check if the save or validation methods have already been defined
166
+ # before actually defining them.
159
167
  def add_autosave_association_callbacks(reflection)
160
- save_method = "autosave_associated_records_for_#{reflection.name}"
161
- validation_method = "validate_associated_records_for_#{reflection.name}"
162
- force_validation = (reflection.options[:validate] == true || reflection.options[:autosave] == true)
168
+ save_method = :"autosave_associated_records_for_#{reflection.name}"
169
+ validation_method = :"validate_associated_records_for_#{reflection.name}"
170
+ collection = reflection.collection?
163
171
 
164
- case reflection.macro
165
- when :has_many, :has_and_belongs_to_many
166
- before_save :before_save_collection_association
172
+ unless method_defined?(save_method)
173
+ if collection
174
+ before_save :before_save_collection_association
167
175
 
168
- define_method(save_method) { save_collection_association(reflection) }
169
- # Doesn't use after_save as that would save associations added in after_create/after_update twice
170
- after_create save_method
171
- after_update save_method
172
-
173
- if force_validation || (reflection.macro == :has_many && reflection.options[:validate] != false)
174
- define_method(validation_method) { validate_collection_association(reflection) }
175
- validate validation_method
176
- end
177
- else
178
- case reflection.macro
179
- when :has_one
180
- define_method(save_method) { save_has_one_association(reflection) }
181
- after_save save_method
182
- when :belongs_to
183
- define_method(save_method) { save_belongs_to_association(reflection) }
184
- before_save save_method
176
+ define_method(save_method) { save_collection_association(reflection) }
177
+ # Doesn't use after_save as that would save associations added in after_create/after_update twice
178
+ after_create save_method
179
+ after_update save_method
180
+ else
181
+ if reflection.macro == :has_one
182
+ define_method(save_method) { save_has_one_association(reflection) }
183
+ after_save save_method
184
+ else
185
+ define_method(save_method) { save_belongs_to_association(reflection) }
186
+ before_save save_method
187
+ end
185
188
  end
189
+ end
186
190
 
187
- if force_validation
188
- define_method(validation_method) { validate_single_association(reflection) }
189
- validate validation_method
190
- end
191
+ if reflection.validate? && !method_defined?(validation_method)
192
+ method = (collection ? :validate_collection_association : :validate_single_association)
193
+ define_method(validation_method) { send(method, reflection) }
194
+ validate validation_method
191
195
  end
192
196
  end
193
197
  end
@@ -213,6 +217,12 @@ module ActiveRecord
213
217
  @marked_for_destruction
214
218
  end
215
219
 
220
+ # Returns whether or not this record has been changed in any way (including whether
221
+ # any of its nested autosave associations are likewise changed)
222
+ def changed_for_autosave?
223
+ new_record? || changed? || marked_for_destruction? || nested_records_changed_for_autosave?
224
+ end
225
+
216
226
  private
217
227
 
218
228
  # Returns the record for an association collection that should be validated
@@ -221,13 +231,28 @@ module ActiveRecord
221
231
  def associated_records_to_validate_or_save(association, new_record, autosave)
222
232
  if new_record
223
233
  association
224
- elsif association.loaded?
225
- autosave ? association : association.select { |record| record.new_record? }
234
+ elsif autosave
235
+ association.target.select { |record| record.changed_for_autosave? }
226
236
  else
227
- autosave ? association.target : association.target.select { |record| record.new_record? }
237
+ association.target.select { |record| record.new_record? }
228
238
  end
229
239
  end
230
-
240
+
241
+ # go through nested autosave associations that are loaded in memory (without loading
242
+ # any new ones), and return true if is changed for autosave
243
+ def nested_records_changed_for_autosave?
244
+ self.class.reflect_on_all_autosave_associations.each do |reflection|
245
+ if association = association_instance_get(reflection.name)
246
+ if [:belongs_to, :has_one].include?(reflection.macro)
247
+ return true if association.target && association.target.changed_for_autosave?
248
+ else
249
+ association.target.each {|record| return true if record.changed_for_autosave? }
250
+ end
251
+ end
252
+ end
253
+ false
254
+ end
255
+
231
256
  # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
232
257
  # turned on for the association specified by +reflection+.
233
258
  def validate_single_association(reflection)
@@ -293,13 +318,15 @@ module ActiveRecord
293
318
  association.destroy(record)
294
319
  elsif autosave != false && (@new_record_before_save || record.new_record?)
295
320
  if autosave
296
- association.send(:insert_record, record, false, false)
321
+ saved = association.send(:insert_record, record, false, false)
297
322
  else
298
323
  association.send(:insert_record, record)
299
324
  end
300
325
  elsif autosave
301
- record.save(false)
326
+ saved = record.save(false)
302
327
  end
328
+
329
+ raise ActiveRecord::Rollback if saved == false
303
330
  end
304
331
  end
305
332
 
@@ -326,7 +353,9 @@ module ActiveRecord
326
353
  key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
327
354
  if autosave != false && (new_record? || association.new_record? || association[reflection.primary_key_name] != key || autosave)
328
355
  association[reflection.primary_key_name] = key
329
- association.save(!autosave)
356
+ saved = association.save(!autosave)
357
+ raise ActiveRecord::Rollback if !saved && autosave
358
+ saved
330
359
  end
331
360
  end
332
361
  end
@@ -347,7 +376,7 @@ module ActiveRecord
347
376
  if autosave && association.marked_for_destruction?
348
377
  association.destroy
349
378
  elsif autosave != false
350
- association.save(!autosave) if association.new_record? || autosave
379
+ saved = association.save(!autosave) if association.new_record? || autosave
351
380
 
352
381
  if association.updated?
353
382
  association_id = association.send(reflection.options[:primary_key] || :id)
@@ -357,6 +386,8 @@ module ActiveRecord
357
386
  self[reflection.options[:foreign_type]] = association.class.base_class.name.to_s
358
387
  end
359
388
  end
389
+
390
+ saved if autosave
360
391
  end
361
392
  end
362
393
  end
@@ -461,6 +461,9 @@ module ActiveRecord #:nodoc:
461
461
  # Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
462
462
  # table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient way of creating a namespace
463
463
  # for tables in a shared database. By default, the prefix is the empty string.
464
+ #
465
+ # If you are organising your models within modules you can add a prefix to the models within a namespace by defining
466
+ # a singleton method in the parent module called table_name_prefix which returns your chosen prefix.
464
467
  cattr_accessor :table_name_prefix, :instance_writer => false
465
468
  @@table_name_prefix = ""
466
469
 
@@ -916,6 +919,29 @@ module ActiveRecord #:nodoc:
916
919
  connection.select_value(sql, "#{name} Count").to_i
917
920
  end
918
921
 
922
+ # Resets one or more counter caches to their correct value using an SQL
923
+ # count query. This is useful when adding new counter caches, or if the
924
+ # counter has been corrupted or modified directly by SQL.
925
+ #
926
+ # ==== Parameters
927
+ #
928
+ # * +id+ - The id of the object you wish to reset a counter on.
929
+ # * +counters+ - One or more counter names to reset
930
+ #
931
+ # ==== Examples
932
+ #
933
+ # # For Post with id #1 records reset the comments_count
934
+ # Post.reset_counters(1, :comments)
935
+ def reset_counters(id, *counters)
936
+ object = find(id)
937
+ counters.each do |association|
938
+ child_class = reflect_on_association(association).klass
939
+ counter_name = child_class.reflect_on_association(self.name.downcase.to_sym).counter_cache_column
940
+
941
+ connection.update("UPDATE #{quoted_table_name} SET #{connection.quote_column_name(counter_name)} = #{object.send(association).count} WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}", "#{name} UPDATE")
942
+ end
943
+ end
944
+
919
945
  # A generic "counter updater" implementation, intended primarily to be
920
946
  # used by increment_counter and decrement_counter, but which may also
921
947
  # be useful on its own. It simply does a direct SQL update for the record
@@ -1148,7 +1174,7 @@ module ActiveRecord #:nodoc:
1148
1174
  contained = contained.singularize if parent.pluralize_table_names
1149
1175
  contained << '_'
1150
1176
  end
1151
- name = "#{table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}"
1177
+ name = "#{full_table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}"
1152
1178
  end
1153
1179
 
1154
1180
  set_table_name(name)
@@ -1178,6 +1204,10 @@ module ActiveRecord #:nodoc:
1178
1204
  key
1179
1205
  end
1180
1206
 
1207
+ def full_table_name_prefix #:nodoc:
1208
+ (parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
1209
+ end
1210
+
1181
1211
  # Defines the column name for use with single table inheritance
1182
1212
  # -- can be set in subclasses like so: self.inheritance_column = "type_id"
1183
1213
  def inheritance_column
@@ -1861,7 +1891,7 @@ module ActiveRecord #:nodoc:
1861
1891
  # find(:first, options.merge(finder_options))
1862
1892
  # end
1863
1893
  # end
1864
- self.class_eval %{
1894
+ self.class_eval <<-EOS, __FILE__, __LINE__ + 1
1865
1895
  def self.#{method_id}(*args)
1866
1896
  options = args.extract_options!
1867
1897
  attributes = construct_attributes_from_arguments(
@@ -1881,7 +1911,7 @@ module ActiveRecord #:nodoc:
1881
1911
  end
1882
1912
  #{'result || raise(RecordNotFound, "Couldn\'t find #{name} with #{attributes.to_a.collect {|pair| "#{pair.first} = #{pair.second}"}.join(\', \')}")' if bang}
1883
1913
  end
1884
- }, __FILE__, __LINE__
1914
+ EOS
1885
1915
  send(method_id, *arguments)
1886
1916
  elsif match.instantiator?
1887
1917
  instantiator = match.instantiator
@@ -1910,25 +1940,30 @@ module ActiveRecord #:nodoc:
1910
1940
  # record
1911
1941
  # end
1912
1942
  # end
1913
- self.class_eval %{
1943
+ self.class_eval <<-EOS, __FILE__, __LINE__ + 1
1914
1944
  def self.#{method_id}(*args)
1915
- guard_protected_attributes = false
1916
-
1917
- if args[0].is_a?(Hash)
1918
- guard_protected_attributes = true
1919
- attributes = args[0].with_indifferent_access
1920
- find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
1921
- else
1922
- find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
1945
+ attributes = [:#{attribute_names.join(',:')}]
1946
+ protected_attributes_for_create, unprotected_attributes_for_create = {}, {}
1947
+ args.each_with_index do |arg, i|
1948
+ if arg.is_a?(Hash)
1949
+ protected_attributes_for_create = args[i].with_indifferent_access
1950
+ else
1951
+ unprotected_attributes_for_create[attributes[i]] = args[i]
1952
+ end
1923
1953
  end
1924
1954
 
1955
+ find_attributes = (protected_attributes_for_create.merge(unprotected_attributes_for_create)).slice(*attributes)
1956
+
1925
1957
  options = { :conditions => find_attributes }
1926
1958
  set_readonly_option!(options)
1927
1959
 
1928
1960
  record = find(:first, options)
1929
1961
 
1930
1962
  if record.nil?
1931
- record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
1963
+ record = self.new do |r|
1964
+ r.send(:attributes=, protected_attributes_for_create, true) unless protected_attributes_for_create.empty?
1965
+ r.send(:attributes=, unprotected_attributes_for_create, false) unless unprotected_attributes_for_create.empty?
1966
+ end
1932
1967
  #{'yield(record) if block_given?'}
1933
1968
  #{'record.save' if instantiator == :create}
1934
1969
  record
@@ -1936,14 +1971,14 @@ module ActiveRecord #:nodoc:
1936
1971
  record
1937
1972
  end
1938
1973
  end
1939
- }, __FILE__, __LINE__
1974
+ EOS
1940
1975
  send(method_id, *arguments, &block)
1941
1976
  end
1942
1977
  elsif match = DynamicScopeMatch.match(method_id)
1943
1978
  attribute_names = match.attribute_names
1944
1979
  super unless all_attributes_exists?(attribute_names)
1945
1980
  if match.scope?
1946
- self.class_eval %{
1981
+ self.class_eval <<-EOS, __FILE__, __LINE__ + 1
1947
1982
  def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
1948
1983
  options = args.extract_options! # options = args.extract_options!
1949
1984
  attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments(
@@ -1952,7 +1987,7 @@ module ActiveRecord #:nodoc:
1952
1987
  #
1953
1988
  scoped(:conditions => attributes) # scoped(:conditions => attributes)
1954
1989
  end # end
1955
- }, __FILE__, __LINE__
1990
+ EOS
1956
1991
  send(method_id, *arguments)
1957
1992
  end
1958
1993
  else
@@ -2194,9 +2229,9 @@ module ActiveRecord #:nodoc:
2194
2229
  modularized_name = type_name_with_module(type_name)
2195
2230
  silence_warnings do
2196
2231
  begin
2197
- class_eval(modularized_name, __FILE__, __LINE__)
2232
+ class_eval(modularized_name, __FILE__)
2198
2233
  rescue NameError
2199
- class_eval(type_name, __FILE__, __LINE__)
2234
+ class_eval(type_name, __FILE__)
2200
2235
  end
2201
2236
  end
2202
2237
  end
@@ -2436,7 +2471,7 @@ module ActiveRecord #:nodoc:
2436
2471
  @new_record = true
2437
2472
  ensure_proper_type
2438
2473
  self.attributes = attributes unless attributes.nil?
2439
- self.class.send(:scope, :create).each { |att,value| self.send("#{att}=", value) } if self.class.send(:scoped?, :create)
2474
+ assign_attributes(self.class.send(:scope, :create)) if self.class.send(:scoped?, :create)
2440
2475
  result = yield self if block_given?
2441
2476
  callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
2442
2477
  result
@@ -2693,7 +2728,7 @@ module ActiveRecord #:nodoc:
2693
2728
  def reload(options = nil)
2694
2729
  clear_aggregation_cache
2695
2730
  clear_association_cache
2696
- @attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes'))
2731
+ @attributes.update(self.class.send(:with_exclusive_scope) { self.class.find(self.id, options) }.instance_variable_get('@attributes'))
2697
2732
  @attributes_cache = {}
2698
2733
  self
2699
2734
  end
@@ -2736,26 +2771,15 @@ module ActiveRecord #:nodoc:
2736
2771
  attributes = new_attributes.dup
2737
2772
  attributes.stringify_keys!
2738
2773
 
2739
- multi_parameter_attributes = []
2740
2774
  attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes
2741
-
2742
- attributes.each do |k, v|
2743
- if k.include?("(")
2744
- multi_parameter_attributes << [ k, v ]
2745
- else
2746
- respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(UnknownAttributeError, "unknown attribute: #{k}")
2747
- end
2748
- end
2749
-
2750
- assign_multiparameter_attributes(multi_parameter_attributes)
2775
+ assign_attributes(attributes) if attributes and attributes.any?
2751
2776
  end
2752
2777
 
2753
2778
  # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
2754
2779
  def attributes
2755
- self.attribute_names.inject({}) do |attrs, name|
2756
- attrs[name] = read_attribute(name)
2757
- attrs
2758
- end
2780
+ attrs = {}
2781
+ attribute_names.each { |name| attrs[name] = read_attribute(name) }
2782
+ attrs
2759
2783
  end
2760
2784
 
2761
2785
  # Returns a hash of attributes before typecasting and deserialization.
@@ -2869,6 +2893,23 @@ module ActiveRecord #:nodoc:
2869
2893
  end
2870
2894
 
2871
2895
  private
2896
+ # Assigns attributes, dealing nicely with both multi and single paramater attributes
2897
+ # Assumes attributes is a hash
2898
+
2899
+ def assign_attributes(attributes={})
2900
+ multiparameter_attributes = []
2901
+
2902
+ attributes.each do |k, v|
2903
+ if k.to_s.include?("(")
2904
+ multiparameter_attributes << [ k, v ]
2905
+ else
2906
+ respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(UnknownAttributeError, "unknown attribute: #{k}")
2907
+ end
2908
+ end
2909
+
2910
+ assign_multiparameter_attributes(multiparameter_attributes) unless multiparameter_attributes.empty?
2911
+ end
2912
+
2872
2913
  def create_or_update
2873
2914
  raise ReadOnlyRecord if readonly?
2874
2915
  result = new_record? ? create : update
@@ -3099,7 +3140,7 @@ module ActiveRecord #:nodoc:
3099
3140
 
3100
3141
  # Returns a comma-separated pair list, like "key1 = val1, key2 = val2".
3101
3142
  def comma_pair_list(hash)
3102
- hash.inject([]) { |list, pair| list << "#{pair.first} = #{pair.last}" }.join(", ")
3143
+ hash.map { |k,v| "#{k} = #{v}" }.join(", ")
3103
3144
  end
3104
3145
 
3105
3146
  def quoted_column_names(attributes = attributes_with_quotes)