activemodel 5.2.6 → 6.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -133
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_model.rb +1 -1
  6. data/lib/active_model/attribute.rb +3 -4
  7. data/lib/active_model/attribute/user_provided_default.rb +1 -2
  8. data/lib/active_model/attribute_assignment.rb +1 -1
  9. data/lib/active_model/attribute_methods.rb +39 -1
  10. data/lib/active_model/attribute_mutation_tracker.rb +1 -6
  11. data/lib/active_model/attribute_set.rb +2 -10
  12. data/lib/active_model/attribute_set/builder.rb +1 -3
  13. data/lib/active_model/attribute_set/yaml_encoder.rb +1 -2
  14. data/lib/active_model/attributes.rb +10 -22
  15. data/lib/active_model/callbacks.rb +10 -7
  16. data/lib/active_model/conversion.rb +1 -1
  17. data/lib/active_model/dirty.rb +2 -2
  18. data/lib/active_model/errors.rb +90 -11
  19. data/lib/active_model/gem_version.rb +4 -4
  20. data/lib/active_model/naming.rb +19 -3
  21. data/lib/active_model/railtie.rb +6 -0
  22. data/lib/active_model/secure_password.rb +48 -55
  23. data/lib/active_model/serializers/json.rb +10 -9
  24. data/lib/active_model/type/binary.rb +1 -1
  25. data/lib/active_model/type/boolean.rb +1 -10
  26. data/lib/active_model/type/date.rb +1 -2
  27. data/lib/active_model/type/date_time.rb +3 -4
  28. data/lib/active_model/type/decimal.rb +4 -0
  29. data/lib/active_model/type/helpers.rb +0 -1
  30. data/lib/active_model/type/helpers/time_value.rb +19 -1
  31. data/lib/active_model/type/integer.rb +1 -6
  32. data/lib/active_model/type/registry.rb +2 -10
  33. data/lib/active_model/type/string.rb +2 -2
  34. data/lib/active_model/type/time.rb +0 -5
  35. data/lib/active_model/validations.rb +0 -2
  36. data/lib/active_model/validations/acceptance.rb +4 -8
  37. data/lib/active_model/validations/clusivity.rb +1 -1
  38. data/lib/active_model/validations/confirmation.rb +2 -2
  39. data/lib/active_model/validations/inclusion.rb +1 -1
  40. data/lib/active_model/validations/numericality.rb +9 -6
  41. data/lib/active_model/validations/validates.rb +2 -2
  42. data/lib/active_model/validator.rb +1 -1
  43. metadata +10 -11
  44. data/lib/active_model/type/helpers/timezone.rb +0 -19
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/array/extract_options"
4
+ require "active_support/core_ext/hash/keys"
4
5
 
5
6
  module ActiveModel
6
7
  # == Active \Model \Callbacks
@@ -127,26 +128,28 @@ module ActiveModel
127
128
  private
128
129
 
129
130
  def _define_before_model_callback(klass, callback)
130
- klass.define_singleton_method("before_#{callback}") do |*args, &block|
131
- set_callback(:"#{callback}", :before, *args, &block)
131
+ klass.define_singleton_method("before_#{callback}") do |*args, **options, &block|
132
+ options.assert_valid_keys(:if, :unless, :prepend)
133
+ set_callback(:"#{callback}", :before, *args, options, &block)
132
134
  end
133
135
  end
134
136
 
135
137
  def _define_around_model_callback(klass, callback)
136
- klass.define_singleton_method("around_#{callback}") do |*args, &block|
137
- set_callback(:"#{callback}", :around, *args, &block)
138
+ klass.define_singleton_method("around_#{callback}") do |*args, **options, &block|
139
+ options.assert_valid_keys(:if, :unless, :prepend)
140
+ set_callback(:"#{callback}", :around, *args, options, &block)
138
141
  end
139
142
  end
140
143
 
141
144
  def _define_after_model_callback(klass, callback)
142
- klass.define_singleton_method("after_#{callback}") do |*args, &block|
143
- options = args.extract_options!
145
+ klass.define_singleton_method("after_#{callback}") do |*args, **options, &block|
146
+ options.assert_valid_keys(:if, :unless, :prepend)
144
147
  options[:prepend] = true
