activerecord 4.0.0 → 4.0.1.rc1

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +326 -3
  3. data/README.rdoc +1 -1
  4. data/lib/active_record.rb +1 -0
  5. data/lib/active_record/association_relation.rb +18 -0
  6. data/lib/active_record/associations/association.rb +11 -3
  7. data/lib/active_record/associations/builder/belongs_to.rb +4 -2
  8. data/lib/active_record/associations/collection_association.rb +8 -8
  9. data/lib/active_record/associations/collection_proxy.rb +2 -4
  10. data/lib/active_record/associations/has_many_association.rb +2 -2
  11. data/lib/active_record/associations/has_one_association.rb +3 -1
  12. data/lib/active_record/associations/join_dependency/join_association.rb +2 -0
  13. data/lib/active_record/associations/join_dependency/join_part.rb +14 -1
  14. data/lib/active_record/associations/preloader.rb +3 -2
  15. data/lib/active_record/attribute_methods.rb +26 -2
  16. data/lib/active_record/attribute_methods/read.rb +15 -9
  17. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  18. data/lib/active_record/attribute_methods/write.rb +2 -0
  19. data/lib/active_record/autosave_association.rb +8 -8
  20. data/lib/active_record/callbacks.rb +4 -1
  21. data/lib/active_record/connection_adapters/abstract/database_statements.rb +7 -7
  22. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +12 -3
  23. data/lib/active_record/connection_adapters/abstract_adapter.rb +16 -2
  24. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +3 -1
  25. data/lib/active_record/connection_adapters/column.rb +12 -11
  26. data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -3
  27. data/lib/active_record/connection_adapters/mysql_adapter.rb +19 -9
  28. data/lib/active_record/connection_adapters/postgresql/cast.rb +1 -1
  29. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +7 -6
  30. data/lib/active_record/connection_adapters/postgresql/oid.rb +5 -0
  31. data/lib/active_record/connection_adapters/postgresql/quoting.rb +2 -0
  32. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +7 -10
  33. data/lib/active_record/connection_adapters/postgresql_adapter.rb +19 -13
  34. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +8 -5
  35. data/lib/active_record/core.rb +18 -18
  36. data/lib/active_record/dynamic_matchers.rb +1 -1
  37. data/lib/active_record/fixtures.rb +2 -2
  38. data/lib/active_record/locking/optimistic.rb +1 -1
  39. data/lib/active_record/migration.rb +1 -1
  40. data/lib/active_record/migration/command_recorder.rb +4 -1
  41. data/lib/active_record/model_schema.rb +27 -17
  42. data/lib/active_record/null_relation.rb +1 -5
  43. data/lib/active_record/persistence.rb +16 -5
  44. data/lib/active_record/railtie.rb +1 -0
  45. data/lib/active_record/railties/databases.rake +4 -1
  46. data/lib/active_record/reflection.rb +1 -1
  47. data/lib/active_record/relation.rb +1 -1
  48. data/lib/active_record/relation/batches.rb +2 -2
  49. data/lib/active_record/relation/calculations.rb +1 -0
  50. data/lib/active_record/relation/finder_methods.rb +10 -10
  51. data/lib/active_record/relation/merger.rb +31 -28
  52. data/lib/active_record/relation/query_methods.rb +13 -11
  53. data/lib/active_record/result.rb +15 -1
  54. data/lib/active_record/sanitization.rb +13 -3
  55. data/lib/active_record/schema_dumper.rb +5 -1
  56. data/lib/active_record/tasks/database_tasks.rb +2 -1
  57. data/lib/active_record/tasks/sqlite_database_tasks.rb +1 -1
  58. data/lib/active_record/transactions.rb +5 -5
  59. data/lib/active_record/validations/uniqueness.rb +2 -2
  60. data/lib/active_record/version.rb +1 -1
  61. metadata +10 -9
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
  ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
14
14
  end
15
15
 
16
- attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale
16
+ attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale, :default_function
17
17
  attr_accessor :primary, :coder
18
18
 
19
19
  alias :encoded? :coder
@@ -27,16 +27,17 @@ module ActiveRecord
27
27
  # It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
28
28
  # +null+ determines if this column allows +NULL+ values.
29
29
  def initialize(name, default, sql_type = nil, null = true)
