1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

change AMS::JSON.include_root_in_json default value to false

Changes:

* Update `include_root_in_json` default value to false for default value
  to false for `ActiveModel::Serializers::JSON`.
* Remove unnecessary change to include_root_in_json option in
  wrap_parameters template.
* Update `as_json` documentation.
* Fix JSONSerialization tests.

Problem:

It's confusing that AM serializers behave differently from AR,
even when AR objects include AM serializers module.

    class User < ActiveRecord::Base; end

    class Person
      include ActiveModel::Model
      include ActiveModel::AttributeMethods
      include ActiveModel::Serializers::JSON

      attr_accessor :name, :age

      def attributes
        instance_values
      end
    end

    user.as_json
    => {"id"=>1, "name"=>"Konata Izumi", "age"=>16, "awesome"=>true}
    # root is not included

    person.as_json
    => {"person"=>{"name"=>"Francesco", "age"=>22}}
    # root is included

    ActiveRecord::Base.include_root_in_json
    => false

    Person.include_root_in_json
    => true

    # different default values for include_root_in_json

Proposal:

Change the default value of AM serializers to false, update
the misleading documentation and remove unnecessary change
to false of include_root_in_json option with AR objects.

    class User < ActiveRecord::Base; end

    class Person
      include ActiveModel::Model
      include ActiveModel::AttributeMethods
      include ActiveModel::Serializers::JSON

      attr_accessor :name, :age

      def attributes
        instance_values
      end
    end

    user.as_json
    => {"id"=>1, "name"=>"Konata Izumi", "age"=>16, "awesome"=>true}
    # root is not included

    person.as_json
    => {"name"=>"Francesco", "age"=>22}
    # root is not included

    ActiveRecord::Base.include_root_in_json
    => false

    Person.include_root_in_json
    => false

    # same behaviour, more consistent

Fixes #6578.
This commit is contained in:
Francesco Rodriguez 2012-06-06 01:11:39 -05:00
parent d10eb69964
commit ab11a2780f
4 changed files with 115 additions and 98 deletions

View file

@ -1,5 +1,32 @@
## Rails 4.0.0 (unreleased) ## ## Rails 4.0.0 (unreleased) ##
* Changed `AM::Serializers::JSON.include_root_in_json' default value to false.
Now, AM Serializers and AR objects have the same default behaviour. Fixes #6578.
class User < ActiveRecord::Base; end
class Person
include ActiveModel::Model
include ActiveModel::AttributeMethods
include ActiveModel::Serializers::JSON
attr_accessor :name, :age
def attributes
instance_values
end
end
user.as_json
=> {"id"=>1, "name"=>"Konata Izumi", "age"=>16, "awesome"=>true}
# root is not included
person.as_json
=> {"name"=>"Francesco", "age"=>22}
# root is not included
*Francesco Rodriguez*
* Passing false hash values to `validates` will no longer enable the corresponding validators *Steve Purcell* * Passing false hash values to `validates` will no longer enable the corresponding validators *Steve Purcell*
* `ConfirmationValidator` error messages will attach to `:#{attribute}_confirmation` instead of `attribute` *Brian Cardarella* * `ConfirmationValidator` error messages will attach to `:#{attribute}_confirmation` instead of `attribute` *Brian Cardarella*

View file