145
148
  conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v|
146
149
  v != false
147
150
  }
148
151
  options[:if] = Array(options[:if]) << conditional
149
- set_callback(:"#{callback}", :after, *(args << options), &block)
152
+ set_callback(:"#{callback}", :after, *args, options, &block)
150
153
  end
151
154
  end
152
155
  end
@@ -103,7 +103,7 @@ module ActiveModel
103
103
  @_to_partial_path ||= begin
104
104
  element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
105
105
  collection = ActiveSupport::Inflector.tableize(name)
106
- "#{collection}/#{element}".freeze
106
+ "#{collection}/#{element}"
107
107
  end
108
108
  end
109
109
  end
@@ -153,7 +153,7 @@ module ActiveModel
153
153
  @mutations_from_database = nil
154
154
  end
155
155
 
156
- # Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise.
156
+ # Returns +true+ if any of the attributes has unsaved changes, +false+ otherwise.
157
157
  #
158
158
  # person.changed? # => false
159
159
  # person.name = 'bob'
@@ -306,7 +306,7 @@ module ActiveModel
306
306
 
307
307
  # Handles <tt>*_previous_change</tt> for +method_missing+.
308
308
  def attribute_previous_change(attr)
309
- previous_changes[attr] if attribute_previously_changed?(attr)
309
+ previous_changes[attr]
310
310
  end
311
311
 
312
312
  # Handles <tt>*_will_change!</tt> for +method_missing+.
@@ -62,6 +62,11 @@ module ActiveModel
62
62
  CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
63
63
  MESSAGE_OPTIONS = [:message]
64
64
 
65
+ class << self
66
+ attr_accessor :i18n_full_message # :nodoc:
67
+ end
68
+ self.i18n_full_message = false
69
+
65
70
  attr_reader :messages, :details
66
71
 
67
72
  # Pass in the instance of the object that is using the errors object.
@@ -107,6 +112,17 @@ module ActiveModel
107
112
  @details.merge!(other.details) { |_, ary1, ary2| ary1 + ary2 }
108
113
  end
109
114
 
115
+ # Removes all errors except the given keys. Returns a hash containing the removed errors.
116
+ #
117
+ # person.errors.keys # => [:name, :age, :gender, :city]
118
+ # person.errors.slice!(:age, :gender) # => { :name=>["cannot be nil"], :city=>["cannot be nil"] }
119
+ # person.errors.keys # => [:age, :gender]
120
+ def slice!(*keys)
121
+ keys = keys.map(&:to_sym)
122
+ @details.slice!(*keys)
123
+ @messages.slice!(*keys)
124
+ end
125
+
110
126
  # Clear the error messages.
111
127
  #
112
128
  # person.errors.full_messages # => ["name cannot be nil"]
@@ -312,15 +328,15 @@ module ActiveModel
312
328
  # person.errors.added? :name, :blank # => true
313
329
  # person.errors.added? :name, "can't be blank" # => true
314
330
  #
315
- # If the error message requires an option, then it returns +true+ with
316
- # the correct option, or +false+ with an incorrect or missing option.
331
+ # If the error message requires options, then it returns +true+ with
332
+ # the correct options, or +false+ with incorrect or missing options.
317
333
  #
318
- # person.errors.add :name, :too_long, { count: 25 }
319
- # person.errors.added? :name, :too_long, count: 25 # => true
320
- # person.errors.added? :name, "is too long (maximum is 25 characters)" # => true
321
- # person.errors.added? :name, :too_long, count: 24 # => false
322
- # person.errors.added? :name, :too_long # => false
323
- # person.errors.added? :name, "is too long" # => false
334
+ # person.errors.add :name, :too_long, { count: 25 }
335
+ # person.errors.added? :name, :too_long, count: 25 # => true
336
+ # person.errors.added? :name, "is too long (maximum is 25 characters)" # => true
337
+ # person.errors.added? :name, :too_long, count: 24 # => false
338
+ # person.errors.added? :name, :too_long # => false
339
+ # person.errors.added? :name, "is too long" # => false
324
340
  def added?(attribute, message = :invalid, options = {})
