active_model_serializers 0.8.3 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE.md +29 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +15 -0
- data/.gitignore +17 -0
- data/.rubocop.yml +104 -0
- data/.rubocop_todo.yml +167 -0
- data/.simplecov +110 -0
- data/.travis.yml +39 -24
- data/CHANGELOG.md +465 -6
- data/CONTRIBUTING.md +105 -0
- data/Gemfile +50 -1
- data/{MIT-LICENSE.txt → MIT-LICENSE} +3 -2
- data/README.md +102 -590
- data/Rakefile +93 -8
- data/active_model_serializers.gemspec +65 -23
- data/appveyor.yml +24 -0
- data/bin/bench +171 -0
- data/bin/bench_regression +316 -0
- data/bin/serve_benchmark +39 -0
- data/docs/ARCHITECTURE.md +126 -0
- data/docs/README.md +40 -0
- data/docs/STYLE.md +58 -0
- data/docs/general/adapters.md +245 -0
- data/docs/general/caching.md +52 -0
- data/docs/general/configuration_options.md +100 -0
- data/docs/general/deserialization.md +100 -0
- data/docs/general/getting_started.md +133 -0
- data/docs/general/instrumentation.md +40 -0
- data/docs/general/key_transforms.md +40 -0
- data/docs/general/logging.md +14 -0
- data/docs/general/rendering.md +255 -0
- data/docs/general/serializers.md +372 -0
- data/docs/how-open-source-maintained.jpg +0 -0
- data/docs/howto/add_pagination_links.md +139 -0
- data/docs/howto/add_root_key.md +51 -0
- data/docs/howto/outside_controller_use.md +58 -0
- data/docs/howto/passing_arbitrary_options.md +27 -0
- data/docs/howto/serialize_poro.md +32 -0
- data/docs/howto/test.md +152 -0
- data/docs/integrations/ember-and-json-api.md +112 -0
- data/docs/integrations/grape.md +19 -0
- data/docs/jsonapi/errors.md +56 -0
- data/docs/jsonapi/schema/schema.json +366 -0
- data/docs/jsonapi/schema.md +151 -0
- data/docs/rfcs/0000-namespace.md +106 -0
- data/docs/rfcs/template.md +15 -0
- data/lib/action_controller/serialization.rb +31 -36
- data/lib/active_model/serializable_resource.rb +11 -0
- data/lib/active_model/serializer/adapter/attributes.rb +15 -0
- data/lib/active_model/serializer/adapter/base.rb +16 -0
- data/lib/active_model/serializer/adapter/json.rb +15 -0
- data/lib/active_model/serializer/adapter/json_api.rb +15 -0
- data/lib/active_model/serializer/adapter/null.rb +15 -0
- data/lib/active_model/serializer/adapter.rb +24 -0
- data/lib/active_model/serializer/array_serializer.rb +9 -0
- data/lib/active_model/serializer/association.rb +19 -0
- data/lib/active_model/serializer/associations.rb +87 -220
- data/lib/active_model/serializer/attribute.rb +25 -0
- data/lib/active_model/serializer/attributes.rb +82 -0
- data/lib/active_model/serializer/belongs_to_reflection.rb +10 -0
- data/lib/active_model/serializer/caching.rb +333 -0
- data/lib/active_model/serializer/collection_reflection.rb +7 -0
- data/lib/active_model/serializer/collection_serializer.rb +64 -0
- data/lib/active_model/serializer/configuration.rb +35 -0
- data/lib/active_model/serializer/error_serializer.rb +10 -0
- data/lib/active_model/serializer/errors_serializer.rb +27 -0
- data/lib/active_model/serializer/field.rb +90 -0
- data/lib/active_model/serializer/fieldset.rb +31 -0
- data/lib/active_model/serializer/has_many_reflection.rb +10 -0
- data/lib/active_model/serializer/has_one_reflection.rb +10 -0
- data/lib/active_model/serializer/include_tree.rb +111 -0
- data/lib/active_model/serializer/links.rb +35 -0
- data/lib/active_model/serializer/lint.rb +146 -0
- data/lib/active_model/serializer/meta.rb +29 -0
- data/lib/active_model/serializer/null.rb +17 -0
- data/lib/active_model/serializer/reflection.rb +147 -0
- data/lib/active_model/serializer/singular_reflection.rb +7 -0
- data/lib/active_model/serializer/type.rb +25 -0
- data/lib/active_model/{serializers → serializer}/version.rb +1 -1
- data/lib/active_model/serializer.rb +158 -481
- data/lib/active_model_serializers/adapter/attributes.rb +76 -0
- data/lib/active_model_serializers/adapter/base.rb +83 -0
- data/lib/active_model_serializers/adapter/json.rb +21 -0
- data/lib/active_model_serializers/adapter/json_api/deserialization.rb +213 -0
- data/lib/active_model_serializers/adapter/json_api/error.rb +96 -0
- data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +49 -0
- data/lib/active_model_serializers/adapter/json_api/link.rb +83 -0
- data/lib/active_model_serializers/adapter/json_api/meta.rb +37 -0
- data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +62 -0
- data/lib/active_model_serializers/adapter/json_api/relationship.rb +52 -0
- data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +37 -0
- data/lib/active_model_serializers/adapter/json_api.rb +516 -0
- data/lib/active_model_serializers/adapter/null.rb +9 -0
- data/lib/active_model_serializers/adapter.rb +92 -0
- data/lib/active_model_serializers/callbacks.rb +55 -0
- data/lib/active_model_serializers/deprecate.rb +55 -0
- data/lib/active_model_serializers/deserialization.rb +13 -0
- data/lib/active_model_serializers/json_pointer.rb +14 -0
- data/lib/active_model_serializers/key_transform.rb +70 -0
- data/lib/active_model_serializers/logging.rb +122 -0
- data/lib/active_model_serializers/model.rb +49 -0
- data/lib/active_model_serializers/railtie.rb +46 -0
- data/lib/active_model_serializers/register_jsonapi_renderer.rb +65 -0
- data/lib/active_model_serializers/serializable_resource.rb +81 -0
- data/lib/active_model_serializers/serialization_context.rb +32 -0
- data/lib/active_model_serializers/test/schema.rb +138 -0
- data/lib/active_model_serializers/test/serializer.rb +125 -0
- data/lib/active_model_serializers/test.rb +7 -0
- data/lib/active_model_serializers.rb +32 -89
- data/lib/generators/rails/USAGE +6 -0
- data/lib/generators/rails/resource_override.rb +10 -0
- data/lib/generators/rails/serializer_generator.rb +36 -0
- data/lib/generators/rails/templates/serializer.rb.erb +8 -0
- data/lib/grape/active_model_serializers.rb +14 -0
- data/lib/grape/formatters/active_model_serializers.rb +15 -0
- data/lib/grape/helpers/active_model_serializers.rb +16 -0
- data/test/action_controller/adapter_selector_test.rb +53 -0
- data/test/action_controller/explicit_serializer_test.rb +134 -0
- data/test/action_controller/json/include_test.rb +167 -0
- data/test/action_controller/json_api/deserialization_test.rb +112 -0
- data/test/action_controller/json_api/errors_test.rb +41 -0
- data/test/action_controller/json_api/linked_test.rb +197 -0
- data/test/action_controller/json_api/pagination_test.rb +116 -0
- data/test/action_controller/json_api/transform_test.rb +181 -0
- data/test/action_controller/serialization_scope_name_test.rb +229 -0
- data/test/action_controller/serialization_test.rb +469 -0
- data/test/active_model_serializers/adapter_for_test.rb +208 -0
- data/test/active_model_serializers/json_pointer_test.rb +20 -0
- data/test/active_model_serializers/key_transform_test.rb +263 -0
- data/test/active_model_serializers/logging_test.rb +77 -0
- data/test/active_model_serializers/model_test.rb +9 -0
- data/test/active_model_serializers/railtie_test_isolated.rb +63 -0
- data/test/active_model_serializers/serialization_context_test_isolated.rb +58 -0
- data/test/active_model_serializers/test/schema_test.rb +130 -0
- data/test/active_model_serializers/test/serializer_test.rb +62 -0
- data/test/active_record_test.rb +9 -0
- data/test/adapter/deprecation_test.rb +100 -0
- data/test/adapter/json/belongs_to_test.rb +45 -0
- data/test/adapter/json/collection_test.rb +90 -0
- data/test/adapter/json/has_many_test.rb +45 -0
- data/test/adapter/json/transform_test.rb +93 -0
- data/test/adapter/json_api/belongs_to_test.rb +155 -0
- data/test/adapter/json_api/collection_test.rb +95 -0
- data/test/adapter/json_api/errors_test.rb +78 -0
- data/test/adapter/json_api/fields_test.rb +87 -0
- data/test/adapter/json_api/has_many_embed_ids_test.rb +43 -0
- data/test/adapter/json_api/has_many_explicit_serializer_test.rb +96 -0
- data/test/adapter/json_api/has_many_test.rb +144 -0
- data/test/adapter/json_api/has_one_test.rb +80 -0
- data/test/adapter/json_api/json_api_test.rb +35 -0
- data/test/adapter/json_api/linked_test.rb +392 -0
- data/test/adapter/json_api/links_test.rb +93 -0
- data/test/adapter/json_api/pagination_links_test.rb +166 -0
- data/test/adapter/json_api/parse_test.rb +137 -0
- data/test/adapter/json_api/relationship_test.rb +161 -0
- data/test/adapter/json_api/relationships_test.rb +199 -0
- data/test/adapter/json_api/resource_identifier_test.rb +85 -0
- data/test/adapter/json_api/resource_meta_test.rb +100 -0
- data/test/adapter/json_api/toplevel_jsonapi_test.rb +82 -0
- data/test/adapter/json_api/transform_test.rb +502 -0
- data/test/adapter/json_api/type_test.rb +61 -0
- data/test/adapter/json_test.rb +45 -0
- data/test/adapter/null_test.rb +23 -0
- data/test/adapter/polymorphic_test.rb +171 -0
- data/test/adapter_test.rb +67 -0
- data/test/array_serializer_test.rb +20 -73
- data/test/benchmark/app.rb +65 -0
- data/test/benchmark/benchmarking_support.rb +67 -0
- data/test/benchmark/bm_caching.rb +119 -0
- data/test/benchmark/bm_transform.rb +34 -0
- data/test/benchmark/config.ru +3 -0
- data/test/benchmark/controllers.rb +84 -0
- data/test/benchmark/fixtures.rb +219 -0
- data/test/cache_test.rb +485 -0
- data/test/collection_serializer_test.rb +110 -0
- data/test/fixtures/active_record.rb +78 -0
- data/test/fixtures/poro.rb +282 -0
- data/test/generators/scaffold_controller_generator_test.rb +24 -0
- data/test/generators/serializer_generator_test.rb +57 -0
- data/test/grape_test.rb +82 -0
- data/test/include_tree/from_include_args_test.rb +26 -0
- data/test/include_tree/from_string_test.rb +94 -0
- data/test/include_tree/include_args_to_hash_test.rb +64 -0
- data/test/lint_test.rb +49 -0
- data/test/logger_test.rb +18 -0
- data/test/poro_test.rb +9 -0
- data/test/serializable_resource_test.rb +83 -0
- data/test/serializers/association_macros_test.rb +36 -0
- data/test/serializers/associations_test.rb +295 -0
- data/test/serializers/attribute_test.rb +151 -0
- data/test/serializers/attributes_test.rb +52 -0
- data/test/serializers/caching_configuration_test_isolated.rb +170 -0
- data/test/serializers/configuration_test.rb +32 -0
- data/test/serializers/fieldset_test.rb +14 -0
- data/test/serializers/meta_test.rb +196 -0
- data/test/serializers/options_test.rb +21 -0
- data/test/serializers/read_attribute_for_serialization_test.rb +79 -0
- data/test/serializers/root_test.rb +21 -0
- data/test/serializers/serialization_test.rb +55 -0
- data/test/serializers/serializer_for_test.rb +134 -0
- data/test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
- data/test/support/isolated_unit.rb +79 -0
- data/test/support/rails5_shims.rb +47 -0
- data/test/support/rails_app.rb +45 -0
- data/test/support/schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
- data/test/support/schemas/active_model_serializers/test/schema_test/my/show.json +6 -0
- data/test/support/schemas/custom/show.json +7 -0
- data/test/support/schemas/hyper_schema.json +93 -0
- data/test/support/schemas/render_using_json_api.json +43 -0
- data/test/support/schemas/simple_json_pointers.json +10 -0
- data/test/support/serialization_testing.rb +53 -0
- data/test/test_helper.rb +48 -23
- metadata +449 -43
- data/DESIGN.textile +0 -586
- data/Gemfile.edge +0 -9
- data/bench/perf.rb +0 -43
- data/cruft.md +0 -19
- data/lib/active_model/array_serializer.rb +0 -104
- data/lib/active_record/serializer_override.rb +0 -16
- data/lib/generators/resource_override.rb +0 -13
- data/lib/generators/serializer/USAGE +0 -9
- data/lib/generators/serializer/serializer_generator.rb +0 -42
- data/lib/generators/serializer/templates/serializer.rb +0 -19
- data/test/association_test.rb +0 -592
- data/test/caching_test.rb +0 -96
- data/test/generators_test.rb +0 -85
- data/test/no_serialization_scope_test.rb +0 -34
- data/test/serialization_scope_name_test.rb +0 -67
- data/test/serialization_test.rb +0 -392
- data/test/serializer_support_test.rb +0 -51
- data/test/serializer_test.rb +0 -1465
- data/test/test_fakes.rb +0 -217
data/README.md
CHANGED
|
@@ -1,655 +1,167 @@
|
|
|
1
|
-
|
|
1
|
+
# ActiveModelSerializers
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<table>
|
|
4
|
+
<tr>
|
|
5
|
+
<td>Build Status</td>
|
|
6
|
+
<td>
|
|
7
|
+
<a href="https://travis-ci.org/rails-api/active_model_serializers"><img src="https://travis-ci.org/rails-api/active_model_serializers.svg?branch=master" alt="Build Status" ></a>
|
|
8
|
+
<a href="https://ci.appveyor.com/project/joaomdmoura/active-model-serializers/branch/master"><img src="https://ci.appveyor.com/api/projects/status/x6xdjydutm54gvyt/branch/master?svg=true" alt="Build status"></a>
|
|
9
|
+
</td>
|
|
10
|
+
</tr>
|
|
11
|
+
<tr>
|
|
12
|
+
<td>Code Quality</td>
|
|
13
|
+
<td>
|
|
14
|
+
<a href="https://codeclimate.com/github/rails-api/active_model_serializers"><img src="https://codeclimate.com/github/rails-api/active_model_serializers/badges/gpa.svg" alt="Code Quality"></a>
|
|
15
|
+
<a href="https://codebeat.co/projects/github-com-rails-api-active_model_serializers"><img src="https://codebeat.co/badges/a9ab35fa-8b5a-4680-9d4e-a81f9a55ebcd" alt="codebeat" ></a>
|
|
16
|
+
<a href="https://codeclimate.com/github/rails-api/active_model_serializers/coverage"><img src="https://codeclimate.com/github/rails-api/active_model_serializers/badges/coverage.svg" alt="Test Coverage"></a>
|
|
17
|
+
</td>
|
|
18
|
+
</tr>
|
|
19
|
+
<tr>
|
|
20
|
+
<td>Issue Stats</td>
|
|
21
|
+
<td>
|
|
22
|
+
<a href="https://github.com/rails-api/active_model_serializers/pulse/monthly">Pulse</a>
|
|
23
|
+
</td>
|
|
24
|
+
</tr>
|
|
25
|
+
</table>
|
|
4
26
|
|
|
5
|
-
The purpose of `ActiveModel::Serializers` is to provide an object to
|
|
6
|
-
encapsulate serialization of `ActiveModel` objects, including `ActiveRecord`
|
|
7
|
-
objects.
|
|
8
27
|
|
|
9
|
-
|
|
10
|
-
customize serialization based upon whether a user is authorized to see the
|
|
11
|
-
content.
|
|
28
|
+
## Documentation
|
|
12
29
|
|
|
13
|
-
|
|
14
|
-
|
|
30
|
+
- [0.10 (master) Documentation](https://github.com/rails-api/active_model_serializers/tree/master)
|
|
31
|
+
- [](http://www.rubydoc.info/github/rails-api/active_model_serializers/v0.10.0)
|
|
32
|
+
- [Guides](docs)
|
|
33
|
+
- [0.9 (0-9-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-9-stable)
|
|
34
|
+
- [](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-9-stable)
|
|
35
|
+
- [0.8 (0-8-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-8-stable)
|
|
36
|
+
- [](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-8-stable)
|
|
15
37
|
|
|
16
|
-
|
|
38
|
+
## About
|
|
17
39
|
|
|
18
|
-
|
|
19
|
-
`Gemfile`:
|
|
40
|
+
ActiveModelSerializers brings convention over configuration to your JSON generation.
|
|
20
41
|
|
|
21
|
-
|
|
22
|
-
gem "active_model_serializers", "~> 0.8.0"
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Then, install it on the command line:
|
|
26
|
-
|
|
27
|
-
```
|
|
28
|
-
$ bundle install
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
# Creating a Serializer
|
|
32
|
-
|
|
33
|
-
The easiest way to create a new serializer is to generate a new resource, which
|
|
34
|
-
will generate a serializer at the same time:
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
$ rails g resource post title:string body:string
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
This will generate a serializer in `app/serializers/post_serializer.rb` for
|
|
41
|
-
your new model. You can also generate a serializer for an existing model with
|
|
42
|
-
the serializer generator:
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
$ rails g serializer post
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Support for PORO's and other ORM's.
|
|
49
|
-
|
|
50
|
-
Currently `ActiveModel::Serializers` adds serialization support to all models
|
|
51
|
-
that descend from `ActiveRecord` or include `Mongoid::Document`. If you are
|
|
52
|
-
using another ORM, or if you are using objects that are `ActiveModel`
|
|
53
|
-
compliant but do not descend from `ActiveRecord` or include
|
|
54
|
-
`Mongoid::Document`, you must add an include statement for
|
|
55
|
-
`ActiveModel::SerializerSupport` to make models serializable. If you
|
|
56
|
-
also want to make collections serializable, you should include
|
|
57
|
-
`ActiveModel::ArraySerializerSupport` into your ORM's
|
|
58
|
-
relation/criteria class.
|
|
59
|
-
|
|
60
|
-
# ActiveModel::Serializer
|
|
61
|
-
|
|
62
|
-
All new serializers descend from ActiveModel::Serializer
|
|
63
|
-
|
|
64
|
-
# render :json
|
|
65
|
-
|
|
66
|
-
In your controllers, when you use `render :json`, Rails will now first search
|
|
67
|
-
for a serializer for the object and use it if available.
|
|
68
|
-
|
|
69
|
-
```ruby
|
|
70
|
-
class PostsController < ApplicationController
|
|
71
|
-
def show
|
|
72
|
-
@post = Post.find(params[:id])
|
|
73
|
-
render :json => @post
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
In this case, Rails will look for a serializer named `PostSerializer`, and if
|
|
79
|
-
it exists, use it to serialize the `Post`.
|
|
80
|
-
|
|
81
|
-
This also works with `respond_with`, which uses `to_json` under the hood. Also
|
|
82
|
-
note that any options passed to `render :json` will be passed to your
|
|
83
|
-
serializer and available as `@options` inside.
|
|
84
|
-
|
|
85
|
-
To specify a custom serializer for an object, there are 2 options:
|
|
86
|
-
|
|
87
|
-
#### 1. Specify the serializer in your model:
|
|
88
|
-
|
|
89
|
-
```ruby
|
|
90
|
-
class Post < ActiveRecord::Base
|
|
91
|
-
def active_model_serializer
|
|
92
|
-
FancyPostSerializer
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
#### 2. Specify the serializer when you render the object:
|
|
98
|
-
|
|
99
|
-
```ruby
|
|
100
|
-
render :json => @post, :serializer => FancyPostSerializer
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
## Arrays
|
|
104
|
-
|
|
105
|
-
In your controllers, when you use `render :json` for an array of objects, AMS will
|
|
106
|
-
use `ActiveModel::ArraySerializer` (included in this project) as the base serializer,
|
|
107
|
-
and the individual `Serializer` for the objects contained in that array.
|
|
108
|
-
|
|
109
|
-
```ruby
|
|
110
|
-
class PostSerializer < ActiveModel::Serializer
|
|
111
|
-
attributes :title, :body
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
class PostsController < ApplicationController
|
|
115
|
-
def index
|
|
116
|
-
@posts = Post.all
|
|
117
|
-
render :json => @posts
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
Given the example above, the index action will return
|
|
123
|
-
|
|
124
|
-
```json
|
|
125
|
-
{
|
|
126
|
-
"posts":
|
|
127
|
-
[
|
|
128
|
-
{ "title": "Post 1", "body": "Hello!" },
|
|
129
|
-
{ "title": "Post 2", "body": "Goodbye!" }
|
|
130
|
-
]
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
By default, the root element is the name of the controller. For example, `PostsController`
|
|
135
|
-
generates a root element "posts". To change it:
|
|
136
|
-
|
|
137
|
-
```ruby
|
|
138
|
-
render :json => @posts, :root => "some_posts"
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
You may disable the root element for arrays at the top level, which will result in
|
|
142
|
-
more concise json. See the next section for ways on how to do this. Disabling the
|
|
143
|
-
root element of the array with any of those methods will produce
|
|
144
|
-
|
|
145
|
-
```json
|
|
146
|
-
[
|
|
147
|
-
{ "title": "Post 1", "body": "Hello!" },
|
|
148
|
-
{ "title": "Post 2", "body": "Goodbye!" }
|
|
149
|
-
]
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
To specify a custom serializer for the items within an array:
|
|
153
|
-
|
|
154
|
-
```ruby
|
|
155
|
-
render :json => @posts, :each_serializer => FancyPostSerializer
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
## Disabling the root element
|
|
159
|
-
|
|
160
|
-
You have 4 options to disable the root element, each with a slightly different scope:
|
|
161
|
-
|
|
162
|
-
#### 1. Disable root globally for all, or per class
|
|
163
|
-
|
|
164
|
-
In an initializer:
|
|
165
|
-
|
|
166
|
-
```ruby
|
|
167
|
-
ActiveSupport.on_load(:active_model_serializers) do
|
|
168
|
-
# Disable for all serializers (except ArraySerializer)
|
|
169
|
-
ActiveModel::Serializer.root = false
|
|
170
|
-
|
|
171
|
-
# Disable for ArraySerializer
|
|
172
|
-
ActiveModel::ArraySerializer.root = false
|
|
173
|
-
end
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
#### 2. Disable root per render call in your controller
|
|
177
|
-
|
|
178
|
-
```ruby
|
|
179
|
-
render :json => @posts, :root => false
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
#### 3. Subclass the serializer, and specify using it
|
|
183
|
-
|
|
184
|
-
```ruby
|
|
185
|
-
class CustomArraySerializer < ActiveModel::ArraySerializer
|
|
186
|
-
self.root = false
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
# controller:
|
|
190
|
-
render :json => @posts, :serializer => CustomArraySerializer
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
#### 4. Define default_serializer_options in your controller
|
|
194
|
-
|
|
195
|
-
If you define `default_serializer_options` method in your controller,
|
|
196
|
-
all serializers in actions of this controller and it's children will use them.
|
|
197
|
-
One of the options may be `root: false`
|
|
198
|
-
|
|
199
|
-
```ruby
|
|
200
|
-
def default_serializer_options
|
|
201
|
-
{
|
|
202
|
-
root: false
|
|
203
|
-
}
|
|
204
|
-
end
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
## Getting the old version
|
|
208
|
-
|
|
209
|
-
If you find that your project is already relying on the old rails to_json
|
|
210
|
-
change `render :json` to `render :json => @your_object.to_json`.
|
|
211
|
-
|
|
212
|
-
# Attributes and Associations
|
|
213
|
-
|
|
214
|
-
Once you have a serializer, you can specify which attributes and associations
|
|
215
|
-
you would like to include in the serialized form.
|
|
216
|
-
|
|
217
|
-
```ruby
|
|
218
|
-
class PostSerializer < ActiveModel::Serializer
|
|
219
|
-
attributes :id, :title, :body
|
|
220
|
-
has_many :comments
|
|
221
|
-
end
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
## Attributes
|
|
42
|
+
ActiveModelSerializers works through two components: **serializers** and **adapters**.
|
|
225
43
|
|
|
226
|
-
|
|
227
|
-
object you passed to `render :json`. It uses
|
|
228
|
-
`read_attribute_for_serialization`, which `ActiveRecord` objects implement as a
|
|
229
|
-
regular attribute lookup.
|
|
44
|
+
Serializers describe _which_ attributes and relationships should be serialized.
|
|
230
45
|
|
|
231
|
-
|
|
232
|
-
presence of a method with the name of the attribute. This allows serializers to
|
|
233
|
-
include properties beyond the simple attributes of the model. For example:
|
|
46
|
+
Adapters describe _how_ attributes and relationships should be serialized.
|
|
234
47
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
48
|
+
SerializableResource co-ordinates the resource, Adapter and Serializer to produce the
|
|
49
|
+
resource serialization. The serialization has the `#as_json`, `#to_json` and `#serializable_hash`
|
|
50
|
+
methods used by the Rails JSON Renderer. (SerializableResource actually delegates
|
|
51
|
+
these methods to the adapter.)
|
|
238
52
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
```
|
|
53
|
+
By default ActiveModelSerializers will use the **Attributes Adapter**.
|
|
54
|
+
But we strongly advise you to use **JsonApi Adapter**, which
|
|
55
|
+
follows 1.0 of the format specified in [jsonapi.org/format](http://jsonapi.org/format).
|
|
56
|
+
Check how to change the adapter in the sections below.
|
|
244
57
|
|
|
245
|
-
|
|
246
|
-
serialized as `object`.
|
|
58
|
+
## RELEASE CANDIDATE, PLEASE READ
|
|
247
59
|
|
|
248
|
-
|
|
249
|
-
authorization context to your serializer. By default, the context
|
|
250
|
-
is the current user of your application, but this
|
|
251
|
-
[can be customized](#customizing-scope).
|
|
60
|
+
This is the **master** branch of ActiveModelSerializers.
|
|
252
61
|
|
|
253
|
-
|
|
254
|
-
`include_[ATTRIBUTE]?` to determine whether a particular attribute should be
|
|
255
|
-
included in the output. This is typically used to customize output
|
|
256
|
-
based on `current_user`. For example:
|
|
62
|
+
It will become the `0.10.0` release when it's ready. Currently this is a release candidate.
|
|
257
63
|
|
|
258
|
-
|
|
259
|
-
class PostSerializer < ActiveModel::Serializer
|
|
260
|
-
attributes :id, :title, :body, :author
|
|
64
|
+
`0.10.x` is **not** backward compatible with `0.9.x` nor `0.8.x`.
|
|
261
65
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
end
|
|
265
|
-
end
|
|
266
|
-
```
|
|
66
|
+
`0.10.x` will be based on the `0.8.0` code, but with a more flexible
|
|
67
|
+
architecture. We'd love your help. [Learn how you can help here.](CONTRIBUTING.md)
|
|
267
68
|
|
|
268
|
-
|
|
269
|
-
calculated without some sophisticated static code analysis. To specify the
|
|
270
|
-
type of a computed attribute:
|
|
69
|
+
It is generally safe and recommended to use the master branch.
|
|
271
70
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
attributes :first_name, :last_name, {:full_name => :string}
|
|
71
|
+
For more information, see the post '[The future of
|
|
72
|
+
AMS](https://medium.com/@joaomdmoura/the-future-of-ams-e5f9047ca7e9)'.
|
|
275
73
|
|
|
276
|
-
|
|
277
|
-
"#{object.first_name} #{object.last_name}"
|
|
278
|
-
end
|
|
279
|
-
end
|
|
280
|
-
```
|
|
74
|
+
## Installation
|
|
281
75
|
|
|
282
|
-
|
|
283
|
-
in ActiveRecord, you can use the `:key` option to customize it:
|
|
76
|
+
Add this line to your application's Gemfile:
|
|
284
77
|
|
|
285
|
-
```ruby
|
|
286
|
-
class PostSerializer < ActiveModel::Serializer
|
|
287
|
-
attributes :id, :body
|
|
288
|
-
|
|
289
|
-
# look up :subject on the model, but use +title+ in the JSON
|
|
290
|
-
attribute :subject, :key => :title
|
|
291
|
-
has_many :comments
|
|
292
|
-
end
|
|
293
78
|
```
|
|
294
|
-
|
|
295
|
-
If you would like to add meta information to the outputted JSON, use the `:meta`
|
|
296
|
-
option:
|
|
297
|
-
|
|
298
|
-
```ruby
|
|
299
|
-
render :json => @posts, :serializer => CustomArraySerializer, :meta => {:total => 10}
|
|
79
|
+
gem 'active_model_serializers'
|
|
300
80
|
```
|
|
301
81
|
|
|
302
|
-
|
|
82
|
+
And then execute:
|
|
303
83
|
|
|
304
|
-
```json
|
|
305
|
-
{
|
|
306
|
-
"meta": { "total": 10 },
|
|
307
|
-
"posts": [
|
|
308
|
-
{ "title": "Post 1", "body": "Hello!" },
|
|
309
|
-
{ "title": "Post 2", "body": "Goodbye!" }
|
|
310
|
-
]
|
|
311
|
-
}
|
|
312
84
|
```
|
|
313
|
-
|
|
314
|
-
If you would like to change the meta key name you can use the `:meta_key` option:
|
|
315
|
-
|
|
316
|
-
```ruby
|
|
317
|
-
render :json => @posts, :serializer => CustomArraySerializer, :meta => {:total => 10}, :meta_key => 'meta_object'
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
The above usage of `:meta_key` will produce the following:
|
|
321
|
-
|
|
322
|
-
```json
|
|
323
|
-
{
|
|
324
|
-
"meta_object": { "total": 10 },
|
|
325
|
-
"posts": [
|
|
326
|
-
{ "title": "Post 1", "body": "Hello!" },
|
|
327
|
-
{ "title": "Post 2", "body": "Goodbye!" }
|
|
328
|
-
]
|
|
329
|
-
}
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
If you would like direct, low-level control of attribute serialization, you can
|
|
333
|
-
completely override the `attributes` method to return the hash you need:
|
|
334
|
-
|
|
335
|
-
```ruby
|
|
336
|
-
class PersonSerializer < ActiveModel::Serializer
|
|
337
|
-
attributes :first_name, :last_name
|
|
338
|
-
|
|
339
|
-
def attributes
|
|
340
|
-
hash = super
|
|
341
|
-
if current_user.admin?
|
|
342
|
-
hash["ssn"] = object.ssn
|
|
343
|
-
hash["secret"] = object.mothers_maiden_name
|
|
344
|
-
end
|
|
345
|
-
hash
|
|
346
|
-
end
|
|
347
|
-
end
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
## Associations
|
|
351
|
-
|
|
352
|
-
For specified associations, the serializer will look up the association and
|
|
353
|
-
then serialize each element of the association. For instance, a `has_many
|
|
354
|
-
:comments` association will create a new `CommentSerializer` for each comment
|
|
355
|
-
and use it to serialize the comment.
|
|
356
|
-
|
|
357
|
-
By default, serializers simply look up the association on the original object.
|
|
358
|
-
You can customize this behavior by implementing a method with the name of the
|
|
359
|
-
association and returning a different Array. Often, you will do this to
|
|
360
|
-
customize the objects returned based on the current user.
|
|
361
|
-
|
|
362
|
-
```ruby
|
|
363
|
-
class PostSerializer < ActiveModel::Serializer
|
|
364
|
-
attributes :id, :title, :body
|
|
365
|
-
has_many :comments
|
|
366
|
-
|
|
367
|
-
# only let the user see comments he created.
|
|
368
|
-
def comments
|
|
369
|
-
object.comments.where(:created_by => current_user)
|
|
370
|
-
end
|
|
371
|
-
end
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
As with attributes, you can change the JSON key that the serializer should
|
|
375
|
-
use for a particular association.
|
|
376
|
-
|
|
377
|
-
```ruby
|
|
378
|
-
class PostSerializer < ActiveModel::Serializer
|
|
379
|
-
attributes :id, :title, :body
|
|
380
|
-
|
|
381
|
-
# look up comments, but use +my_comments+ as the key in JSON
|
|
382
|
-
has_many :comments, :key => :my_comments
|
|
383
|
-
end
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
Also, as with attributes, serializers will check for the presence
|
|
387
|
-
of a method named `include_[ASSOCIATION]?` to determine whether a particular association
|
|
388
|
-
should be included in the output. For example:
|
|
389
|
-
|
|
390
|
-
```ruby
|
|
391
|
-
class PostSerializer < ActiveModel::Serializer
|
|
392
|
-
attributes :id, :title, :body
|
|
393
|
-
has_many :comments
|
|
394
|
-
|
|
395
|
-
def include_comments?
|
|
396
|
-
!object.comments_disabled?
|
|
397
|
-
end
|
|
398
|
-
end
|
|
85
|
+
$ bundle
|
|
399
86
|
```
|
|
400
87
|
|
|
401
|
-
|
|
402
|
-
override `include_associations!` to specify which associations should be included:
|
|
88
|
+
## Getting Started
|
|
403
89
|
|
|
404
|
-
|
|
405
|
-
class PostSerializer < ActiveModel::Serializer
|
|
406
|
-
attributes :id, :title, :body
|
|
407
|
-
has_one :author
|
|
408
|
-
has_many :comments
|
|
409
|
-
|
|
410
|
-
def include_associations!
|
|
411
|
-
include! :author if current_user.admin?
|
|
412
|
-
include! :comments unless object.comments_disabled?
|
|
413
|
-
end
|
|
414
|
-
end
|
|
415
|
-
```
|
|
90
|
+
See [Getting Started](docs/general/getting_started.md) for the nuts and bolts.
|
|
416
91
|
|
|
417
|
-
|
|
92
|
+
More information is available in the [Guides](docs) and
|
|
93
|
+
[High-level behavior](README.md#high-level-behavior).
|
|
418
94
|
|
|
419
|
-
|
|
420
|
-
has_many :comments, :serializer => CommentShortSerializer
|
|
421
|
-
has_one :reviewer, :polymorphic => true
|
|
422
|
-
```
|
|
95
|
+
## Getting Help
|
|
423
96
|
|
|
424
|
-
|
|
97
|
+
If you find a bug, please report an [Issue](https://github.com/rails-api/active_model_serializers/issues/new)
|
|
98
|
+
and see our [contributing guide](CONTRIBUTING.md).
|
|
425
99
|
|
|
426
|
-
|
|
100
|
+
If you have a question, please [post to Stack Overflow](http://stackoverflow.com/questions/tagged/active-model-serializers).
|
|
427
101
|
|
|
428
|
-
|
|
429
|
-
you have a post, the outputted JSON will look like:
|
|
102
|
+
If you'd like to chat, we have a [community slack](http://amserializers.herokuapp.com).
|
|
430
103
|
|
|
431
|
-
|
|
432
|
-
{
|
|
433
|
-
"post": {
|
|
434
|
-
"id": 1,
|
|
435
|
-
"title": "New post",
|
|
436
|
-
"body": "A body!",
|
|
437
|
-
"comments": [
|
|
438
|
-
{ "id": 1, "body": "what a dumb post" }
|
|
439
|
-
]
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
```
|
|
104
|
+
Thanks!
|
|
443
105
|
|
|
444
|
-
|
|
445
|
-
better to supply an Array of IDs for the association. This makes your API more
|
|
446
|
-
flexible from a performance standpoint and avoids wasteful duplication.
|
|
106
|
+
## High-level behavior
|
|
447
107
|
|
|
448
|
-
|
|
108
|
+
Given a [serializable model](lib/active_model/serializer/lint.rb):
|
|
449
109
|
|
|
450
110
|
```ruby
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
attributes :id, :title, :body
|
|
455
|
-
has_many :comments
|
|
111
|
+
# either
|
|
112
|
+
class SomeResource < ActiveRecord::Base
|
|
113
|
+
# columns: title, body
|
|
456
114
|
end
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
```json
|
|
462
|
-
{
|
|
463
|
-
"post": {
|
|
464
|
-
"id": 1,
|
|
465
|
-
"title": "New post",
|
|
466
|
-
"body": "A body!",
|
|
467
|
-
"comment_ids": [ 1, 2, 3 ]
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
Alternatively, you can choose to embed only the ids or the associated objects per association:
|
|
473
|
-
|
|
474
|
-
```ruby
|
|
475
|
-
class PostSerializer < ActiveModel::Serializer
|
|
476
|
-
attributes :id, :title, :body
|
|
477
|
-
|
|
478
|
-
has_many :comments, embed: :objects
|
|
479
|
-
has_many :tags, embed: :ids
|
|
115
|
+
# or
|
|
116
|
+
class SomeResource < ActiveModelSerializers::Model
|
|
117
|
+
attr_accessor :title, :body
|
|
480
118
|
end
|
|
481
119
|
```
|
|
482
120
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
```json
|
|
486
|
-
{
|
|
487
|
-
"post": {
|
|
488
|
-
"id": 1,
|
|
489
|
-
"title": "New post",
|
|
490
|
-
"body": "A body!",
|
|
491
|
-
"comments": [
|
|
492
|
-
{ "id": 1, "body": "what a dumb post" }
|
|
493
|
-
],
|
|
494
|
-
"tag_ids": [ 1, 2, 3 ]
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
In addition to supplying an Array of IDs, you may want to side-load the data
|
|
500
|
-
alongside the main object. This makes it easier to process the entire package
|
|
501
|
-
of data without having to recursively scan the tree looking for embedded
|
|
502
|
-
information. It also ensures that associations that are shared between several
|
|
503
|
-
objects (like tags), are only delivered once for the entire payload.
|
|
504
|
-
|
|
505
|
-
You can specify that the data be included like this:
|
|
121
|
+
And initialized as:
|
|
506
122
|
|
|
507
123
|
```ruby
|
|
508
|
-
|
|
509
|
-
embed :ids, :include => true
|
|
510
|
-
|
|
511
|
-
attributes :id, :title, :body
|
|
512
|
-
has_many :comments
|
|
513
|
-
end
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
Assuming that the comments also `has_many :tags`, you will get a JSON like
|
|
517
|
-
this:
|
|
518
|
-
|
|
519
|
-
```json
|
|
520
|
-
{
|
|
521
|
-
"post": {
|
|
522
|
-
"id": 1,
|
|
523
|
-
"title": "New post",
|
|
524
|
-
"body": "A body!",
|
|
525
|
-
"comment_ids": [ 1, 2 ]
|
|
526
|
-
},
|
|
527
|
-
"comments": [
|
|
528
|
-
{ "id": 1, "body": "what a dumb post", "tag_ids": [ 1, 2 ] },
|
|
529
|
-
{ "id": 2, "body": "i liked it", "tag_ids": [ 1, 3 ] },
|
|
530
|
-
],
|
|
531
|
-
"tags": [
|
|
532
|
-
{ "id": 1, "name": "short" },
|
|
533
|
-
{ "id": 2, "name": "whiny" },
|
|
534
|
-
{ "id": 3, "name": "happy" }
|
|
535
|
-
]
|
|
536
|
-
}
|
|
124
|
+
resource = SomeResource.new(title: 'ActiveModelSerializers', body: 'Convention over configuration')
|
|
537
125
|
```
|
|
538
126
|
|
|
539
|
-
|
|
540
|
-
used to reference them:
|
|
127
|
+
Given a serializer for the serializable model:
|
|
541
128
|
|
|
542
129
|
```ruby
|
|
543
|
-
class
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
attributes :id, :title, :body
|
|
547
|
-
has_many :comments, :key => :comment_ids, :root => :comment_objects
|
|
130
|
+
class SomeSerializer < ActiveModel::Serializer
|
|
131
|
+
attribute :title, key: :name
|
|
132
|
+
attributes :body
|
|
548
133
|
end
|
|
549
134
|
```
|
|
550
135
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
```json
|
|
554
|
-
{
|
|
555
|
-
"post": {
|
|
556
|
-
"id": 1,
|
|
557
|
-
"title": "New post",
|
|
558
|
-
"body": "A body!",
|
|
559
|
-
"comment_ids": [ 1 ]
|
|
560
|
-
},
|
|
561
|
-
"comment_objects": [
|
|
562
|
-
{ "id": 1, "body": "what a dumb post" }
|
|
563
|
-
]
|
|
564
|
-
}
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
You can also specify a different attribute to use rather than the ID of the
|
|
568
|
-
objects:
|
|
136
|
+
The model can be serialized as:
|
|
569
137
|
|
|
570
138
|
```ruby
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
has_many :comments, :embed_key => :external_id
|
|
576
|
-
end
|
|
139
|
+
options = {}
|
|
140
|
+
serialization = ActiveModelSerializers::SerializableResource.new(resource, options)
|
|
141
|
+
serialization.to_json
|
|
142
|
+
serialization.as_json
|
|
577
143
|
```
|
|
578
144
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
```json
|
|
582
|
-
{
|
|
583
|
-
"post": {
|
|
584
|
-
"id": 1,
|
|
585
|
-
"title": "New post",
|
|
586
|
-
"body": "A body!",
|
|
587
|
-
"comment_ids": [ "COMM001" ]
|
|
588
|
-
},
|
|
589
|
-
"comments": [
|
|
590
|
-
{ "id": 1, "external_id": "COMM001", "body": "what a dumb post" }
|
|
591
|
-
]
|
|
592
|
-
}
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
**NOTE**: The `embed :ids` mechanism is primary useful for clients that process
|
|
596
|
-
data in bulk and load it into a local store. For these clients, the ability to
|
|
597
|
-
easily see all of the data per type, rather than having to recursively scan the
|
|
598
|
-
data looking for information, is extremely useful.
|
|
599
|
-
|
|
600
|
-
If you are mostly working with the data in simple scenarios and manually making
|
|
601
|
-
Ajax requests, you probably just want to use the default embedded behavior.
|
|
602
|
-
|
|
603
|
-
## Customizing Scope
|
|
604
|
-
|
|
605
|
-
In a serializer, `current_user` is the current authorization scope which the controller
|
|
606
|
-
provides to the serializer when you call `render :json`. By default, this is
|
|
607
|
-
`current_user`, but can be customized in your controller by calling
|
|
608
|
-
`serialization_scope`:
|
|
145
|
+
SerializableResource delegates to the adapter, which it builds as:
|
|
609
146
|
|
|
610
147
|
```ruby
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
148
|
+
adapter_options = {}
|
|
149
|
+
adapter = ActiveModelSerializers::Adapter.create(serializer, adapter_options)
|
|
150
|
+
adapter.to_json
|
|
151
|
+
adapter.as_json
|
|
152
|
+
adapter.serializable_hash
|
|
614
153
|
```
|
|
615
154
|
|
|
616
|
-
The
|
|
617
|
-
`current_admin`.
|
|
618
|
-
|
|
619
|
-
Please note that, until now, `serialization_scope` doesn't accept a second
|
|
620
|
-
object with options for specifying which actions should or should not take a
|
|
621
|
-
given scope in consideration.
|
|
622
|
-
|
|
623
|
-
To be clear, it's not possible, yet, to do something like this:
|
|
155
|
+
The adapter formats the serializer's attributes and associations (a.k.a. includes):
|
|
624
156
|
|
|
625
157
|
```ruby
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
158
|
+
serializer_options = {}
|
|
159
|
+
serializer = SomeSerializer.new(resource, serializer_options)
|
|
160
|
+
serializer.attributes
|
|
161
|
+
serializer.associations
|
|
629
162
|
```
|
|
163
|
+
See [ARCHITECTURE.md](docs/ARCHITECTURE.md) for more information.
|
|
630
164
|
|
|
631
|
-
|
|
632
|
-
consideration for its scope, you may use something like this:
|
|
633
|
-
|
|
634
|
-
```ruby
|
|
635
|
-
class CitiesController < ApplicationController
|
|
636
|
-
serialization_scope nil
|
|
637
|
-
|
|
638
|
-
def index
|
|
639
|
-
@cities = City.all
|
|
640
|
-
|
|
641
|
-
render :json => @cities, :each_serializer => CitySerializer
|
|
642
|
-
end
|
|
643
|
-
|
|
644
|
-
def show
|
|
645
|
-
@city = City.find(params[:id])
|
|
646
|
-
|
|
647
|
-
render :json => @city, :scope => current_admin, :scope_name => :current_admin
|
|
648
|
-
end
|
|
649
|
-
end
|
|
650
|
-
```
|
|
165
|
+
# Contributing
|
|
651
166
|
|
|
652
|
-
|
|
653
|
-
for the current user, the advantage of this approach is that, by setting
|
|
654
|
-
`serialization_scope` to `nil`, the `index` action no longer will need to make
|
|
655
|
-
that query, only the `show` action will.
|
|
167
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md)
|