30
- @name = name
31
- @sql_type = sql_type
32
- @null = null
33
- @limit = extract_limit(sql_type)
34
- @precision = extract_precision(sql_type)
35
- @scale = extract_scale(sql_type)
36
- @type = simplified_type(sql_type)
37
- @default = extract_default(default)
38
- @primary = nil
39
- @coder = nil
30
+ @name = name
31
+ @sql_type = sql_type
32
+ @null = null
33
+ @limit = extract_limit(sql_type)
34
+ @precision = extract_precision(sql_type)
35
+ @scale = extract_scale(sql_type)
36
+ @type = simplified_type(sql_type)
37
+ @default = extract_default(default)
38
+ @default_function = nil
39
+ @primary = nil
40
+ @coder = nil
40
41
  end
41
42
 
42
43
  # Returns +true+ if the column is either of type string or text.
@@ -213,9 +213,11 @@ module ActiveRecord
213
213
 
214
214
  # Executes the SQL statement in the context of this connection.
215
215
  def execute(sql, name = nil)
216
- # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
217
- # made since we established the connection
218
- @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
216
+ if @connection
217
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
218
+ # made since we established the connection
219
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
220
+ end
219
221
 
220
222
  super
221
223
  end
@@ -268,6 +270,10 @@ module ActiveRecord
268
270
  def version
269
271
  @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
270
272
  end
273
+
274
+ def set_field_encoding field_name
275
+ field_name
276
+ end
271
277
  end
272
278
  end
273
279
  end
@@ -279,11 +279,7 @@ module ActiveRecord
279
279
  end
280
280
 
281
281
  def exec_query(sql, name = 'SQL', binds = [])
282
- # If the configuration sets prepared_statements:false, binds will
283
- # always be empty, since the bind variables will have been already
284
- # substituted and removed from binds by BindVisitor, so this will
285
- # effectively disable prepared statement usage completely.
286
- if binds.empty?
282
+ if without_prepared_statement?(binds)
287
283
  result_set, affected_rows = exec_without_stmt(sql, name)
288
284
  else
289
285
  result_set, affected_rows = exec_stmt(sql, name, binds)
@@ -393,6 +389,14 @@ module ActiveRecord
393
389
  TYPES[new] = TYPES[old]
394
390
  end
395
391
 
392
+ def self.find_type(field)
393
+ if field.type == Mysql::Field::TYPE_TINY && field.length > 1
394
+ TYPES[Mysql::Field::TYPE_LONG]
395
+ else
396
+ TYPES.fetch(field.type) { Fields::Identity.new }
397
+ end
398
+ end
399
+
396
400
  register_type Mysql::Field::TYPE_TINY, Fields::Boolean.new
397
401
  register_type Mysql::Field::TYPE_LONG, Fields::Integer.new
398
402
  alias_type Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_LONG
@@ -425,9 +429,7 @@ module ActiveRecord
425
429
  if field.decimals > 0
426
430
  types[field.name] = Fields::Decimal.new
427
431
  else
428
- types[field.name] = Fields::TYPES.fetch(field.type) {
429
- Fields::Identity.new
430
- }
432
+ types[field.name] = Fields.find_type field
431
433
  end
432
434
  }
433
435
  result_set = ActiveRecord::Result.new(types.keys, result.to_a, types)
@@ -501,12 +503,12 @@ module ActiveRecord
501
503
  cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
502
504
  field.name
503
505
  }
506
+ metadata.free
504
507
  end
505
508
 
506
509
  result_set = ActiveRecord::Result.new(cols, stmt.to_a) if cols
507
510
  affected_rows = stmt.affected_rows
508
511
 
509
- stmt.result_metadata.free if cols
510
512
  stmt.free_result
511
513
  stmt.close if binds.empty?
512
514
 
@@ -553,6 +555,14 @@ module ActiveRecord
553
555
  def version
554
556
  @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
555
557
  end
558
+
559
+ def set_field_encoding field_name
560
+ field_name.force_encoding(client_encoding)
561
+ if internal_enc = Encoding.default_internal
562
+ field_name = field_name.encoding(internal_enc)
563
+ end
564
+ field_name
565
+ end
556
566
  end
557
567
  end
558
568
  end
@@ -60,7 +60,7 @@ module ActiveRecord
60
60
  end
61
61
 
62
62
  def json_to_string(object)
63
- if Hash === object
63
+ if Hash === object || Array === object
64
64
  ActiveSupport::JSON.encode(object)
65
65
  else
66
66
  object
@@ -135,11 +135,12 @@ module ActiveRecord
135
135
 