325
341
  message = message.call if message.respond_to?(:call)
326
342
 
@@ -331,6 +347,27 @@ module ActiveModel
331
347
  end
332
348
  end
333
349
 
350
+ # Returns +true+ if an error on the attribute with the given message is
351
+ # present, or +false+ otherwise. +message+ is treated the same as for +add+.
352
+ #
353
+ # person.errors.add :age
354
+ # person.errors.add :name, :too_long, { count: 25 }
355
+ # person.errors.of_kind? :age # => true
356
+ # person.errors.of_kind? :name # => false
357
+ # person.errors.of_kind? :name, :too_long # => true
358
+ # person.errors.of_kind? :name, "is too long (maximum is 25 characters)" # => true
359
+ # person.errors.of_kind? :name, :not_too_long # => false
360
+ # person.errors.of_kind? :name, "is too long" # => false
361
+ def of_kind?(attribute, message = :invalid)
362
+ message = message.call if message.respond_to?(:call)
363
+
364
+ if message.is_a? Symbol
365
+ details[attribute.to_sym].map { |e| e[:error] }.include? message
366
+ else
367
+ self[attribute].include? message
368
+ end
369
+ end
370
+
334
371
  # Returns all the full error messages in an array.
335
372
  #
336
373
  # class Person
@@ -364,12 +401,54 @@ module ActiveModel
364
401
  # Returns a full message for a given attribute.
365
402
  #
366
403
  # person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
404
+ #
405
+ # The `"%{attribute} %{message}"` error format can be overridden with either
406
+ #
407
+ # * <tt>activemodel.errors.models.person/contacts/addresses.attributes.street.format</tt>
408
+ # * <tt>activemodel.errors.models.person/contacts/addresses.format</tt>
409
+ # * <tt>activemodel.errors.models.person.attributes.name.format</tt>
410
+ # * <tt>activemodel.errors.models.person.format</tt>
411
+ # * <tt>errors.format</tt>
367
412
  def full_message(attribute, message)
368
413
  return message if attribute == :base
369
- attr_name = attribute.to_s.tr(".", "_").humanize
414
+ attribute = attribute.to_s
415
+
416
+ if self.class.i18n_full_message && @base.class.respond_to?(:i18n_scope)
417
+ attribute = attribute.remove(/\[\d\]/)
418
+ parts = attribute.split(".")
419
+ attribute_name = parts.pop
420
+ namespace = parts.join("/") unless parts.empty?
421
+ attributes_scope = "#{@base.class.i18n_scope}.errors.models"
422
+
423
+ if namespace
424
+ defaults = @base.class.lookup_ancestors.map do |klass|
425
+ [
426
+ :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.attributes.#{attribute_name}.format",
427
+ :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.format",
428
+ ]
429
+ end
430
+ else
431
+ defaults = @base.class.lookup_ancestors.map do |klass|
432
+ [
433
+ :"#{attributes_scope}.#{klass.model_name.i18n_key}.attributes.#{attribute_name}.format",
434
+ :"#{attributes_scope}.#{klass.model_name.i18n_key}.format",
435
+ ]
436
+ end
437
+ end
438
+
439
+ defaults.flatten!
440
+ else
441
+ defaults = []
442
+ end
443
+
444
+ defaults << :"errors.format"
445
+ defaults << "%{attribute} %{message}"
446
+
447
+ attr_name = attribute.tr(".", "_").humanize
370
448
  attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
