serializable_hash and serializable_add_includes

I'm trying to write a helper method similar to attr_accessible but
instead it will be used to whitelist which attributes are available for
serialization (attr_serializable). My first attempt at this was to
override serialized_hash and modify the :only option to include nothing
but serializable attributes. I'm facing a problem when nested models
are 'included' using the :include option. The :only options for the
parent model are also getting sent to the nested model(s). Here's an
example:

model1.to_json(:include => model2, :only => [:id])

First serializable_hash gets called for model1 and is passed an options
hash with :only set to an array including :id. This makes sense to me
since the :only option was specified as a top level options. Next
serializable_hash gets called for model2 and it is also passed an
options hash that include :id in the :only option. Why would :only =>
[:id] be an option for the included model (model2)? Same goes for
:methods and :except options set at the top level. I would expect the
:only , :except, and :methods options to be removed if they are not
specified directly on the included model as such:

model1.to_json(:include => {:model2 => {:only =>
[:some_other_property]}, :only => [:id])

Can someone please explain to me why it works this way.

The code in active_model/serialization.rb was changed significantly
between 3.1.3 and 3.2.0. On which version are you working? In
my case, the code in 3.2.0 did the right thing :slight_smile:

:only => [] resulted (correctly) in an empty list in 3.2.0, while in 3.1.3
it resulted in all attributes included.

These 2 commits seem to be most relevant:

https://github.com/rails/rails/commit/5b2eb64c

https://github.com/rails/rails/commit/2a663dcf

HTH,

Peter

Peter Vandenabeele wrote in post #1042445:

model1.to_json(:include => {:model2 => {:only =>
[:some_other_property]}, :only => [:id])

Can someone please explain to me why it works this way.

The code in active_model/serialization.rb was changed significantly
between 3.1.3 and 3.2.0. On which version are you working? In
my case, the code in 3.2.0 did the right thing :slight_smile:

:only => [] resulted (correctly) in an empty list in 3.2.0, while in
3.1.3
it resulted in all attributes included.

These 2 commits seem to be most relevant:

https://github.com/rails/rails/commit/5b2eb64c
https://github.com/rails/rails/commit/2a663dcf

HTH,

Peter

Thanks for the reply and the links. I am running on 3.1.3 which may be
the problem but after walking through the commit you sent I believe
3.2.0 is handling it the same way. My problem is the same options are
being used for the parent and included models.

book.to_json( :only => [:id] )

{ "id": 1 } <- expected

book.to_json(:only => [:id], :include => :chapters)

{ "id": 1, "chapters":[ { "id" : 1 } ] } <- sorta expected but shouldn't
be

book.to_json(:only => [:id, :name], :include => :chapters)

{ "id": 1, "name":"The Ruby Way", "chapters":[ { "id" : 1,
"name":"serialization" } ] } <- don't understand

Notice how name is included for the chapters as well? The reason this
happens is because :only => [:id, :name] is passed to the
serializable_hash call for both book and chapters. My argument is that
the options should only be passed to the parent model (books) but not
the included association (chapters). Let's expand upon the previous
example and say we added one more attribute to the only option.

book.to_json(:only => [:id, :name, :price], :include => :chapters)

{ "id": 1, "name":"The Ruby Way", "price":2000, "chapters":[ { "id" : 1,
"name":"serialization" } ] }

In the example above, the "price" attribute was added as an :only
option. Since book has a price the value is included in the json. The
problem is chapters doesn't have a price but the option is still passed
in during serialization anyway. Why not start clean for each included
relationship? Instead of passing the same options along, exclude the
top level options for :only, :except, and :methods when serializing
included associations.

What are your thoughts

Peter Vandenabeele wrote in post #1042445:

model1.to_json(:include => {:model2 => {:only =>

[:some_other_property]}, :only => [:id])

Can someone please explain to me why it works this way.

The code in active_model/serialization.rb was changed significantly

between 3.1.3 and 3.2.0. On which version are you working? In

my case, the code in 3.2.0 did the right thing :slight_smile:

:only => [] resulted (correctly) in an empty list in 3.2.0, while in

3.1.3

it resulted in all attributes included.

These 2 commits seem to be most relevant:

https://github.com/rails/rails/commit/5b2eb64c

https://github.com/rails/rails/commit/2a663dcf

HTH,

Peter

Thanks for the reply and the links. I am running on 3.1.3 which may be

the problem but after walking through the commit you sent I believe

3.2.0 is handling it the same way. My problem is the same options are

being used for the parent and included models.

book.to_json( :only => [:id] )

{ “id”: 1 } <- expected

book.to_json(:only => [:id], :include => :chapters)

{ “id”: 1, “chapters”:[ { “id” : 1 } ] } <- sorta expected but shouldn’t

be

book.to_json(:only => [:id, :name], :include => :chapters)

{ “id”: 1, “name”:“The Ruby Way”, “chapters”:[ { “id” : 1,

“name”:“serialization” } ] } <- don’t understand

Notice how name is included for the chapters as well? The reason this

happens is because :only => [:id, :name] is passed to the

serializable_hash call for both book and chapters.

I can confirm your observation (Rails 3.1.3):

Calling user.to_xml with the options:

{:include=>{:address=>{}}, :only=>[:city, :first_name]}

where :city is only in address and :first_name is only in user, I get

<first_name>Jan</first_name>

AND

\n Brussel\n

in the result. The latter was not expected (I did not
specify any "only options in the included :address.

The reason I never bumped into this, is that for each
to_xml of main object and associated objects, I run

through a white_list (that explicitly sets the :only list),
to protect what I will show in the API.

Specifically, this options set

Rails 3.1.3 :

book.to_json(:only => [:id], :include => {:chapters => {:only => :nothing}})

yes, the :nothing looks funny …

Rails 3.2.0 :

book.to_json(:only => [:id], :include => {:chapters => {:only => []}})

would be a work-around for your case (if you wanted nothing shown

from the chapters attributes).

My argument is that

the options should only be passed to the parent model (books) but not

the included association (chapters). Let’s expand upon the previous

example and say we added one more attribute to the only option.

book.to_json(:only => [:id, :name, :price], :include => :chapters)

{ “id”: 1, “name”:“The Ruby Way”, “price”:2000, “chapters”:[ { “id” : 1,

“name”:“serialization” } ] }

In the example above, the “price” attribute was added as an :only

option. Since book has a price the value is included in the json. The

problem is chapters doesn’t have a price but the option is still passed

in during serialization anyway. Why not start clean for each included

relationship? Instead of passing the same options along, exclude the

top level options for :only, :except, and :methods when serializing

included associations.

What are your thoughts

I agree (the current behavior seems a bug to me). I did not test this
specific behavior in Rails 3.2.0

HTH,

Peter

Peter Vandenabeele wrote in post #1042445:

model1.to_json(:include => {:model2 => {:only =>

[:some_other_property]}, :only => [:id])

Can someone please explain to me why it works this way.

The code in active_model/serialization.rb was changed significantly

between 3.1.3 and 3.2.0. On which version are you working? In

my case, the code in 3.2.0 did the right thing :slight_smile:

:only => [] resulted (correctly) in an empty list in 3.2.0, while in

3.1.3

it resulted in all attributes included.

These 2 commits seem to be most relevant:

https://github.com/rails/rails/commit/5b2eb64c

https://github.com/rails/rails/commit/2a663dcf

HTH,

Peter

Thanks for the reply and the links. I am running on 3.1.3 which may be

the problem but after walking through the commit you sent I believe

3.2.0 is handling it the same way.

Could it be that is actually solved in Rails 3.2.0 ?
Did you effectively test with 3.2.0?

I just tested now in Rails 3.2.0 and found this:

1.9.3-p0 :012 > p.to_xml(:only => :name, :include => :child)
=> “<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n dad\n \n <created-at type=“datetime”>2012-01-24T10:05:43Z\n <id type=“integer”>1\n Sarah\n <parent-id type=“integer”>1\n <updated-at type=“datetime”>2012-01-24T10:05:43Z\n \n\n”

This seems to indicate that for the child, all attributes are shown,
independent of the :only on the parent.

There is recent discussion about this on
https://github.com/rails/rails/issues/714

My argument is that

the options should only be passed to the parent model (books) but not

the included association (chapters). Let’s expand upon the previous

example and say we added one more attribute to the only option.

book.to_json(:only => [:id, :name, :price], :include => :chapters)

{ “id”: 1, “name”:“The Ruby Way”, “price”:2000, “chapters”:[ { “id” : 1,

“name”:“serialization” } ] }

In the example above, the “price” attribute was added as an :only

option. Since book has a price the value is included in the json. The

problem is chapters doesn’t have a price but the option is still passed

in during serialization anyway. Why not start clean for each included

relationship? Instead of passing the same options along, exclude the

top level options for :only, :except, and :methods when serializing

included associations.

What are your thoughts

I agree (the current behavior seems a bug to me). I did not test this
specific behavior in Rails 3.2.0

I have the impression now that the issue is resolved in 3.2.0.

Peter