136
136
  def exec_query(sql, name = 'SQL', binds = [])
137
137
  log(sql, name, binds) do
138
- result = binds.empty? ? exec_no_cache(sql, binds) :
139
- exec_cache(sql, binds)
138
+ result = without_prepared_statement?(binds) ? exec_no_cache(sql, binds) :
139
+ exec_cache(sql, binds)
140
140
 
141
141
  types = {}
142
- result.fields.each_with_index do |fname, i|
142
+ fields = result.fields
143
+ fields.each_with_index do |fname, i|
143
144
  ftype = result.ftype i
144
145
  fmod = result.fmod i
145
146
  types[fname] = OID::TYPE_MAP.fetch(ftype, fmod) { |oid, mod|
@@ -148,7 +149,7 @@ module ActiveRecord
148
149
  }
149
150
  end
150
151
 
151
- ret = ActiveRecord::Result.new(result.fields, result.values, types)
152
+ ret = ActiveRecord::Result.new(fields, result.values, types)
152
153
  result.clear
153
154
  return ret
154
155
  end
@@ -156,8 +157,8 @@ module ActiveRecord
156
157
 
157
158
  def exec_delete(sql, name = 'SQL', binds = [])
158
159
  log(sql, name, binds) do
159
- result = binds.empty? ? exec_no_cache(sql, binds) :
160
- exec_cache(sql, binds)
160
+ result = without_prepared_statement?(binds) ? exec_no_cache(sql, binds) :
161
+ exec_cache(sql, binds)
161
162
  affected = result.cmd_tuples
162
163
  result.clear
163
164
  affected
@@ -38,12 +38,17 @@ module ActiveRecord
38
38
  class Money < Type
39
39
  def type_cast(value)
40
40
  return if value.nil?
41
+ return value unless String === value
41
42
 
42
43
  # Because money output is formatted according to the locale, there are two
43
44
  # cases to consider (note the decimal separators):
44
45
  # (1) $12,345,678.12
45
46
  # (2) $12.345.678,12
47
+ # Negative values are represented as follows:
48
+ # (3) -$2.55
49
+ # (4) ($2.55)
46
50
 
51
+ value.sub!(/^\((.+)\)$/, '-\1') # (4)
47
52
  case value
48
53
  when /^-?\D+[\d,]+\.\d{2}$/ # (1)
49
54
  value.gsub!(/[^-\d.]/, '')
@@ -30,6 +30,7 @@ module ActiveRecord
30
30
  when Array
31
31
  case sql_type
32
32
  when 'point' then super(PostgreSQLColumn.point_to_string(value))
33
+ when 'json' then super(PostgreSQLColumn.json_to_string(value))
33
34
  else
34
35
  if column.array
35
36
  "'#{PostgreSQLColumn.array_to_string(value, column, self).gsub(/'/, "''")}'"
@@ -98,6 +99,7 @@ module ActiveRecord
98
99
  when Array
99
100
  case column.sql_type
100
101
  when 'point' then PostgreSQLColumn.point_to_string(value)
102
+ when 'json' then PostgreSQLColumn.json_to_string(value)
101
103
  else
102
104
  return super(value, column) unless column.array
103
105
  PostgreSQLColumn.array_to_string(value, column, self)
@@ -321,6 +321,7 @@ module ActiveRecord
321
321
  result = query(<<-end_sql, 'SCHEMA')[0]
322
322
  SELECT attr.attname,
323
323
  CASE
324
+ WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
324
325
  WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
325
326
  substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
326
327
  strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
@@ -332,7 +333,7 @@ module ActiveRecord
332
333
  JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
333
334
  WHERE t.oid = '#{quote_table_name(table)}'::regclass
334
335
  AND cons.contype = 'p'
335
- AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval'
336
+ AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
336
337
  end_sql
337
338
  end
338
339
 
@@ -383,8 +384,9 @@ module ActiveRecord
383
384
  def change_column(table_name, column_name, type, options = {})
384
385
  clear_cache!
385
386
  quoted_table_name = quote_table_name(table_name)
386
-
387
- execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
387
+ sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale])
388
+ sql_type << "[]" if options[:array]
389
+ execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}"
388
390
 
389
391
  change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
390
392
  change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
@@ -466,14 +468,9 @@ module ActiveRecord
466
468
  end
467
469
  end
468
470
 
469
- # Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
470
- #
471
471
  # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
472
472
  # requires that the ORDER BY include the distinct column.