371
- I18n.t(:"errors.format",
372
- default: "%{attribute} %{message}",
449
+
450
+ I18n.t(defaults.shift,
451
+ default: defaults,
373
452
  attribute: attr_name,
374
453
  message: message)
375
454
  end
@@ -7,10 +7,10 @@ module ActiveModel
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 5
11
- MINOR = 2
12
- TINY = 6
13
- PRE = nil
10
+ MAJOR = 6
11
+ MINOR = 0
12
+ TINY = 0
13
+ PRE = "beta1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -110,6 +110,22 @@ module ActiveModel
110
110
  # BlogPost.model_name.eql?('BlogPost') # => true
111
111
  # BlogPost.model_name.eql?('Blog Post') # => false
112
112
 
113
+ ##
114
+ # :method: match?
115
+ #
116
+ # :call-seq:
117
+ # match?(regexp)
118
+ #
119
+ # Equivalent to <tt>String#match?</tt>. Match the class name against the
120
+ # given regexp. Returns +true+ if there is a match, otherwise +false+.
121
+ #
122
+ # class BlogPost
123
+ # extend ActiveModel::Naming
124
+ # end
125
+ #
126
+ # BlogPost.model_name.match?(/Post/) # => true
127
+ # BlogPost.model_name.match?(/\d/) # => false
128
+
113
129
  ##
114
130
  # :method: to_s
115
131
  #
@@ -131,7 +147,7 @@ module ActiveModel
131
147
  # to_str()
132
148
  #
133
149
  # Equivalent to +to_s+.
134
- delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s,
150
+ delegate :==, :===, :<=>, :=~, :"!~", :eql?, :match?, :to_s,
135
151
  :to_str, :as_json, to: :name
136
152
 
137
153
  # Returns a new ActiveModel::Name instance. By default, the +namespace+
@@ -193,7 +209,7 @@ module ActiveModel
193
209
  private
194
210
 
195
211
  def _singularize(string)
196
- ActiveSupport::Inflector.underscore(string).tr("/".freeze, "_".freeze)
212
+ ActiveSupport::Inflector.underscore(string).tr("/", "_")
197
213
  end
198
214
  end
199
215
 
@@ -236,7 +252,7 @@ module ActiveModel
236
252
  # Person.model_name.plural # => "people"
237
253
  def model_name
238
254
  @_model_name ||= begin
239
- namespace = parents.detect do |n|
255
+ namespace = module_parents.detect do |n|
240
256
  n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
241
257
  end
242
258
  ActiveModel::Name.new(self, namespace)
@@ -7,8 +7,14 @@ module ActiveModel
7
7
  class Railtie < Rails::Railtie # :nodoc:
8
8
  config.eager_load_namespaces << ActiveModel
9
9
 
10
+ config.active_model = ActiveSupport::OrderedOptions.new
11
+
10
12
  initializer "active_model.secure_password" do
11
13
  ActiveModel::SecurePassword.min_cost = Rails.env.test?
12
14
  end
15
+
16
+ initializer "active_model.i18n_full_message" do
17
+ ActiveModel::Errors.i18n_full_message = config.active_model.delete(:i18n_full_message) || false
18
+ end
13
19
  end
14
20
  end
@@ -16,15 +16,16 @@ module ActiveModel
16
16
 
17
17
  module ClassMethods
18
18
  # Adds methods to set and authenticate against a BCrypt password.
19
- # This mechanism requires you to have a +password_digest+ attribute.
19
+ # This mechanism requires you to have a +XXX_digest+ attribute.
20
+ # Where +XXX+ is the attribute name of your desired password.
20
21
  #
21
22
  # The following validations are added automatically:
22
23
  # * Password must be present on creation
23
24
  # * Password length should be less than or equal to 72 bytes
24
- # * Confirmation of password (using a +password_confirmation+ attribute)
25
+ # * Confirmation of password (using a +XXX_confirmation+ attribute)
25
26
  #
26
- # If password confirmation validation is not needed, simply leave out the
27
- # value for +password_confirmation+ (i.e. don't provide a form field for
27
+ # If confirmation validation is not needed, simply leave out the
28
+ # value for +XXX_confirmation+ (i.e. don't provide a form field for
28
29
  # it). When this attribute has a +nil+ value, the validation will not be
29
30
  # triggered.
30
31
  #
@@ -37,9 +38,10 @@ module ActiveModel
37
38
  #
38
39
  # Example using Active Record (which automatically includes ActiveModel::SecurePassword):
39
40
  #
40
- # # Schema: User(name:string, password_digest:string)
41
+ # # Schema: User(name:string, password_digest:string, recovery_password_digest:string)
41
42
  # class User < ActiveRecord::Base
42
43
  # has_secure_password
44
+ # has_secure_password :recovery_password, validations: false
43
45
  # end
44
46
  #
45
47
  # user = User.new(name: 'david', password: '', password_confirmation: 'nomatch')
@@ -48,11 +50,15 @@ module ActiveModel
48
50
  # user.save # => false, confirmation doesn't match
49
51
  # user.password_confirmation = 'mUc3m00RsqyRe'
50
52
  # user.save # => true
53
+ # user.recovery_password = "42password"
54
+ # user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC"
55
+ # user.save # => true
51
56
  # user.authenticate('notright') # => false
52
57
  # user.authenticate('mUc3m00RsqyRe') # => user
58
+ # user.authenticate_recovery_password('42password') # => user
53
59
  # User.find_by(name: 'david').try(:authenticate, 'notright') # => false
54
60
  # User.find_by(name: 'david').try(:authenticate, 'mUc3m00RsqyRe') # => user
55
- def has_secure_password(options = {})
61
+ def has_secure_password(attribute = :password, validations: true)
56
62
  # Load bcrypt gem only when has_secure_password is used.
57
63
  # This is to avoid ActiveModel (and by extension the entire framework)
58
64
  # being dependent on a binary library.
@@ -63,9 +69,40 @@ module ActiveModel
63
69
  raise
64
70
  end
65
71
 
66
- include InstanceMethodsOnActivation
72
+ attr_reader attribute
73
+
74
+ define_method("#{attribute}=") do |unencrypted_password|
75
+ if unencrypted_password.nil?
76
+ self.send("#{attribute}_digest=", nil)
77
+ elsif !unencrypted_password.empty?
78
+ instance_variable_set("@#{attribute}", unencrypted_password)
79
+ cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
80
+ self.send("#{attribute}_digest=", BCrypt::Password.create(unencrypted_password, cost: cost))
81
+ end
82
+ end
83
+
84
+ define_method("#{attribute}_confirmation=") do |unencrypted_password|
85
+ instance_variable_set("@#{attribute}_confirmation", unencrypted_password)
86
+ end
87
+
88
+ # Returns +self+ if the password is correct, otherwise +false+.
89
+ #
90
+ # class User < ActiveRecord::Base
91
+ # has_secure_password validations: false
92
+ # end
93
+ #
94
+ # user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
95
+ # user.save
96
+ # user.authenticate_password('notright') # => false
97
+ # user.authenticate_password('mUc3m00RsqyRe') # => user
98
+ define_method("authenticate_#{attribute}") do |unencrypted_password|
99
+ attribute_digest = send("#{attribute}_digest")
100
+ BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
101
+ end
102
+
103
+ alias_method :authenticate, :authenticate_password if attribute == :password
67
104
 
68
- if options.fetch(:validations, true)
105
+ if validations
69
106
  include ActiveModel::Validations
70
107
 
71
108
  # This ensures the model has a password by checking whether the password_digest
@@ -73,57 +110,13 @@ module ActiveModel
73
110
  # when there is an error, the message is added to the password attribute instead
74
111
  # so that the error message will make sense to the end-user.
75
112
  validate do |record|
76
- record.errors.add(:password, :blank) unless record.password_digest.present?
113
+ record.errors.add(attribute, :blank) unless record.send("#{attribute}_digest").present?
77
114
  end
78
115
 
79
- validates_length_of :password, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
80
- validates_confirmation_of :password, allow_blank: true
116
+ validates_length_of attribute, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
117
+ validates_confirmation_of attribute, allow_blank: true
81
118
  end
82
119
  end
83
120
  end
84
-
85
- module InstanceMethodsOnActivation
86
- # Returns +self+ if the password is correct, otherwise +false+.
87
- #
88
- # class User < ActiveRecord::Base
89
- # has_secure_password validations: false
90
- # end
91
- #
92
- # user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
93
- # user.save
94
- # user.authenticate('notright') # => false
95
- # user.authenticate('mUc3m00RsqyRe') # => user
96
- def authenticate(unencrypted_password)
97
- BCrypt::Password.new(password_digest).is_password?(unencrypted_password) && self
98
- end
99
-
100
- attr_reader :password
101
-
102
- # Encrypts the password into the +password_digest+ attribute, only if the
103
- # new password is not empty.
104
- #
105
- # class User < ActiveRecord::Base
106
- # has_secure_password validations: false
107
- # end
108
- #
109
- # user = User.new
110
- # user.password = nil
111
- # user.password_digest # => nil
112
- # user.password = 'mUc3m00RsqyRe'
113
- # user.password_digest # => "$2a$10$4LEA7r4YmNHtvlAvHhsYAeZmk/xeUVtMTYqwIvYY76EW5GUqDiP4."
114
- def password=(unencrypted_password)
115
- if unencrypted_password.nil?
116
- self.password_digest = nil
117
- elsif !unencrypted_password.empty?
118
- @password = unencrypted_password
119
- cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
120
- self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost)
121
- end
122
- end
123
-
124
- def password_confirmation=(unencrypted_password)
125
- @password_confirmation = unencrypted_password
126
- end
127
- end
128
121
  end
