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:

Revert "Implement ArraySerializer and move old serialization API to a… · rails/rails@5b2eb64 · GitHub 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