@ -13,80 +13,80 @@ module ActiveModel
extend ActiveModel::Configuration extend ActiveModel::Configuration
config_attribute :include_root_in_json config_attribute :include_root_in_json
self.include_root_in_json = true self.include_root_in_json = false
end end
# Returns a hash representing the model. Some configuration can be # Returns a hash representing the model. Some configuration can be
# passed through +options+. # passed through +options+.
# #
# The option <tt>include_root_in_json</tt> controls the top-level behavior # The option <tt>include_root_in_json</tt> controls the top-level behavior
# of +as_json+. If true (the default) +as_json+ will emit a single root # of +as_json+. If true +as_json+ will emit a single root node named after
# node named after the object's type. For example: # the object's type. The default value for <tt>include_root_in_json</tt>
# option is +false+.
# #
# user = User.find(1) # user = User.find(1)
# user.as_json # user.as_json
# # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16, # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
# "created_at": "2006/08/01", "awesome": true} } # # "created_at" => "2006/08/01", "awesome" => true}
#
# ActiveRecord::Base.include_root_in_json = true
# #
# ActiveRecord::Base.include_root_in_json = false
# user.as_json # user.as_json
# # => {"id": 1, "name": "Konata Izumi", "age": 16, # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
# "created_at": "2006/08/01", "awesome": true} # # "created_at" => "2006/08/01", "awesome" => true } }
# #
# This behavior can also be achieved by setting the <tt>:root</tt> option to +false+ as in: # This behavior can also be achieved by setting the <tt>:root</tt> option
# to +true+ as in:
# #
# user = User.find(1) # user = User.find(1)
# user.as_json(root: false) # user.as_json(root: true)
# # => {"id": 1, "name": "Konata Izumi", "age": 16, # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
# "created_at": "2006/08/01", "awesome": true} # # "created_at" => "2006/08/01", "awesome" => true } }
#
# The remainder of the examples in this section assume include_root_in_json is set to
# <tt>false</tt>.
# #
# Without any +options+, the returned Hash will include all the model's # Without any +options+, the returned Hash will include all the model's
# attributes. For example: # attributes.
# #
# user = User.find(1) # user = User.find(1)
# user.as_json # user.as_json
# # => {"id": 1, "name": "Konata Izumi", "age": 16, # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
# "created_at": "2006/08/01", "awesome": true} # # "created_at" => "2006/08/01", "awesome" => true}
# #
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit
# included, and work similar to the +attributes+ method. For example: # the attributes included, and work similar to the +attributes+ method.
# #
# user.as_json(:only => [ :id, :name ]) # user.as_json(only: [:id, :name])
# # => {"id": 1, "name": "Konata Izumi"} # # => { "id" => 1, "name" => "Konata Izumi" }
# #
# user.as_json(:except => [ :id, :created_at, :age ]) # user.as_json(except: [:id, :created_at, :age])
# # => {"name": "Konata Izumi", "awesome": true} # # => { "name" => "Konata Izumi", "awesome" => true }
# #
# To include the result of some method calls on the model use <tt>:methods</tt>: # To include the result of some method calls on the model use <tt>:methods</tt>:
# #
# user.as_json(:methods => :permalink) # user.as_json(methods: :permalink)
# # => {"id": 1, "name": "Konata Izumi", "age": 16, # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
# "created_at": "2006/08/01", "awesome": true, # # "created_at" => "2006/08/01", "awesome" => true,
# "permalink": "1-konata-izumi"} # # "permalink" => "1-konata-izumi" }
# #
# To include associations use <tt>:include</tt>: # To include associations use <tt>:include</tt>:
# #
# user.as_json(:include => :posts) # user.as_json(include: :posts)
# # => {"id": 1, "name": "Konata Izumi", "age": 16, # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
# "created_at": "2006/08/01", "awesome": true, # # "created_at" => "2006/08/01", "awesome" => true,
# "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"}, # # "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" },
# {"id": 2, author_id: 1, "title": "So I was thinking"}]} # # { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] }
# #
# Second level and higher order associations work as well: # Second level and higher order associations work as well:
# #
# user.as_json(:include => { :posts => { # user.as_json(include: { posts: {
# :include => { :comments => { # include: { comments: {
# :only => :body } }, # only: :body } },
# :only => :title } }) # only: :title } })
# # => {"id": 1, "name": "Konata Izumi", "age": 16, # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
# "created_at": "2006/08/01", "awesome": true, # # "created_at" => "2006/08/01", "awesome" => true,
# "posts": [{"comments": [{"body": "1st post!"}, {"body": "Second!"}], # # "posts" => [ { "comments" => [ { "body" => "1st post!" }, { "body" => "Second!" } ],
# "title": "Welcome to the weblog"}, # # "title" => "Welcome to the weblog" },
# {"comments": [{"body": "Don't think too hard"}], # # { "comments" => [ { "body" => "Don't think too hard" } ],
# "title": "So I was thinking"}]} # # "title" => "So I was thinking" } ] }
def as_json(options = nil) def as_json(options = nil)
root = include_root_in_json root = include_root_in_json
root = options[:root] if options.try(:key?, :root) root = options[:root] if options.try(:key?, :root)
@ -106,4 +106,4 @@ module ActiveModel
end end
end end
end end
end end