129
122
  end
@@ -26,13 +26,13 @@ module ActiveModel
26
26
  # user = User.find(1)
27
27
  # user.as_json
28
28
  # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
29
- # # "created_at" => "2006/08/01", "awesome" => true}
29
+ # # "created_at" => "2006-08-01T17:27:133.000Z", "awesome" => true}
30
30
  #
31
31
  # ActiveRecord::Base.include_root_in_json = true
32
32
  #
33
33
  # user.as_json
34
34
  # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
35
- # # "created_at" => "2006/08/01", "awesome" => true } }
35
+ # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } }
36
36
  #
37
37
  # This behavior can also be achieved by setting the <tt>:root</tt> option
38
38
  # to +true+ as in:
@@ -40,7 +40,7 @@ module ActiveModel
40
40
  # user = User.find(1)
41
41
  # user.as_json(root: true)
42
42
  # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
43
- # # "created_at" => "2006/08/01", "awesome" => true } }
43
+ # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } }
44
44
  #
45
45
  # Without any +options+, the returned Hash will include all the model's
46
46
  # attributes.
@@ -48,7 +48,7 @@ module ActiveModel
48
48
  # user = User.find(1)
49
49
  # user.as_json
50
50
  # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
51
- # # "created_at" => "2006/08/01", "awesome" => true}
51
+ # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true}
52
52
  #
