Giter Site home page Giter Site logo

active_model_serializers's Introduction

Rails::API

Build Status

IMPORTANT: Rails::API has been merged into Rails

Rails::API is a subset of a normal Rails application, created for applications that don't require all functionality that a complete Rails application provides. It is a bit more lightweight, and consequently a bit faster than a normal Rails application. The main example for its usage is in API applications only, where you usually don't need the entire Rails middleware stack nor template generation.

Using Rails for API-only Apps

This is a quick walk-through to help you get up and running with Rails::API to create API-only Apps, covering:

  • What Rails::API provides for API-only applications
  • How to decide which middlewares you will want to include
  • How to decide which modules to use in your controller

What is an API app?

Traditionally, when people said that they used Rails as an "API", they meant providing a programmatically accessible API alongside their web application. For example, GitHub provides an API that you can use from your own custom clients.

With the advent of client-side frameworks, more developers are using Rails to build a backend that is shared between their web application and other native applications.

For example, Twitter uses its public API in its web application, which is built as a static site that consumes JSON resources.

Instead of using Rails to generate dynamic HTML that will communicate with the server through forms and links, many developers are treating their web application as just another client, consuming a simple JSON API.

This guide covers building a Rails application that serves JSON resources to an API client or client-side framework.

Why use Rails for JSON APIs?

The first question a lot of people have when thinking about building a JSON API using Rails is: "isn't using Rails to spit out some JSON overkill? Shouldn't I just use something like Sinatra?"

For very simple APIs, this may be true. However, even in very HTML-heavy applications, most of an application's logic is actually outside of the view layer.

The reason most people use Rails is that it provides a set of defaults that allows us to get up and running quickly without having to make a lot of trivial decisions.

Let's take a look at some of the things that Rails provides out of the box that are still applicable to API applications.

Handled at the middleware layer:

  • Reloading: Rails applications support transparent reloading. This works even if your application gets big and restarting the server for every request becomes non-viable.
  • Development Mode: Rails application come with smart defaults for development, making development pleasant without compromising production-time performance.
  • Test Mode: Ditto test mode.
  • Logging: Rails applications log every request, with a level of verbosity appropriate for the current mode. Rails logs in development include information about the request environment, database queries, and basic performance information.
  • Security: Rails detects and thwarts IP spoofing attacks and handles cryptographic signatures in a timing attack aware way. Don't know what an IP spoofing attack or a timing attack is? Exactly.
  • Parameter Parsing: Want to specify your parameters as JSON instead of as a URL-encoded String? No problem. Rails will decode the JSON for you and make it available in params. Want to use nested URL-encoded params? That works too.
  • Conditional GETs: Rails handles conditional GET, (ETag and Last-Modified), processing request headers and returning the correct response headers and status code. All you need to do is use the stale? check in your controller, and Rails will handle all of the HTTP details for you.
  • Caching: If you use dirty? with public cache control, Rails will automatically cache your responses. You can easily configure the cache store.
  • HEAD requests: Rails will transparently convert HEAD requests into GET requests, and return just the headers on the way out. This makes HEAD work reliably in all Rails APIs.

While you could obviously build these up in terms of existing Rack middlewares, I think this list demonstrates that the default Rails middleware stack provides a lot of value, even if you're "just generating JSON".