View file

@ -31,7 +31,24 @@ class JsonSerializationTest < ActiveModel::TestCase
@contact.preferences = { 'shows' => 'anime' } @contact.preferences = { 'shows' => 'anime' }
end end
test "should include root in json" do def teardown
# set to the default value
Contact.include_root_in_json = false
end
test "should not include root in json (class method)" do
json = @contact.to_json
assert_no_match %r{^\{"contact":\{}, json
assert_match %r{"name":"Konata Izumi"}, json
assert_match %r{"age":16}, json
assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
assert_match %r{"awesome":true}, json
assert_match %r{"preferences":\{"shows":"anime"\}}, json
end
test "should include root in json if include_root_in_json is true" do
Contact.include_root_in_json = true
json = @contact.to_json json = @contact.to_json
assert_match %r{^\{"contact":\{}, json assert_match %r{^\{"contact":\{}, json
@ -42,41 +59,19 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_match %r{"preferences":\{"shows":"anime"\}}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json
end end
test "should not include root in json (class method)" do
begin
Contact.include_root_in_json = false
json = @contact.to_json
assert_no_match %r{^\{"contact":\{}, json
assert_match %r{"name":"Konata Izumi"}, json
assert_match %r{"age":16}, json
assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
assert_match %r{"awesome":true}, json
assert_match %r{"preferences":\{"shows":"anime"\}}, json
ensure
Contact.include_root_in_json = true
end
end
test "should include root in json (option) even if the default is set to false" do test "should include root in json (option) even if the default is set to false" do
begin json = @contact.to_json(root: true)
Contact.include_root_in_json = false assert_match %r{^\{"contact":\{}, json
json = @contact.to_json(:root => true)
assert_match %r{^\{"contact":\{}, json
ensure
Contact.include_root_in_json = true
end
end end
test "should not include root in json (option)" do test "should not include root in json (option)" do
json = @contact.to_json(root: false)
json = @contact.to_json(:root => false)
assert_no_match %r{^\{"contact":\{}, json assert_no_match %r{^\{"contact":\{}, json
end end
test "should include custom root in json" do test "should include custom root in json" do
json = @contact.to_json(:root => 'json_contact') json = @contact.to_json(root: 'json_contact')
assert_match %r{^\{"json_contact":\{}, json assert_match %r{^\{"json_contact":\{}, json
assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"name":"Konata Izumi"}, json
@ -107,7 +102,7 @@ class JsonSerializationTest < ActiveModel::TestCase
end end
test "should allow attribute filtering with except" do test "should allow attribute filtering with except" do
json = @contact.to_json(:except => [:name, :age]) json = @contact.to_json(except: [:name, :age])
assert_no_match %r{"name":"Konata Izumi"}, json assert_no_match %r{"name":"Konata Izumi"}, json
assert_no_match %r{"age":16}, json assert_no_match %r{"age":16}, json
@ -122,10 +117,10 @@ class JsonSerializationTest < ActiveModel::TestCase
def @contact.favorite_quote; "Constraints are liberating"; end def @contact.favorite_quote; "Constraints are liberating"; end
# Single method. # Single method.
assert_match %r{"label":"Has cheezburger"}, @contact.to_json(:only => :name, :methods => :label) assert_match %r{"label":"Has cheezburger"}, @contact.to_json(only: :name, methods: :label)
# Both methods. # Both methods.
methods_json = @contact.to_json(:only => :name, :methods => [:label, :favorite_quote]) methods_json = @contact.to_json(only: :name, methods: [:label, :favorite_quote])
assert_match %r{"label":"Has cheezburger"}, methods_json assert_match %r{"label":"Has cheezburger"}, methods_json
assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json
end end
@ -143,14 +138,15 @@ class JsonSerializationTest < ActiveModel::TestCase
end end
test "serializable_hash should not modify options passed in argument" do test "serializable_hash should not modify options passed in argument" do
options = { :except => :name } options = { except: :name }
@contact.serializable_hash(options) @contact.serializable_hash(options)
assert_nil options[:only] assert_nil options[:only]
assert_equal :name, options[:except] assert_equal :name, options[:except]
end end
test "as_json should return a hash" do test "as_json should return a hash if include_root_in_json is true" do
Contact.include_root_in_json = true
json = @contact.as_json json = @contact.as_json
assert_kind_of Hash, json assert_kind_of Hash, json
@ -160,7 +156,7 @@ class JsonSerializationTest < ActiveModel::TestCase
end end
end end
test "from_json should set the object's attributes" do test "from_json should work without a root (class attribute)" do
json = @contact.to_json json = @contact.to_json
result = Contact.new.from_json(json) result = Contact.new.from_json(json)
@ -172,7 +168,7 @@ class JsonSerializationTest < ActiveModel::TestCase
end end
test "from_json should work without a root (method parameter)" do test "from_json should work without a root (method parameter)" do
json = @contact.to_json(:root => false) json = @contact.to_json
result = Contact.new.from_json(json, false) result = Contact.new.from_json(json, false)
assert_equal result.name, @contact.name assert_equal result.name, @contact.name
@ -182,24 +178,19 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_equal result.preferences, @contact.preferences assert_equal result.preferences, @contact.preferences
end end
test "from_json should work without a root (class attribute)" do test "from_json should work with a root (method parameter)" do
begin json = @contact.to_json(root: :true)
Contact.include_root_in_json = false result = Contact.new.from_json(json, true)
json = @contact.to_json
result = Contact.new.from_json(json)
assert_equal result.name, @contact.name assert_equal result.name, @contact.name
assert_equal result.age, @contact.age assert_equal result.age, @contact.age
assert_equal Time.parse(result.created_at), @contact.created_at assert_equal Time.parse(result.created_at), @contact.created_at
assert_equal result.awesome, @contact.awesome assert_equal result.awesome, @contact.awesome
assert_equal result.preferences, @contact.preferences assert_equal result.preferences, @contact.preferences
ensure
Contact.include_root_in_json = true
end
end end
test "custom as_json should be honored when generating json" do test "custom as_json should be honored when generating json" do
def @contact.as_json(options); { :name => name, :created_at => created_at }; end def @contact.as_json(options); { name: name, created_at: created_at }; end
json = @contact.to_json json = @contact.to_json
assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"name":"Konata Izumi"}, json
@ -209,7 +200,7 @@ class JsonSerializationTest < ActiveModel::TestCase
end end
test "custom as_json options should be extendible" do test "custom as_json options should be extendible" do
def @contact.as_json(options = {}); super(options.merge(:only => [:name])); end def @contact.as_json(options = {}); super(options.merge(only: [:name])); end
json = @contact.to_json json = @contact.to_json
assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"name":"Konata Izumi"}, json
@ -217,5 +208,4 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_no_match %r{"awesome":}, json assert_no_match %r{"awesome":}, json
assert_no_match %r{"preferences":}, json assert_no_match %r{"preferences":}, json
end end
end end

View file

@ -9,8 +9,8 @@ ActiveSupport.on_load(:action_controller) do
end end
<%- unless options.skip_active_record? -%> <%- unless options.skip_active_record? -%>
# Disable root element in JSON by default. # To enable root element in JSON for ActiveRecord objects.
ActiveSupport.on_load(:active_record) do # ActiveSupport.on_load(:active_record) do
self.include_root_in_json = false # self.include_root_in_json = true
end # end
<%- end -%> <%- end -%>