Skip to content

WIP - Deserializer implementation #950

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

joaomdmoura
Copy link
Member

We decided that it's time to work with deserialization. 🎉🎉 🎉

AMS aim to bring convention over configuration to JSON generation, but as it evolves into a more complete solution to build great APIs it makes sense to also handle deserialization.

The initial proposal of Deserialization is to convert your json input into something the ActiveRecord understands.

It will play really nice with json-api, enabling us to follow it conventions when creating and updating objects using AMS.

How it works:

  • Automatically handle strong parameters, permitting the parameters that should be deserialized
  • It identify the attributes that should be deserialized by your attributes method inside an existing serializer
  • Allows you to override the parameters that should be serialized (ex. deserialize :title, :body)
  • Work with the existing adapters conventions
  • JSON
    • Working CRUD
    • Parsing attributes
    • Parsing associations automatically
  • JSON API
    • Working CRUD
    • Parsing attributes
    • Parsing associations automatically

Usage:

post_serialization.rb

class PostSerialization < ActiveModel::Serializer
  attributes :id, :title, :body
  belongs_to :user, root: :rows
  params :title, :body
end

post_controller.rb

class PostsController < ApplicationController

  def create
    @post = Post.new(post_params)

    if @post.save
      render json: @post, status: :created, location: @post
    else
      render json: @post.errors, status: :unprocessable_entity
    end
  end

  private

  def post_params
    PostSerialization.deserialize(params)
  end
end

This is a Working In Progress yet 😝

@joaomdmoura joaomdmoura self-assigned this Jun 11, 2015
@joaomdmoura joaomdmoura added this to the 0.10 milestone Jun 11, 2015
@joaomdmoura joaomdmoura changed the title Deserializer implementation WIP - Deserializer implementation Jun 11, 2015
@joaomdmoura joaomdmoura force-pushed the deserializer-implementation branch 7 times, most recently from af2dc47 to 9b9177a Compare June 11, 2015 21:31
@kurko
Copy link
Member

kurko commented Jun 11, 2015

Would it make sense to include ActiveModel::Deserializer on load, instead of manually doing it in every controller?

@saneshark
Copy link

👍

Some thoughts I have:

  1. introducing some sort of type validation around strong parameters
  2. some mechanism to sanitize or make uniform, during serialization for things such as dates. Ideally they should always be passed in as iso8601 (I assume this is what deserialize could be used for)
  3. unique exceptions that can be rescued appropriately when deserialization fails, params are invalid

How will this work with PORO objects, presenter objects that accept parameters for multiple underlying objects as part of a renormalization of how the underlying data is stored vs presented?

@bf4
Copy link
Member

bf4 commented Jun 12, 2015

I guess if we're going to add another sort of transformation, I'd really like to be able to generate the serializer/deserializer from a json_schema such what a hypermedia client library like heroics or translator such as swagger do.

And/or maybe use transformation 'service' object such as https://github.com/solnic/transproc

@joaomdmoura joaomdmoura force-pushed the deserializer-implementation branch from 9b9177a to b4c7911 Compare June 12, 2015 05:56
@joaomdmoura joaomdmoura force-pushed the deserializer-implementation branch 9 times, most recently from 767d5ba to f15071b Compare July 2, 2015 06:02
@joaomdmoura
Copy link
Member Author

Hey ppl, just letting you know I've being working on this. I've just updated the initial PR.

You might notice that the implementation changed a little bit. We've being improving it with Rails Team. 😄

@saneshark thanks for share you thoughts, indeed we will be able to help user into sanitizing their data too with this. Maybe in next steps. For now, deserialization would still work with PORO project because it just return and instance of the object.

@bf4 Indeed, as I said, deserialization will enable us to do a lot more, new features that will make it even easier to write APIs, I don't think the PR will handle those features right now, but some of those might follow this one, once finished and merged.

btw this is still WIP 😄 cc @spastorino

@joaomdmoura joaomdmoura force-pushed the deserializer-implementation branch 3 times, most recently from 8f90729 to 10141e5 Compare July 5, 2015 04:52
@joaomdmoura joaomdmoura force-pushed the deserializer-implementation branch from bbae1fe to 7448e2a Compare August 26, 2015 06:19
@joaomdmoura
Copy link
Member Author

It took longer than I expected but this is rebased, finally.

class ExplicitSerializerTest < ActionController::TestCase
class ExplicitSerializerTestController < ActionController::Base
def render_using_explicit_serializer
@profile = Profile.new(name: 'Name 1',
description: 'Description 1',
comments: 'Comments 1')
render json: @profile, serializer: ProfilePreviewSerializer
render json: @profile, serializer: ProfilePreviewSerializer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for this extra space?

@GCorbel
Copy link

GCorbel commented Sep 4, 2015

It seems to work for me. Thanks for your great job!

@beauby
Copy link
Contributor

beauby commented Sep 8, 2015

So, what's the status on this one?

@Jeehut
Copy link

Jeehut commented Sep 11, 2015