Handled at the ActionPack layer:

  • Resourceful Routing: If you're building a RESTful JSON API, you want to be using the Rails router. Clean and conventional mapping from HTTP to controllers means not having to spend time thinking about how to model your API in terms of HTTP.
  • URL Generation: The flip side of routing is URL generation. A good API based on HTTP includes URLs (see the GitHub gist API for an example).
  • Header and Redirection Responses: head :no_content and redirect_to user_url(current_user) come in handy. Sure, you could manually add the response headers, but why?
  • Basic, Digest and Token Authentication: Rails comes with out-of-the-box support for three kinds of HTTP authentication.
  • Instrumentation: Rails 3.0 added an instrumentation API that will trigger registered handlers for a variety of events, such as action processing, sending a file or data, redirection, and database queries. The payload of each event comes with relevant information (for the action processing event, the payload includes the controller, action, params, request format, request method and the request's full path).
  • Generators: This may be passé for advanced Rails users, but it can be nice to generate a resource and get your model, controller, test stubs, and routes created for you in a single command.
  • Plugins: Many third-party libraries come with support for Rails that reduces or eliminates the cost of setting up and gluing together the library and the web framework. This includes things like overriding default generators, adding rake tasks, and honoring Rails choices (like the logger and cache backend).

Of course, the Rails boot process also glues together all registered components. For example, the Rails boot process is what uses your config/database.yml file when configuring ActiveRecord.

The short version is: you may not have thought about which parts of Rails are still applicable even if you remove the view layer, but the answer turns out to be "most of it".

The Basic Configuration

If you're building a Rails application that will be an API server first and foremost, you can start with a more limited subset of Rails and add in features as needed.

NOTE: rails-api only supports Ruby 1.9.3 and above.

For new apps

Install the gem if you haven't already:

gem install rails-api

Then generate a new Rails::API app:

rails-api new my_api

This will do two main things for you:

  • Make ApplicationController inherit from ActionController::API instead of ActionController::Base. As with middleware, this will leave out any ActionController modules that provide functionality primarily used by browser applications.
  • Configure the generators to skip generating views, helpers and assets when you generate a new resource.

Rails includes all of the sub-frameworks (ActiveRecord, ActionMailer, etc) by default. Some API projects won't need them all, so at the top of config/application.rb, you can replace require 'rails/all' with specific sub-frameworks:

# config/application.rb
# require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
# require "sprockets/railtie"
require "rails/test_unit/railtie"

This can also be achieved with flags when creating a new Rails::API app:

rails-api new my_api --skip-active-record --skip-sprockets

Note: There are references to ActionMailer and ActiveRecord in the various config/environment files. If you decide to exclude any of these from your project its best to comment these out in case you need them later.

# comment out this in config/environments/development.rb
config.active_record.migration_error = :page_load
config.action_mailer.raise_delivery_errors = false

# comment out this in config/environments/test.rb
config.action_mailer.delivery_method = :test

For already existing apps

If you want to take an existing app and make it a Rails::API app, you'll have to do some quick setup manually.

Add the gem to your Gemfile:

gem 'rails-api'

And run bundle to install the gem.

Change app/controllers/application_controller.rb:

# instead of
class ApplicationController < ActionController::Base
end

# do
class ApplicationController < ActionController::API
end

And comment out the protect_from_forgery call if you are using it. (You aren't using cookie-based authentication for your API, are you?)

If you want to use the Rails default middleware stack (avoid the reduction that rails-api does), you can just add config.api_only = false to config/application.rb file.

Serialization

We suggest using ActiveModel::Serializers to serialize your ActiveModel/ActiveRecord objects into the desired response format (e.g. JSON).

Choosing Middlewares

An API application comes with the following middlewares by default.

  • ActionDispatch::DebugExceptions: Log exceptions.
  • ActionDispatch::ParamsParser: Parse XML, YAML and JSON parameters when the request's Content-Type is one of those.
  • ActionDispatch::Reloader: In development mode, support code reloading.
  • ActionDispatch::RemoteIp: Protect against IP spoofing attacks.
  • ActionDispatch::RequestId: Makes a unique request id available, sending the id to the client via the X-Request-Id header. The unique request id can be used to trace a request end-to-end and would typically end up being part of log files from multiple pieces of the stack.
  • ActionDispatch::ShowExceptions: Rescue exceptions and re-dispatch them to an exception handling application.
  • Rack::Cache: Caches responses with public Cache-Control headers using HTTP caching semantics.
  • Rack::Head: Dispatch HEAD requests as GET requests, and return only the status code and headers.
  • Rack::ConditionalGet: Supports the stale? feature in Rails controllers.
  • Rack::ETag: Automatically set an ETag on all string responses. This means that if the same response is returned from a controller for the same URL, the server will return a 304 Not Modified, even if no additional caching steps are taken. This is primarily a client-side optimization; it reduces bandwidth costs but not server processing time.
  • Rack::Lock: If your application is not marked as threadsafe (config.threadsafe!), this middleware will add a mutex around your requests.
  • Rack::Runtime: Adds a header to the response listing the total runtime of the request.
  • Rack::Sendfile: Uses a front-end server's file serving support from your Rails application.
  • Rails::Rack::Logger: Log the request started and flush all loggers after it.

Other plugins, including ActiveRecord, may add additional middlewares. In general, these middlewares are agnostic to the type of app you are building, and make sense in an API-only Rails application.

You can get a list of all middlewares in your application via:

rake middleware

Other Middlewares

Rails ships with a number of other middlewares that you might want to use in an API app, especially if one of your API clients is the browser:

  • Rack::MethodOverride: Allows the use of the _method hack to route POST requests to other verbs.
  • ActionDispatch::Cookies: Supports the cookie method in ActionController, including support for signed and encrypted cookies.
  • ActionDispatch::Flash: Supports the flash mechanism in ActionController.
  • ActionDispatch::BestStandards: Tells Internet Explorer to use the most standards-compliant available renderer. In production mode, if ChromeFrame is available, use ChromeFrame.
  • Session Management: If a config.session_store is supplied and config.api_only = false, this middleware makes the session available as the session method in ActionController.

Any of these middlewares can be added via:

config.middleware.use Rack::MethodOverride

Removing Middlewares

If you don't want to use a middleware that is included by default in the API middleware set, you can remove it using config.middleware.delete:

config.middleware.delete ::Rack::Sendfile

Keep in mind that removing these features may remove support for certain features in ActionController.

Choosing Controller Modules

An API application (using ActionController::API) comes with the following controller modules by default:

  • ActionController::UrlFor: Makes url_for and friends available
  • ActionController::Redirecting: Support for redirect_to
  • ActionController::Rendering: Basic support for rendering
  • ActionController::Renderers::All: Support for render :json and friends
  • ActionController::ConditionalGet: Support for stale?
  • ActionController::ForceSSL: Support for force_ssl
  • ActionController::RackDelegation: Support for the request and response methods returning ActionDispatch::Request and ActionDispatch::Response objects.
  • ActionController::DataStreaming: Support for send_file and send_data
  • AbstractController::Callbacks: Support for before_filter and friends
  • ActionController::Instrumentation: Support for the instrumentation hooks defined by ActionController (see the source for more).
  • ActionController::Rescue: Support for rescue_from.

Other plugins may add additional modules. You can get a list of all modules included into ActionController::API in the rails console:

ActionController::API.ancestors - ActionController::Metal.ancestors

Adding Other Modules

All Action Controller modules know about their dependent modules, so you can feel free to include any modules into your controllers, and all dependencies will be included and set up as well.

Some common modules you might want to add:

  • AbstractController::Translation: Support for the l and t localization and translation methods. These delegate to I18n.translate and I18n.localize.
  • ActionController::HttpAuthentication::Basic::ControllerMethods (or Digest or Token): Support for basic, digest or token HTTP authentication.
  • ActionView::Layouts: Support for layouts when rendering.
  • ActionController::MimeResponds (and ActionController::ImplicitRender for Rails 4): Support for content negotiation (respond_to, respond_with).
  • ActionController::Cookies: Support for cookies, which includes support for signed and encrypted cookies. This requires the cookie middleware.

The best place to add a module is in your ApplicationController. You can also add modules to individual controllers.

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Added some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Maintainers

License

MIT License.

Mailing List

https://groups.google.com/forum/?fromgroups#!forum/rails-api-core

active_model_serializers's People

Contributors

arenoir avatar bacarini avatar beauby avatar bf4 avatar biow0lf avatar bkoltai avatar bolshakov avatar cgmckeever avatar corainchicago avatar dgynn avatar domitian avatar dubadub avatar empact avatar eric-guo avatar ggordon avatar guilleiguaran avatar hiimtaylorjones avatar joaomdmoura avatar joshsmith avatar kurko avatar mateomurphy avatar maurogeorge avatar msathieu avatar nullvoxpopuli avatar remear avatar richmolj avatar rodrigora avatar spastorino avatar steveklabnik avatar tricknotes avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

active_model_serializers's Issues

has_many associations blow up if the collection is nil

Title says it all. You get an UndefinedMethodError, map for NilClass when it's serialized. How should this be handled?

This is only a problem no ActiveRecord associations since they return [] if empty. The library should be robust enough to handle this case. I think that just putting "comments": [] would be ok in the vast majority of situations.

embed :ids, include: false

From looking at the code, I can't see a way to disable include once you've enabled it. I have an Ember app, with embed :ids, include: true in an ApplicationSerializer and want to be able to disable it for one of the serializers. (A double nested include is breaking the adapter).

Currently, I've just removed the line from the ApplicationSerializer
Is there some way to handle this that I missed?
If not would you welcome a patch?

Cheers,
Brad

Add coarse support for caching/cache expirey

Talked to Yehuda about this at Ruby on Ales last week.

We believe that it should be possible to provide a default caching/cache-expiry mechanism via AMS. Ideally, it would be able to support action caching for actions not requiring authentication and fragment caching (still really caching the whole action) keyed in part by user ID for authenticated users.

I've already started tinkering with this. Ultimately, I intend to submit the capability as a pull request.

I have a proof-of-concept that already has working cache writes via AMS -- though it's not on github just yet as I'm still recovering from RoA ;-). Plus I intend to write up a full failing test case for it to demonstrate the API that I'm suggesting. I hope to have something interesting to show in the next few days.

How do you tell a serializer to use the object name in a polymorphic association?

Apologies for raising an issue about this, but after much looking I haven't been able to have much success.

I've seen the pull requests where this feature's been put in, but looking at the source it looked like it was introspective, however when I've tried to use code such as this (owner is a polymorphic association):

class MesssageSerializer < ActiveModel::Serializer
  attributes :id

  has_one :owner

end

the key for the owner relationship in the JSON response is still "owner" when it should be object_type, in this case "user". I've also tried adding

has_one :owner, :polymorphic => true

however this hasn't produced the desired result either,

Apologies if I'm missing something simple but I haven't been able to work this out.

Using v. 0.5.1 of the gem.

Thanks.

Add a default SetSerializer

Just as Arrays have a default ArraySerializerSupport module that gets included into them, Sets should likely have the same. Probably, the standard ArraySerializerSupport will work for both.

Could not find generator serializers:install.

I can't seem to get the rails generator to work. I added gem "active_model_serializers", :git => "http://github.com/josevalim/active_model_serializers.git" to my Gemfile and ran bundle install. This is what followed. Am I missing something obvious?

buch:test_app:% bundle install
Fetching http://github.com/josevalim/active_model_serializers.git
remote: Counting objects: 465, done.
remote: Compressing objects: 100% (185/185), done.
remote: Total 465 (delta 283), reused 404 (delta 222)
Receiving objects: 100% (465/465), 71.14 KiB, done.
Resolving deltas: 100% (283/283), done.
...
Using active_model_serializers (0.1.0) from http://github.com/josevalim/active_model_serializers.git (at master)
...
Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.

buch:test_app:% rails g

buch:test_app:% rails g serializers:install
Could not find generator serializers:install.

buch:test_app:% bundle exec rails g serializers:install
Could not find generator serializers:install.

buch:test_app:% bundle show active_model_serializers
/Users/patrick/.rbenv/versions/1.9.3-p0/lib/ruby/gems/1.9.1/bundler/gems/active_model_serializers-a0d02b43a59f

I'm using the latest rails, bundle, and ruby gems.

buch:test_app:% rails -v
Rails 3.2.1
buch:test_app:% bundle -v
Bundler version 1.0.21
buch:test_app:% gem -v      
1.8.15

Is the existence of 'current_user' mandatory?

I'm creating a Rails app from scratch and still don't have any authentication mechanism in place.
I had to specify a current_user method in the controller in order to produce json from the active_model_serializer, otherwise i get an exception:

NoMethodError in EntriesController#index

undefined method `current_user' for #EntriesController:0x007fc14cb4a8d8

Serializers on attributes are not used

I'm not sure if I'm simply doing this wrong, but if I specify an attribute that is a belongs_to, and that association has a serializer, that serializer does not get applied.

For example, BarSerializer is not used in this case when serializing a Foo instance:

class Foo < ActiveRecord::Base
  belongs_to :bar
end

class FooSerializer < ActiveModel::Serializer
  attributes :bar
end

Generator complains about test_unit

When running the serializer generator, it will complain about test_unit:

$ rails g serializer Customer
create  app/serializers/customer_serializer.rb
error  test_unit [not found]

Make it possible to do conditional includes

The original plan for the include! API was to be able to do something like the following:

def include_associations!
  include! :comments
  include! :user if admin?
end

However, the current internals require an options hash and passes in a node.

Test and document a public API that supports overriding include_associations! with a series of include! calls.

render :xml

Great gem.

Would be great it would be possible to render :xml equal to :json. (Same serializer)

0.5.0 Generator Problem

Running rails g serializer post produces a serializer:

class PostSerializer < ApplicationSerialzer

rather than

class PostSerialzer < ActiveModel::Serializer

Running posts.json or post/:id.json produces
NameErrer (uninitialized constant ApplciationSerializer)

can't specify serializer for a collection

In a controller, if I:
render json: Post.all, serializer: SpecialSerializer

it fails with the exception:
undefined method `read_attribute_for_serialization' for #ActiveRecord::Relation:0x000000030bcf18

It's trying to use the SpecialSerializer on the list of Posts, not on each post. If I pass in serializer: ArraySerializer, it asks Post what its serializer is and uses the default PostSerializer instead of the SpecialSerializer I want to specify. I don't want to define Post#active_model_serializer because everywhere else I do want to use PostSerializer, not the SpecialSerializer.

I'm not sure how this should be fixed: letting ArraySerializer take an option specifying a serializer, or _render_option_json should know how to deal with enumerables, or if there's some better approach for my controller that I'm completely missing. Ideas?

Breaks `rails generate` on 3.0.x

Given a Rails app generated with:

$ rails _3.0.13_ new app

And this addition to the Gemfile (and bundle install):

gem 'active_model_serializers', git: 'git://github.com/josevalim/active_model_serializers.git'

... rails generate no longer operates correctly:

$ rails generate
/Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.0.13/lib/active_support/whiny_nil.rb:48:in `method_missing': undefined method `config' for nil:NilClass (NoMethodError)
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/bundler/gems/active_model_serializers-5beecd5947de/lib/active_model_serializers.rb:10:in `block in <class:Railtie>'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.0.13/lib/rails/railtie.rb:189:in `call'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.0.13/lib/rails/railtie.rb:189:in `each'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.0.13/lib/rails/railtie.rb:189:in `load_generators'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.0.13/lib/rails/application.rb:147:in `block in load_generators'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.0.13/lib/rails/application/railties.rb:11:in `each'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.0.13/lib/rails/application/railties.rb:11:in `all'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.0.13/lib/rails/application.rb:147:in `load_generators'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.0.13/lib/rails/generators.rb:319:in `load_generators_from_railties!'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.0.13/lib/rails/generators.rb:302:in `lookup!'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.0.13/lib/rails/generators.rb:207:in `help'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.0.13/lib/rails/commands/generate.rb:5:in `<top (required)>'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.0.13/lib/active_support/dependencies.rb:242:in `require'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.0.13/lib/active_support/dependencies.rb:242:in `block in require'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.0.13/lib/active_support/dependencies.rb:225:in `block in load_dependency'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.0.13/lib/active_support/dependencies.rb:597:in `new_constants_in'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.0.13/lib/active_support/dependencies.rb:225:in `load_dependency'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.0.13/lib/active_support/dependencies.rb:242:in `require'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.0.13/lib/rails/commands.rb:17:in `<top (required)>'
        from script/rails:6:in `require'
        from script/rails:6:in `<main>'

Anonymous/inline association serializer?

When embedding associations, I often find the need to include just a subset of the attributes on the embedded object. For example, I might want to embed the Author object inside a Post, but only the author's id and full_name attributes (whereas, if requested by itself, the Author object might included other attributes such as created_at, posts_count, email etc).

This is how I currently do this:

class PostSerializer < ActiveModel::Serializer
  attributes :id, :title, :body

  class PostAuthorSerializer < ActiveModel::Serializer
    attributes :id, :full_name
  end

  has_one :author, serializer: PostAuthorSerializer
end

This is okay, but feels a little bit hackish. I am wondering if there are any better patterns for accomplishing this. If not, one way I could think of is to add support for creating anonymous/inline serializers for embedded associations, such as:

class PostSerializer < ActiveModel::Serializer
  attributes :id, :title, :body

  has_one :author do
    attributes :id, :full_name
  end

  # This is roughly equivalent to:
  # has_one :author, serializer: Class.new(ActiveModel::Serializer){ attributes(:id, :full_name) }
end

If there are interest in this and if I can get the green light from either @wycats or @josevalim I could submit a PR.

Changing Association Names

Let's say that you have two association for the same object. One is for internal use only and the other is public. It doesn't make sense to return the 'public_association' in the serializable hash. There should be some way to change the association name and still have the id/object concept.

You could do something like this:

class RecordSerializer < ActiveModel::Serializer
  has_many :public_things, :as => :things, :serializer => ThingSerializer
end

I will gladly write the code for this as well since I need this functionality for my app!

ActiveRecord::Relation should behave the same as an Array

Overview

Whenever you attempt to use an ActiveRecord::Relation object, the serializer fails to detect that it's an array of objects (and therefore, does not use the ArraySerializer).

Example

Failing but should work

@posts = Post.where("id is not null")
render json: @posts

>>

NoMethodError (undefined method `read_attribute_for_serialization' for #<ActiveRecord::Relation:0x007fdc066561e0>):

Workaround

@posts = Post.where("id is not null").to_a
render json: @posts

-or-

@posts=Post.all
render json: @posts

Expected

I would expect for the ActiveRecord:Relation object to behave exactly like an Array object does.

ArraySerializer tries to call serializable_hash on primitive objects

it's smart enough not to try to wrap non-serializable objects but when it does the actual serialization it doesn't re-check each item, and thus tries to serialize non-serializable objects resulting in errors like this:

NoMethodError (undefined method `serializable_hash' for "test3":String):

NoMethodError (undefined method `serializable_hash' for {:label=>"test3", :value=>1000012}:Hash):

Can't use Polymorphic Attributes

I am unsure of how to do this at this point, but here is my proposed solution.

has_one :reference, :polymorphic => true

Then at serialization time, instantiate a serialize based on the class name of the polymorphic object and call serialize with that object and scope.

I also thought you could have a ReferenceSerializer which would determine how to serialize whatever object is passed to it. However, I just realized that would break down if you had multiple polymorphic associations with the same name. IE a Todo can have a reference and an attachment can have a reference. Then you'd have to pass :serializer => TodoReferenceSerializer etc. Thus, I think the first choice is better.

I am not sure exactly how that code would work. I think a subclass of the HasOne association would do the trick. This method is also easily extendable to HasMany as well.

If you think this solution is correct, then I'll write the code for this as I need it for my app (again :D)

Custom helpers, cleaner way?

I want to create a time_ago_in_words version of most time values I am rendering in my json.

I want to specify which keys this happens for, and I think it belongs in the serializer/view layer (not in the model).

I was able to make a helper method that works like this:

  class VideoSerializer < MyBaseSerializer
    time_attributes :recorded_at, :uploaded_at
  end
  # example serializer.as_json => {:recorded_at_ago => "3 days", :uploaded_at_ago => "2 minutes"}

gist is here: https://gist.github.com/3177989

I am just wondering if there is a more concise way to accomplish this?

Respect controller namespace when looking up serializers

I have name-spaced my API controllers like so:

module EngineName
  class Post < ActiveRecord::Base; end

  class API::V1::APIController < ActionController::API; end

  class API::V1::PostsController < API::V1::APIController
    def index
      @posts = Post.all
      render json: @posts
    end
  end
end

Since each API version might serialize the models differently, ideally the serializers should be namespaced accordingly like so:

module EngineName
  class Post < ActiveRecord::Base; end

  class API::V1::PostSerializer < ActiveModel::Serializer
    attributes :id, :title, :body
  end

  class API::V1::APIController < ActionController::API; end

  class API::V1::PostsController < API::V1::APIController
    def index
      @posts = Post.all
      render json: @posts, serializer: PostSerializer # resolves to ::EngineName::API::V1::PostSerializer
    end
  end
end

This gets quite repetitive when you have to do it for all your controllers. I am wondering if it is possible to take into account of the controller's scope when looking up a serializer. In the example above, render json: @posts should these in order (via controller.class.const_get :PostSerializer):

::EngineName::API::V1::PostsController::PostSerializer
::EngineName::API::V1::PostSerializer
::EngineName::API::PostSerializer
::EngineName::PostSerializer
::PostSerializer

(I also thought about overriding default_serializer_options in APIController, but that won't work as the options are merged after the serializer lookup, so def default_serializer_options; { serializer: SomeSerializer }; end won't work.)

Cannot use STI w/has_many

How do you think we should do this?

I have this class structure, Message, Im < Message, Sms < Message, and Email < Message. How would I serialize all the messages?

I could loop over them myself, but we should make the library robust enough to support this use case.

For implementation, I guess it would be like the polymorphic has_one, but going up the class tree until we can find a serializer. Question is, where would it stop? It wouldn't make sense to go all the way up tree. Perhaps define a method on each ActiveModel instance to determine what to use to serialize it.

What do you think? I think this the last problem I'll run into for bit with this library :)

doesn't honor rails config include_root_in_json

setting rails include_root_in_json to false doesn't remove the json wrapper from this gem's output, so when you drop in this gem into a rails project that expects no wrappers on the outbound json, stuff breaks.

Serializers for Associations?

I understand that currently there is no way to define the attributes on associations within a serializer, please correct me if I'm wrong. Are there any plans to add this? I don't mind throwing in some work to do this myself, but wasn't sure that was desired direction.

Example:

#<Post id: 1, title: "My Title", body: "My body of text.">

class Post < ActiveModel::Serializer
  attributes :title, :body
  has_many :comments
end

#<Comment id: 1, name: "Ryan", email: "[email protected]", body: "I did a thing!">

class Comment < ActiveModel::Serializer
  attributes :name, :body
end

render json: @post

Now, let's say I wanted to hide the email address in the json output. This works perfectly when I visit /comments/1.json but when I embed the comment inside of the post's output, it's perfectly visible!

{
  "post": {
    "title": 3,
    "body": 3,
    "comments": [
      {
        "id": 1
        "name": "Ryan",
        "email": "[email protected]"
        "body": "I did a thing!"
      }
    ]
  }
}

Selectively activate serializers on a per-controller basis

We would like to use active_model_serializers for controllers in our app that will be handling API requests. However, we may also have a need to serve JSON responses via non-API controllers (e.g. responding to Ajax requests). If serializers remain in effect for a model in all controllers, this could potentially lead to confusion or bugs.

Two ideas for accomplishing selective activation:

  • Require an explicit call to enable the use of serializers in a controller (e.g. use_json_serializers in the controller class definition)
  • A configuration option that could be set to disable the ActionController mix-in combined with an explicit inclusion (or convenience method as above)

Inline options does not override default options

class SomeController < ActionController::Base
  def some_action
    render json: @some_array, each_serializer: EvenBetterSerializer # Does not work, CustomSerializer will be used
  end

  def default_serializer_options
    { each_serializer: CustomSerializer }
  end
end

Use gem without overwriting render

Is it somehow possible to revert or even better skip this completely?

ActiveSupport.on_load(:action_controller) do
include ::ActionController::Serialization
end

I like the ease of serializers when I want to include the associations of a model but this is not always necessary in my controllers.

Imagine a backbone app where you would like to initialize your single page app with the main model which includes all associations
something like
new Backbone.View({model: <%= @model_serializer.to_json %>})

but in further requests, e.g. updates in your model, you would want from your controller only the attributes of the model (fields of its own table)

I can skip the serializer in my controller if I call render like this
render json: model.to_json

but I thought it would be nice to be able to globally turn it off.

what do you think?

Deserialization based on the same rules as serialization

One of the features I find interesting about this gem is the ability to turn off fields based on the current user. Is the same true for deserializing? Can the serializer control what fields are allowed on the round trip back to a model?

My preference would be that an exception is thrown if any funny business is going on and fields are passed back from the client that the user should have no access to.

Generator accepts unused field:type options

The serializer generator appears to take unused types for fields (attributes?):

rails generate serializer NAME [field:type field:type] [options]

Is this possibly an artifact from the model/resource generator that doesn't make sense for the serializer generator?

How to make it work with Grape?

Can anyone advise me on how to use AM::Serializers with Grape? Currently I am using Rabl and planning to move away from it. Is there an existing bridge for Grape?

`rails generate resource` fails on Rails 3.0.x

$ rails generate resource foo
(erb):1:in `template': undefined method `module_namespacing' for #<Rails::Generators::SerializerGenerator:0x007fc2341b6950> (NoMethodError)
        from /Users/alindeman/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/erb.rb:838:in `eval'
        from /Users/alindeman/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/erb.rb:838:in `result'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/actions/file_manipulation.rb:111:in `block in template'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/actions/create_file.rb:54:in `call'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/actions/create_file.rb:54:in `render'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/actions/create_file.rb:63:in `block (2 levels) in invoke!'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/actions/create_file.rb:63:in `open'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/actions/create_file.rb:63:in `block in invoke!'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/actions/empty_directory.rb:114:in `call'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/actions/empty_directory.rb:114:in `invoke_with_conflict_check'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/actions/create_file.rb:61:in `invoke!'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/actions.rb:95:in `action'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/actions/create_file.rb:26:in `create_file'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/actions/file_manipulation.rb:110:in `template'
        from /Users/alindeman/Documents/workspace/active_model_serializers/lib/generators/serializer/serializer_generator.rb:12:in `create_serializer_file'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/task.rb:22:in `run'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/invocation.rb:118:in `invoke_task'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/invocation.rb:124:in `block in invoke_all'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/invocation.rb:124:in `each'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/invocation.rb:124:in `map'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/invocation.rb:124:in `invoke_all'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/group.rb:226:in `dispatch'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/invocation.rb:109:in `invoke'
        from /Users/alindeman/Documents/workspace/active_model_serializers/lib/generators/resource_override.rb:8:in `add_serializer'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/task.rb:22:in `run'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/invocation.rb:118:in `invoke_task'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/invocation.rb:124:in `block in invoke_all'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/invocation.rb:124:in `each'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/invocation.rb:124:in `map'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/invocation.rb:124:in `invoke_all'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/group.rb:226:in `dispatch'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/thor-0.14.6/lib/thor/base.rb:389:in `start'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.0.10/lib/rails/generators.rb:163:in `invoke'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.0.10/lib/rails/commands/generate.rb:10:in `<top (required)>'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.0.10/lib/active_support/dependencies.rb:239:in `require'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.0.10/lib/active_support/dependencies.rb:239:in `block in require'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.0.10/lib/active_support/dependencies.rb:225:in `block in load_dependency'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.0.10/lib/active_support/dependencies.rb:593:in `new_constants_in'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.0.10/lib/active_support/dependencies.rb:225:in `load_dependency'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/activesupport-3.0.10/lib/active_support/dependencies.rb:239:in `require'
        from /Users/alindeman/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.0.10/lib/rails/commands.rb:17:in `<top (required)>'
        from script/rails:6:in `require'
        from script/rails:6:in `<main>'

Rails 3.0.x does not have the module_namespacing method on Rails::Generators::NamedBase. Not sure what to do about it. Maybe just don't try to namespace under Rails 3.0?

custom attributes readme issue

in the Custom Serializers section of the readme, as written, Person.new.attributes will error on both options and scope not being defined.

It seems like the fix might be to move both methods full_name and attribtues into an ActiveModel::Serializer class rather than an ActiveRecord::Base. However then I get wrong number of arguments on the super(options) line, and full_name needs to be changed to use object.first_name.

Active Model Serializer not sending id -> found a hack -> sends deprecation warnings

We're using :id in the attributes declaration. For some reason the serializer is sending over object_id instead of the id--the primary key. As a workaround, we are serializing :to_param which works and does not throw deprecation warnings, like retrieving _id. However, in another app, I am using :id and it works find. The only difference I can see is that in the broken app we are also including,

embed :ids, :include => true

to sideload the associated ids. Should this break :id for the top level object? Can anyone reproduce? I'll try to reproduce with a blank rails app asap.

License Missing

Unless I'm looking in the wrong place, it doesn't seem that there's any indication of a license to use/redistribute this code. Could we get some clarification on license?

Awkward to use association objects and association ids at the same time.

I'm using this project in my application. I have classes that should return serialized objects and some that only only return the id's. At this point you can get around it by saying attributes :account_id then specifying has_one or has_many. I'm proposing a change to the API that would make it easier for this situation to work. Here is my proposed change:

class UserSerializer < ActiveModel::Serializer
  has_many :contacts, :embed => :objects
  belongs_to :account, :embed => :id
end

The embed method could set the preference, but I'm not sure how that would work. I am interested in adding this functionality and will gladly write a pull request if you guys thing this is a good thing to do.

Question marks in attribute names are serialized

Given

attributes :anonymous?

we get

"anonymous?": true

in the JSON.

In JS you have to write ...['anonymous?'] to access this, which is a bit cumbersome, and goes against JS convention.

I think it would be a reasonable default to drop the question mark. People who don't like it can still use :key.

I'm volunteering a PR. What do you think?

serializer does not reload when changed in development

Creating a serializer with just an id attribute, starting Rails, adding another attribute to a serializer, and then reloading, results in only the id attribute showing up. This is using master of about an hour ago. This is with Rails 3.2.8.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.