53
53
  # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit
54
54
  # the attributes included, and work similar to the +attributes+ method.
@@ -63,14 +63,14 @@ module ActiveModel
63
63
  #
64
64
  # user.as_json(methods: :permalink)
65
65
  # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
66
- # # "created_at" => "2006/08/01", "awesome" => true,
66
+ # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
67
67
  # # "permalink" => "1-konata-izumi" }
68
68
  #
69
69
  # To include associations use <tt>:include</tt>:
70
70
  #
71
71
  # user.as_json(include: :posts)
72
72
  # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
73
- # # "created_at" => "2006/08/01", "awesome" => true,
73
+ # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
74
74
  # # "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" },
75
75
  # # { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] }
76
76
  #
@@ -81,7 +81,7 @@ module ActiveModel
81
81
  # only: :body } },
82
82
  # only: :title } })
83
83
  # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
84
- # # "created_at" => "2006/08/01", "awesome" => true,
84
+ # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
85
85
  # # "posts" => [ { "comments" => [ { "body" => "1st post!" }, { "body" => "Second!" } ],
86
86
  # # "title" => "Welcome to the weblog" },
87
87
  # # { "comments" => [ { "body" => "Don't think too hard" } ],
@@ -93,11 +93,12 @@ module ActiveModel
93
93
  include_root_in_json
94
94
  end
95
95
 
96
+ hash = serializable_hash(options).as_json
96
97
  if root
97
98
  root = model_name.element if root == true
98
- { root => serializable_hash(options) }
99
+ { root => hash }
99
100
  else
100
- serializable_hash(options)
101
+ hash
101
102
  end
102
103
  end
103
104