473
- #
474
- # distinct("posts.id", ["posts.created_at desc"])
475
- # # => "DISTINCT posts.id, posts.created_at AS alias_0"
476
- def distinct(columns, orders) #:nodoc:
473
+ def columns_for_distinct(columns, orders) #:nodoc:
477
474
  order_columns = orders.map{ |s|
478
475
  # Convert Arel node to string
479
476
  s = s.to_sql unless s.is_a?(String)
@@ -481,7 +478,7 @@ module ActiveRecord
481
478
  s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
482
479
  }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
483
480
 
484
- [super].concat(order_columns).join(', ')
481
+ [super, *order_columns].join(', ')
485
482
  end
486
483
  end
487
484
  end
@@ -49,13 +49,17 @@ module ActiveRecord
49
49
  # Instantiates a new PostgreSQL column definition in a table.
50
50
  def initialize(name, default, oid_type, sql_type = nil, null = true)
51
51
  @oid_type = oid_type
52
+ default_value = self.class.extract_value_from_default(default)
53
+
52
54
  if sql_type =~ /\[\]$/
53
55
  @array = true
54
- super(name, self.class.extract_value_from_default(default), sql_type[0..sql_type.length - 3], null)
56
+ super(name, default_value, sql_type[0..sql_type.length - 3], null)
55
57
  else
56
58
  @array = false
57
- super(name, self.class.extract_value_from_default(default), sql_type, null)
59
+ super(name, default_value, sql_type, null)
58
60
  end
61
+
62
+ @default_function = default if has_default_function?(default_value, default)
59
63
  end
60
64
 
61
65
  # :stopdoc:
@@ -138,6 +142,10 @@ module ActiveRecord
138
142
 
139
143
  private
140
144
 
145
+ def has_default_function?(default_value, default)
146
+ !default_value && (%r{\w+(.*)} === default)
147
+ end
148
+
141
149
  def extract_limit(sql_type)
142
150
  case sql_type
143
151
  when /^bigint/i; 8
@@ -374,15 +382,11 @@ module ActiveRecord
374
382
  self
375
383
  end
376
384
 
377
- def xml(options = {})
378
- column(args[0], :text, options)
379
- end
380
-
381
385
  private
382
386
 
383
- def create_column_definition(name, type)
384
- ColumnDefinition.new name, type
385
- end
387
+ def create_column_definition(name, type)
388
+ ColumnDefinition.new name, type
389
+ end
386
390
  end
387
391
 
388
392
  class Table < ActiveRecord::ConnectionAdapters::Table
@@ -436,6 +440,7 @@ module ActiveRecord
436
440
  def prepare_column_options(column, types)
437
441
  spec = super
438
442
  spec[:array] = 'true' if column.respond_to?(:array) && column.array
443
+ spec[:default] = "\"#{column.default_function}\"" if column.default_function
439
444
  spec
440
445
  end
441
446
 
@@ -528,6 +533,7 @@ module ActiveRecord
528
533
  super(connection, logger)
529
534
 
530
535
  if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
536
+ @prepared_statements = true
531
537
  @visitor = Arel::Visitors::PostgreSQL.new self
532
538
  else
533
539
  @visitor = unprepared_visitor
@@ -623,9 +629,9 @@ module ActiveRecord
623
629
  true
624
630
  end
625
631
 
626
- # Returns true if pg > 9.2
632
+ # Returns true if pg > 9.1
627
633
  def supports_extensions?
628
- postgresql_version >= 90200
634
+ postgresql_version >= 90100
629
635
  end
630
636
 
631
637
  # Range datatypes weren't introduced until PostgreSQL 9.2
@@ -647,9 +653,9 @@ module ActiveRecord
647
653
 
648
654
  def extension_enabled?(name)
649
655
  if supports_extensions?
650
- res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL)",
656
+ res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled",
651
657
  'SCHEMA'
652
- res.column_types['exists'].type_cast res.rows.first.first
658
+ res.column_types['enabled'].type_cast res.rows.first.first
653
659
  end
654
660
  end
655
661
 
@@ -17,12 +17,14 @@ module ActiveRecord
17
17
  # Allow database path relative to Rails.root, but only if
18
18
  # the database path is not the special path that tells
19
19
  # Sqlite to build a database only in memory.
