```
require "rails/generators/rails/model/model_generator"
module Rails
module Generators
class ResourceGenerator < ModelGenerator # :nodoc:
include ResourceHelpers
hook_for :resource_controller, required: true do |controller|
invoke controller, [ controller_name, options[:actions] ]
end
class_option :actions, type: :array, banner: "ACTION ACTION", default: [],
desc: "Actions for the resource controller"
hook_for :resource_route, required: true
end
end
end
```
```
# .bundle/ruby/2.2.0/bundler/gems/rails-4c5f1bc9d45e/railties/lib/rails/generators/base.rb
# Invoke a generator based on the value supplied by the user to the
# given option named "name". A class option is created when this method
# is invoked and you can set a hash to customize it.
#
# ==== Examples
#
# module Rails::Generators
# class ControllerGenerator < Base
# hook_for :test_framework, aliases: "-t"
# end
# end
#
# The example above will create a test framework option and will invoke
# a generator based on the user supplied value.
#
# For example, if the user invoke the controller generator as:
#
# rails generate controller Account --test-framework=test_unit
#
# The controller generator will then try to invoke the following generators:
#
# "rails:test_unit", "test_unit:controller", "test_unit"
#
# Notice that "rails:generators:test_unit" could be loaded as well, what
# Rails looks for is the first and last parts of the namespace. This is what
# allows any test framework to hook into Rails as long as it provides any
# of the hooks above.
#
# ==== Options
#
# The first and last part used to find the generator to be invoked are
# guessed based on class invokes hook_for, as noticed in the example above.
# This can be customized with two options: :in and :as.
#
# Let's suppose you are creating a generator that needs to invoke the
# controller generator from test unit. Your first attempt is:
#
# class AwesomeGenerator < Rails::Generators::Base
# hook_for :test_framework
# end
#
# The lookup in this case for test_unit as input is:
#
# "test_unit:awesome", "test_unit"
#
# Which is not the desired lookup. You can change it by providing the
# :as option:
#
# class AwesomeGenerator < Rails::Generators::Base
# hook_for :test_framework, as: :controller
# end
#
# And now it will look up at:
#
# "test_unit:controller", "test_unit"
#
# Similarly, if you want it to also look up in the rails namespace, you
# just need to provide the :in value:
#
# class AwesomeGenerator < Rails::Generators::Base
# hook_for :test_framework, in: :rails, as: :controller
# end
#
# And the lookup is exactly the same as previously:
#
# "rails:test_unit", "test_unit:controller", "test_unit"
#
# ==== Switches
#
# All hooks come with switches for user interface. If you do not want
# to use any test framework, you can do:
#
# rails generate controller Account --skip-test-framework
#
# Or similarly:
#
# rails generate controller Account --no-test-framework
#
# ==== Boolean hooks
#
# In some cases, you may want to provide a boolean hook. For example, webrat
# developers might want to have webrat available on controller generator.
# This can be achieved as:
#
# Rails::Generators::ControllerGenerator.hook_for :webrat, type: :boolean
#
# Then, if you want webrat to be invoked, just supply:
#
# rails generate controller Account --webrat
#
# The hooks lookup is similar as above:
#
# "rails:generators:webrat", "webrat:generators:controller", "webrat"
#
# ==== Custom invocations
#
# You can also supply a block to hook_for to customize how the hook is
# going to be invoked. The block receives two arguments, an instance
# of the current class and the class to be invoked.
#
# For example, in the resource generator, the controller should be invoked
# with a pluralized class name. But by default it is invoked with the same
# name as the resource generator, which is singular. To change this, we
# can give a block to customize how the controller can be invoked.
#
# hook_for :resource_controller do |instance, controller|
# instance.invoke controller, [ instance.name.pluralize ]
# end
#
def self.hook_for(*names, &block)
options = names.extract_options!
in_base = options.delete(:in) || base_name
as_hook = options.delete(:as) || generator_name
names.each do |name|
unless class_options.key?(name)
defaults = if options[:type] == :boolean
{}
elsif [true, false].include?(default_value_for_option(name, options))
{ banner: "" }
else
{ desc: "#{name.to_s.humanize} to be invoked", banner: "NAME" }
end
class_option(name, defaults.merge!(options))
end
hooks[name] = [ in_base, as_hook ]
invoke_from_option(name, options, &block)
end
end
```
```
# .bundle/ruby/2.2.0/gems/thor-0.19.4/lib/thor/parser/option.rb:113:in `validate!'
# parse :foo => true
# #=> Option foo with default value true and type boolean
#
# The valid types are :boolean, :numeric, :hash, :array and :string. If none
# is given a default type is assumed. This default type accepts arguments as
# string (--foo=value) or booleans (just --foo).
#
# By default all options are optional, unless :required is given.
def validate_default_type!
default_type = case @default
when nil
return
when TrueClass, FalseClass
required? ? :string : :boolean
when Numeric
:numeric
when Symbol
:string
when Hash, Array, String
@default.class.name.downcase.to_sym
end
# TODO: This should raise an ArgumentError in a future version of Thor
if default_type != @type
warn "Expected #{@type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})"
end
end
```
|
||
|---|---|---|
| .github | ||
| bin | ||
| docs | ||
| lib | ||
| test | ||
| .gitignore | ||
| .rubocop.yml | ||
| .simplecov | ||
| .travis.yml | ||
| active_model_serializers.gemspec | ||
| appveyor.yml | ||
| CHANGELOG.md | ||
| CODE_OF_CONDUCT.md | ||
| CONTRIBUTING.md | ||
| Gemfile | ||
| MIT-LICENSE | ||
| Rakefile | ||
| README.md | ||
ActiveModelSerializers
| Build Status |
|
| Code Quality |
|
| Issue Stats | Pulse |
About
ActiveModelSerializers brings convention over configuration to your JSON generation.
ActiveModelSerializers works through two components: serializers and adapters.
Serializers describe which attributes and relationships should be serialized.
Adapters describe how attributes and relationships should be serialized.
SerializableResource co-ordinates the resource, Adapter and Serializer to produce the
resource serialization. The serialization has the #as_json, #to_json and #serializable_hash
methods used by the Rails JSON Renderer. (SerializableResource actually delegates
these methods to the adapter.)
By default ActiveModelSerializers will use the Attributes Adapter (no JSON root). But we strongly advise you to use JsonApi Adapter, which follows 1.0 of the format specified in jsonapi.org/format. Check how to change the adapter in the sections below.
0.10.x is not backward compatible with 0.9.x nor 0.8.x.
0.10.x is based on the 0.8.0 code, but with a more flexible
architecture. We'd love your help. Learn how you can help here.
It is generally safe and recommended to use the master branch.
Installation
Add this line to your application's Gemfile:
gem 'active_model_serializers', '~> 0.10.0'
And then execute:
$ bundle
Getting Started
See Getting Started for the nuts and bolts.
More information is available in the Guides and High-level behavior.
Getting Help
If you find a bug, please report an Issue and see our contributing guide.
If you have a question, please post to Stack Overflow.
If you'd like to chat, we have a community slack.
Thanks!
Documentation
If you're reading this at https://github.com/rails-api/active_model_serializers you are
reading documentation for our master, which may include features that have not
been released yet. Please see below for the documentation relevant to you.
- 0.10 (master) Documentation
- 0.10.4 (latest release) Documentation
- 0.9 (0-9-stable) Documentation
- 0.8 (0-8-stable) Documentation
High-level behavior
Choose an adapter from adapters:
ActiveModelSerializers.config.adapter = :json_api # Default: `:attributes`
Given a serializable model:
# either
class SomeResource < ActiveRecord::Base
# columns: title, body
end
# or
class SomeResource < ActiveModelSerializers::Model
attributes :title, :body
end
And initialized as:
resource = SomeResource.new(title: 'ActiveModelSerializers', body: 'Convention over configuration')
Given a serializer for the serializable model:
class SomeSerializer < ActiveModel::Serializer
attribute :title, key: :name
attributes :body
end
The model can be serialized as:
options = {}
serialization = ActiveModelSerializers::SerializableResource.new(resource, options)
serialization.to_json
serialization.as_json
SerializableResource delegates to the adapter, which it builds as:
adapter_options = {}
adapter = ActiveModelSerializers::Adapter.create(serializer, adapter_options)
adapter.to_json
adapter.as_json
adapter.serializable_hash
The adapter formats the serializer's attributes and associations (a.k.a. includes):
serializer_options = {}
serializer = SomeSerializer.new(resource, serializer_options)
serializer.attributes
serializer.associations
Architecture
This section focuses on architecture the 0.10.x version of ActiveModelSerializers. If you are interested in the architecture of the 0.8 or 0.9 versions, please refer to the 0.8 README or 0.9 README.
The original design is also available here.
ActiveModel::Serializer
An ActiveModel::Serializer wraps a serializable resource
and exposes an attributes method, among a few others.
It allows you to specify which attributes and associations should be represented in the serializatation of the resource.
It requires an adapter to transform its attributes into a JSON document; it cannot be serialized itself.
It may be useful to think of it as a
presenter.
ActiveModel::CollectionSerializer
The ActiveModel::CollectionSerializer represents a collection of resources as serializers
and, if there is no serializer, primitives.
ActiveModelSerializers::Adapter::Base
The ActiveModelSerializeres::Adapter::Base describes the structure of the JSON document generated from a
serializer. For example, the Attributes example represents each serializer as its
unmodified attributes. The JsonApi adapter represents the serializer as a JSON
API document.
ActiveModelSerializers::SerializableResource
The ActiveModelSerializers::SerializableResource acts to coordinate the serializer(s) and adapter
to an object that responds to to_json, and as_json. It is used in the controller to
encapsulate the serialization resource when rendered. However, it can also be used on its own
to serialize a resource outside of a controller, as well.
Primitive handling
Definitions: A primitive is usually a String or Array. There is no serializer
defined for them; they will be serialized when the resource is converted to JSON (as_json or
to_json). (The below also applies for any object with no serializer.)
- ActiveModelSerializers doesn't handle primitives passed to
render json:at all.
Internally, if no serializer can be found in the controller, the resource is not decorated by ActiveModelSerializers.
- However, when a primitive value is an attribute or in a collection, it is not modified.
When serializing a collection and the collection serializer (CollectionSerializer) cannot
identify a serializer for a resource in its collection, it throws :no_serializer.
For example, when caught by Reflection#build_association, and the association value is set directly:
reflection_options[:virtual_value] = association_value.try(:as_json) || association_value
(which is called by the adapter as serializer.associations(*).)
How options are parsed
High-level overview:
- For a collection
:serializerspecifies the collection serializer and:each_serializerspecifies the serializer for each resource in the collection.
- For a single resource, the
:serializeroption is the resource serializer. - Options are partitioned in serializer options and adapter options. Keys for adapter options are specified by
ADAPTER_OPTION_KEYS. The remaining options are serializer options.
Details:
- ActionController::Serialization
serializable_resource = ActiveModelSerializers::SerializableResource.new(resource, options)1.optionsare partitioned intoadapter_optsand everything else (serializer_opts). Theadapter_optskeys are defined inActiveModelSerializers::SerializableResource::ADAPTER_OPTION_KEYS.- ActiveModelSerializers::SerializableResource
if serializable_resource.serializer?(there is a serializer for the resource, and an adapter is used.) - Whereserializer?isuse_adapter? && !!(serializer)- Where
use_adapter?: 'True when no explicit adapter given, or explicit value is truthy (non-nil); False when explicit adapter is falsy (nil or false)' - Where
serializer:- from explicit
:serializeroption, else - implicitly from resource
ActiveModel::Serializer.serializer_for(resource)
- from explicit
- Where
- A side-effect of checking
serializeris:- The
:serializeroption is removed from the serializer_opts hash - If the
:each_serializeroption is present, it is removed from the serializer_opts hash and set as the:serializeroption
- The
- The serializer and adapter are created as
1.
serializer_instance = serializer.new(resource, serializer_opts)2.adapter_instance = ActiveModel::Serializer::Adapter.create(serializer_instance, adapter_opts) - ActiveModel::Serializer::CollectionSerializer#new
- If the
serializer_instancewas aCollectionSerializerand the:serializerserializer_opts is present, then that serializer is passed into each resource. - ActiveModel::Serializer#attributes is used by the adapter to get the attributes for resource as defined by the serializer.
(In Rails, the options are also passed to the as_json(options) or to_json(options)
methods on the resource serialization by the Rails JSON renderer. They are, therefore, important
to know about, but not part of ActiveModelSerializers.)
What does a 'serializable resource' look like?
ActiveModelSerializers provides a
ActiveModelSerializers::Model,
which is a simple serializable PORO (Plain-Old Ruby Object).
ActiveModelSerializers::Model may be used either as a reference implementation, or in production code.
class MyModel < ActiveModelSerializers::Model
attributes :id, :name, :level
end
The default serializer for MyModel would be MyModelSerializer whether MyModel is an
ActiveRecord::Base object or not.
Outside of the controller the rules are exactly the same as for records. For example:
render json: MyModel.new(level: 'awesome'), adapter: :json
would be serialized the same as
ActiveModelSerializers::SerializableResource.new(MyModel.new(level: 'awesome'), adapter: :json).as_json
Semantic Versioning
This project adheres to semver
Contributing
See CONTRIBUTING.md