Yeah, same question here, when will the deserializer implementation for the JSON API adapter be merged into the master? Maybe it should be merged before implementation for the JSON adapter is ready – otherwise we'll need to wait even longer until we can use this on the official gem.

Also I suggest to include some usage to the README.

@Jeehut
Copy link

Jeehut commented Sep 11, 2015

I have also just read a lot more into this and tried to get deserialization to work on my real-world app that I'm developing right now – but there's at least two things that don't work yet:

  1. The deserialize method doesn't work properly (both with and without whitelisted password)
  2. A related object doesn't get build alongside my object (if no id is specified it should be build)

Although the last point isn't necessarily a part of the JSON API specification (as far as I could see) there still needs to be a solution on how to deal with bidirectional presence validations like this: A User who needs to have at least one spoken_languages set and a SpokenLanguage which belongs to a user and therefore needs him set, too. I could, of course, remove validations to get it working, but that's not the way to go, is it?

You can check out the entire project for a working example here, but I'm quoting the parts in question below, too, too keep things clean. When trying to fix this it's probably easiest though to just checkout the projects json-api branch and run the rspec command.


Here's my Serializers:

##
# Describes serialization of a User to a JSON format and vice versa according to the JSON API specification.
class UserSerializer < ActiveModel::Serializer
  attributes :id, :first_name, :last_name, :nickname, :email, :gender, :birthday, :country_code, :image

  has_many :spoken_languages
end

##
# Describes serialization of a SpokenLanguage to a JSON format and vice versa according to the JSON API specification.
class SpokenLanguageSerializer < ActiveModel::Serializer
  attributes :id, :language_code

  belongs_to :user
end

Here's my Controller:

##
# The API V1 controller managing users, logins/logouts, registrations, password resets and the like.
class Api::V1::UsersController < Api::V1Controller

  # Creates a new user with given parameters and renders a JSON response.
  #
  # @return [void]
  def create
    @user = User.new(user_params)
    if @user.save
      render json: @user, status: :created, location: @user
    else
      render json: { errors: @user.errors }, status: :unprocessable_entity
    end
  end

private

  # Deserializes the users parameters into ActiveRecord accessable format and filters unpermitted values.
  #
  # @return [Hash] permitted attributes for a user
  def user_params
    UserSerializer.deserialize(params, [:password])
  end

end

And here's my spec:

it 'responds with created including data for valid data' do
  post :create, {
    data: {
      type: 'users',
      attributes: attributes_for(:user), # using FactoryGirl, factories are working! (ling-approved)
      relationships: {
        spoken_languages: { data: [{ type: 'spoken_languages', attributes: attributes_for(:spoken_language) }] }
      }
    }
  }
  expect(response).to have_http_status(:created)
  # more expectations
end

@beauby
Copy link
Contributor

beauby commented Sep 14, 2015

@Dschee Thanks for the thoughts you put into this one.
Concerning your first point, the feature is clearly not ready for production yet. You could help by opening an issue stating how and when the deserialization fails.
Concerning your second point, the JSONAPI spec explicitly states:

If a relationship is provided in the relationships member of the resource object, its value MUST be a relationship object with a data member. The value of this key represents the linkage the new resource is to have. (http://jsonapi.org/format/#crud-creating)

and

Resource linkage MUST be represented as one of the following:

  • null for empty to-one relationships.
  • an empty array ([]) for empty to-many relationships.
  • a single resource identifier object for non-empty to-one relationships.
  • an array of resource identifier objects for non-empty to-many relationships.

(http://jsonapi.org/format/#document-resource-object-linkage)

Therefore, a related object should not be built. Only existing objects should be supplied. In your case, you could either change your API to comply to the spec, or build a custom adapter based on the JSONAPI one.
However, it would probably make sense with the JSON adapter.

@SandNerd
Copy link

SandNerd commented Jan 8, 2016

Not sure what's the status here as of now, so I just wanted to let those like me who were looking for AMS-y deserializing about an alternative Deserializer gem. It pretty much follows the API conventions of AMS. Hope it helps others and it might even benefit the core devs in picking/not picking an approach, API, etc.

@beauby
Copy link
Contributor

beauby commented Jan 9, 2016

Thanks for the heads up @sahal2080!

@beauby
Copy link
Contributor

beauby commented Jan 21, 2016

Should we close this now that #1248 has been merged?

@kurko
Copy link
Member

kurko commented Jan 25, 2016

@joaomdmoura?

@kurko kurko closed this Jan 25, 2016
@joaomdmoura
Copy link
Member Author

@kurko we implemented a new one, that was merged already and will be on RC4 soon 😄

@benlieb
Copy link

benlieb commented Oct 5, 2019

Not sure what's the status here as of now, so I just wanted to let those like me who were looking for AMS-y deserializing about an alternative Deserializer gem. It pretty much follows the API conventions of AMS. Hope it helps others and it might even benefit the core devs in picking/not picking an approach, API, etc.

This link is now here Deserializer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.