20
- if defined?(Rails.root) && ':memory:' != config[:database]
21
- config[:database] = File.expand_path(config[:database], Rails.root)
20
+ if ':memory:' != config[:database]
21
+ config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
22
+ dirname = File.dirname(config[:database])
23
+ Dir.mkdir(dirname) unless File.directory?(dirname)
22
24
  end
23
25
 
24
26
  db = SQLite3::Database.new(
25
- config[:database],
27
+ config[:database].to_s,
26
28
  :results_as_hash => true
27
29
  )
28
30
 
@@ -111,6 +113,7 @@ module ActiveRecord
111
113
  @config = config
112
114
 
113
115
  if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
116
+ @prepared_statements = true
114
117
  @visitor = Arel::Visitors::SQLite.new self
115
118
  else
116
119
  @visitor = unprepared_visitor
@@ -291,8 +294,8 @@ module ActiveRecord
291
294
  def exec_query(sql, name = nil, binds = [])
292
295
  log(sql, name, binds) do
293
296
 
294
- # Don't cache statements without bind values
295
- if binds.empty?
297
+ # Don't cache statements if they are not prepared
298
+ if without_prepared_statement?(binds)
296
299
  stmt = @connection.prepare(sql)
297
300
  cols = stmt.columns
298
301
  records = stmt.to_a
@@ -91,20 +91,8 @@ module ActiveRecord
91
91
  end
92
92
 
93
93
  module ClassMethods
94
- def inherited(child_class) #:nodoc:
95
- child_class.initialize_generated_modules
96
- super
97
- end
98
-
99
94
  def initialize_generated_modules
100
- @attribute_methods_mutex = Mutex.new
101
-
102
- # force attribute methods to be higher in inheritance hierarchy than other generated methods
103
- generated_attribute_methods.const_set(:AttrNames, Module.new {
104
- def self.const_missing(name)
105
- const_set(name, [name.to_s.sub(/ATTR_/, '')].pack('h*').freeze)
106
- end
107
- })
95
+ super
108
96
 
109
97
  generated_feature_methods
110
98
  end
@@ -123,6 +111,8 @@ module ActiveRecord
123
111
  super
124
112
  elsif abstract_class?
125
113
  "#{super}(abstract)"
114
+ elsif !connected?
115
+ "#{super}(no database connection)"
126
116
  elsif table_exists?
127
117
  attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
128
118
  "#{super}(#{attr_list})"
@@ -153,7 +143,7 @@ module ActiveRecord
153
143
  else
154
144
  superclass.arel_engine
155
145
  end
156
- end
146
+ end
157
147
  end
158
148
 
159
149
  private
@@ -177,19 +167,22 @@ module ActiveRecord
177
167
  # ==== Example:
178
168
  # # Instantiates a single new object
179
169
  # User.new(first_name: 'Jamie')
180
- def initialize(attributes = nil)
170
+ def initialize(attributes = nil, options = {})
181
171
  defaults = self.class.column_defaults.dup
182
172
  defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? }
183
173
 
184
174
  @attributes = self.class.initialize_attributes(defaults)
185
- @columns_hash = self.class.column_types.dup
175
+ @column_types_override = nil
176
+ @column_types = self.class.column_types
186
177
 
187
178
  init_internals
188
179
  init_changed_attributes
189
180
  ensure_proper_type
190
181
  populate_with_current_scope_attributes
191
182
 
192
- assign_attributes(attributes) if attributes
183
+ # +options+ argument is only needed to make protected_attributes gem easier to hook.
184
+ # Remove it when we drop support to this gem.
185
+ init_attributes(attributes, options) if attributes
193
186
 
194
187
  yield self if block_given?
195
188
  run_callbacks :initialize unless _initialize_callbacks.empty?
@@ -207,7 +200,8 @@ module ActiveRecord
207
200
  # post.title # => 'hello world'
208
201
  def init_with(coder)
209
202
  @attributes = self.class.initialize_attributes(coder['attributes'])
210
- @columns_hash = self.class.column_types.merge(coder['column_types'] || {})
203
+ @column_types_override = coder['column_types']
204
+ @column_types = self.class.column_types
211
205
 
212
206
  init_internals
213
207
 
@@ -459,5 +453,11 @@ module ActiveRecord
459
453
  @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
460
454
  end
461
455
  end
456
+
457
+ # This method is needed to make protected_attributes gem easier to hook.
458
+ # Remove it when we drop support to this gem.
459
+ def init_attributes(attributes, options)
460
+ assign_attributes(attributes)
461
+ end
462
462
  end
463
463
  end