docs/general/serializers.md in active_model_serializers-0.10.0.rc4 vs docs/general/serializers.md in active_model_serializers-0.10.0.rc5

- old
+ new

@@ -44,10 +44,20 @@ ```ruby has_one :bio has_one :blog, key: :site has_one :maker, virtual_value: { id: 1 } + +has_one :blog do |serializer| + serializer.cached_blog +end + +def cached_blog + cache_store.fetch("cached_blog:#{object.updated_at}") do + Blog.find(object.blog_id) + end +end ``` #### ::has_many e.g. @@ -74,10 +84,22 @@ def blog Blog.new(id: 999, name: 'Custom blog') end ``` +### Polymorphic Relationships + +Polymorphic relationships are serialized by specifying the relationship, like any other association. For example: + +```ruby +class PictureSerializer < ActiveModel::Serializer + has_one :imageable +end +``` + +For more context, see the [tests](../../test/adapter/polymorphic_test.rb) for each adapter. + ### Caching #### ::cache e.g. @@ -105,27 +127,46 @@ ### Other #### ::type -e.g. +The `::type` method defines the JSONAPI [type](http://jsonapi.org/format/#document-resource-object-identification) that will be rendered for this serializer. +It either takes a `String` or `Symbol` as parameter. +Note: This method is useful only when using the `:json_api` adapter. + +Examples: ```ruby class UserProfileSerializer < ActiveModel::Serializer type 'profile' end +class AuthorProfileSerializer < ActiveModel::Serializer + type :profile +end ``` -#### ::link +With the `:json_api` adapter, the previous serializers would be rendered as: -e.g. +``` json +{ + "data": { + "id": "1", + "type": "profile" + } +} +``` +#### ::link + ```ruby -link :other, 'https://example.com/resource' link :self do - href "https://example.com/link_author/#{object.id}" + href "https://example.com/link_author/#{object.id}" end +link :author { link_author_url(object) } +link :link_authors { link_authors_url } +link :other, 'https://example.com/resource' +link :posts { link_author_posts_url(object) } ``` #### #object The object being serialized. @@ -134,10 +175,101 @@ PR please :) #### #scope -PR please :) +Allows you to include in the serializer access to an external method. + +It's intended to provide an authorization context to the serializer, so that +you may e.g. show an admin all comments on a post, else only published comments. + +- `scope` is a method on the serializer instance that comes from `options[:scope]`. It may be nil. +- `scope_name` is an option passed to the new serializer (`options[:scope_name]`). The serializer + defines a method with that name that calls the `scope`, e.g. `def current_user; scope; end`. + Note: it does not define the method if the serializer instance responds to it. + +That's a lot of words, so here's some examples: + +First, let's assume the serializer is instantiated in the controller, since that's the usual scenario. +We'll refer to the serialization context as `controller`. + +| options | `Serializer#scope` | method definition | +|-------- | ------------------|--------------------| +| `scope: current_user, scope_name: :current_user` | `current_user` | `Serializer#current_user` calls `controller.current_user` +| `scope: view_context, scope_name: :view_context` | `view_context` | `Serializer#view_context` calls `controller.view_context` + +We can take advantage of the scope to customize the objects returned based +on the current user (scope). + +For example, we can limit the posts the current user sees to those they created: + +```ruby +class PostSerializer < ActiveModel::Serializer + attributes :id, :title, :body + + # scope comments to those created_by the current user + has_many :comments do + object.comments.where(created_by: current_user) + end +end +``` + +Whether you write the method as above or as `object.comments.where(created_by: scope)` +is a matter of preference (assuming `scope_name` has been set). + +##### Controller Authorization Context + +In the controller, the scope/scope_name options are equal to +the [`serialization_scope`method](https://github.com/rails-api/active_model_serializers/blob/d02cd30fe55a3ea85e1d351b6e039620903c1871/lib/action_controller/serialization.rb#L13-L20), +which is `:current_user`, by default. + +Specfically, the `scope_name` is defaulted to `:current_user`, and may be set as +`serialization_scope :view_context`. The `scope` is set to `send(scope_name)` when `scope_name` is +present and the controller responds to `scope_name`. + +Thus, in a serializer, the controller provides `current_user` as the +current authorization scope when you call `render :json`. + +**IMPORTANT**: Since the scope is set at render, you may want to customize it so that `current_user` isn't +called on every request. This was [also a problem](https://github.com/rails-api/active_model_serializers/pull/1252#issuecomment-159810477) +in [`0.9`](https://github.com/rails-api/active_model_serializers/tree/0-9-stable#customizing-scope). + +We can change the scope from `current_user` to `view_context`. + +```diff +class SomeController < ActionController::Base ++ serialization_scope :view_context + + def current_user + User.new(id: 2, name: 'Bob', admin: true) + end + + def edit + user = User.new(id: 1, name: 'Pete') + render json: user, serializer: AdminUserSerializer, adapter: :json_api + end +end +``` + +We could then use the controller method `view_context` in our serializer, like so: + +```diff +class AdminUserSerializer < ActiveModel::Serializer + attributes :id, :name, :can_edit + + def can_edit? ++ view_context.current_user.admin? + end +end +``` + +So that when we render the `#edit` action, we'll get + +```json +{"data":{"id":"1","type":"users","attributes":{"name":"Pete","can_edit":true}}} +``` + +Where `can_edit` is `view_context.current_user.admin?` (true). #### #read_attribute_for_serialization(key) The serialized value for a given key. e.g. `read_attribute_for_serialization(:title) #=> 'Hello World'`