simple_ams 0.2.5 → 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +28 -0
- data/.rubocop.yml +56 -0
- data/CHANGELOG.md +22 -0
- data/Gemfile +2 -2
- data/README.md +663 -116
- data/Rakefile +3 -3
- data/bin/console +3 -3
- data/lib/simple_ams.rb +34 -33
- data/lib/simple_ams/adapters/ams.rb +26 -32
- data/lib/simple_ams/adapters/jsonapi.rb +46 -65
- data/lib/simple_ams/document.rb +38 -28
- data/lib/simple_ams/document/fields.rb +36 -37
- data/lib/simple_ams/document/forms.rb +7 -9
- data/lib/simple_ams/document/generics.rb +35 -37
- data/lib/simple_ams/document/links.rb +7 -9
- data/lib/simple_ams/document/metas.rb +7 -11
- data/lib/simple_ams/document/primary_id.rb +14 -17
- data/lib/simple_ams/document/relations.rb +99 -109
- data/lib/simple_ams/dsl.rb +73 -71
- data/lib/simple_ams/methy.rb +2 -2
- data/lib/simple_ams/options.rb +268 -266
- data/lib/simple_ams/options/adapter.rb +2 -2
- data/lib/simple_ams/options/concerns/filterable.rb +29 -34
- data/lib/simple_ams/options/concerns/mod.rb +4 -0
- data/lib/simple_ams/options/concerns/name_value_hash.rb +25 -26
- data/lib/simple_ams/options/concerns/tracked_properties.rb +15 -17
- data/lib/simple_ams/options/concerns/value_hash.rb +25 -26
- data/lib/simple_ams/options/fields.rb +1 -1
- data/lib/simple_ams/options/forms.rb +1 -2
- data/lib/simple_ams/options/generics.rb +2 -4
- data/lib/simple_ams/options/includes.rb +1 -1
- data/lib/simple_ams/options/links.rb +1 -1
- data/lib/simple_ams/options/metas.rb +1 -1
- data/lib/simple_ams/options/primary_id.rb +1 -1
- data/lib/simple_ams/options/relations.rb +9 -7
- data/lib/simple_ams/options/type.rb +1 -2
- data/lib/simple_ams/renderer.rb +43 -41
- data/lib/simple_ams/version.rb +1 -1
- data/simple_ams.gemspec +17 -16
- metadata +38 -21
- data/.travis.yml +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d2221682d109248b00a4d4d8a99b8bac469cd76ec60ad4e5710c9b56e43bbf5f
|
4
|
+
data.tar.gz: 0aa48e2d0e09dd36cc8b31ef3daff832059e5d01ea5b090648def84c677de036
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8355af588f6efa58699e0394d3d913c56e16d1573e334930512f971c80a20f285e8200c9d74bd634e07a81de5e79915d7bd694afe6e76161f8b9a592c4a0896
|
7
|
+
data.tar.gz: 38f6bbb2a0493870544866eb04a9501db97232f97b02dd647aeeea83f336e2cf8ae8d7979553aa74ad8cab7d409e06cabec6949b375080d3c96d850f290654fd
|
@@ -0,0 +1,28 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ master ]
|
6
|
+
pull_request:
|
7
|
+
branches: [ master ]
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
test:
|
11
|
+
strategy:
|
12
|
+
fail-fast: false
|
13
|
+
matrix:
|
14
|
+
os: [ ubuntu-latest ]
|
15
|
+
ruby: [ 2.5, 2.6, 2.7, '3.0', truffleruby ]
|
16
|
+
runs-on: ${{ matrix.os }}
|
17
|
+
steps:
|
18
|
+
- uses: actions/checkout@v2
|
19
|
+
- name: Setup Ruby
|
20
|
+
uses: ruby/setup-ruby@v1
|
21
|
+
with:
|
22
|
+
ruby-version: ${{ matrix.ruby }}
|
23
|
+
- name: Install dependencies
|
24
|
+
run: bundle install
|
25
|
+
- name: Rubocop
|
26
|
+
run: bundle exec rubocop
|
27
|
+
- name: Run tests
|
28
|
+
run: bundle exec rspec spec
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
AllCops:
|
2
|
+
NewCops: enable
|
3
|
+
Style/FrozenStringLiteralComment:
|
4
|
+
Enabled: false
|
5
|
+
Style/ClassAndModuleChildren:
|
6
|
+
EnforcedStyle: compact
|
7
|
+
Layout/FirstHashElementIndentation:
|
8
|
+
EnforcedStyle: consistent
|
9
|
+
Metrics/ModuleLength:
|
10
|
+
Max: 250
|
11
|
+
Metrics/ClassLength:
|
12
|
+
Max: 250
|
13
|
+
Metrics/MethodLength:
|
14
|
+
Max: 50
|
15
|
+
Metrics/BlockLength:
|
16
|
+
Max: 50
|
17
|
+
Exclude:
|
18
|
+
- 'spec/**/*.rb'
|
19
|
+
Metrics/AbcSize:
|
20
|
+
Max: 50
|
21
|
+
Lint/UnreachableLoop:
|
22
|
+
Exclude:
|
23
|
+
- 'spec/**/*.rb'
|
24
|
+
Style/Documentation:
|
25
|
+
Enabled: false
|
26
|
+
Lint/UnderscorePrefixedVariableName:
|
27
|
+
Exclude:
|
28
|
+
- 'spec/**/*.rb'
|
29
|
+
- 'lib/simple_ams/options.rb'
|
30
|
+
- 'lib/simple_ams/dsl.rb'
|
31
|
+
Naming/MemoizedInstanceVariableName:
|
32
|
+
Exclude:
|
33
|
+
- 'lib/simple_ams/options.rb'
|
34
|
+
- 'lib/simple_ams/dsl.rb'
|
35
|
+
Lint/SuppressedException:
|
36
|
+
Exclude:
|
37
|
+
- 'spec/**/*.rb'
|
38
|
+
Metrics/PerceivedComplexity:
|
39
|
+
Max: 14
|
40
|
+
Exclude:
|
41
|
+
- 'spec/**/*.rb'
|
42
|
+
Metrics/CyclomaticComplexity:
|
43
|
+
Max: 14
|
44
|
+
Exclude:
|
45
|
+
- 'spec/**/*.rb'
|
46
|
+
Lint/ConstantDefinitionInBlock:
|
47
|
+
Exclude:
|
48
|
+
- 'spec/**/*.rb'
|
49
|
+
Style/OptionalArguments:
|
50
|
+
Exclude:
|
51
|
+
- 'lib/simple_ams/options/relations.rb'
|
52
|
+
Naming/PredicateName:
|
53
|
+
Exclude:
|
54
|
+
- 'lib/simple_ams/dsl.rb'
|
55
|
+
Lint/MissingSuper:
|
56
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
**0.2.6**
|
2
|
+
* [#45](https://github.com/vasilakisfil/SimpleAMS/pull/45) add changelog
|
3
|
+
* [#44](https://github.com/vasilakisfil/SimpleAMS/pull/44) add Ruby 3.0 support
|
4
|
+
* [#42](https://github.com/vasilakisfil/SimpleAMS/pull/42) fix bug when more than 1 nested relations where specified on Renderer
|
5
|
+
* [#37](https://github.com/vasilakisfil/SimpleAMS/pull/37) Use Rubocop to lint
|
6
|
+
* [#34](https://github.com/vasilakisfil/SimpleAMS/pull/34) update README with detailed documentation and examples
|
7
|
+
* [#31](https://github.com/vasilakisfil/SimpleAMS/pull/31) improve serializer inference when collection is not an array
|
8
|
+
|
9
|
+
**v0.2.5**
|
10
|
+
* [#28](https://github.com/vasilakisfil/SimpleAMS/pull/28) fix repo reference in gemspec
|
11
|
+
* [#27](https://github.com/vasilakisfil/SimpleAMS/pull/27) fixes bug when rendering polymorphic collections (properly cleans serializer variable)
|
12
|
+
|
13
|
+
**v0.2.3**
|
14
|
+
* [#24](https://github.com/vasilakisfil/SimpleAMS/pull/24) add travis for CI
|
15
|
+
* fixes on JSON:API adapter (`skip_data` option)
|
16
|
+
* small performance improvements
|
17
|
+
|
18
|
+
**0.2.1**
|
19
|
+
* various bug fixes and performance improvements
|
20
|
+
|
21
|
+
**0.1.5**
|
22
|
+
* first stable release
|
data/Gemfile
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
source
|
1
|
+
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
3
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
4
4
|
|
5
5
|
# Specify your gem's dependencies in SimpleAMS.gemspec
|
6
6
|
gemspec
|
data/README.md
CHANGED
@@ -6,6 +6,43 @@
|
|
6
6
|
If we want to interact with modern APIs we should start building modern, flexible libraries
|
7
7
|
that help developers to build such APIs. Modern Ruby serializers, as I always wanted them to be.
|
8
8
|
|
9
|
+
You can find the core ideas, the reasoning behind the architecture, use cases
|
10
|
+
and examples [here](https://vasilakisfil.social/blog/2020/01/20/modern-ruby-serializers/).
|
11
|
+
|
12
|
+
## Table of contents
|
13
|
+
|
14
|
+
1. [Installation](#installation)
|
15
|
+
2. [Usage](#usage)
|
16
|
+
* [Simple case](#simple-case)
|
17
|
+
- [Rendering a resource](#rendering-a-resource)
|
18
|
+
- [Rendering a collection](#rendering-a-collection)
|
19
|
+
* [Serializer DSL](#serializer-dsl)
|
20
|
+
- [fields directive](#fields-directive)
|
21
|
+
- [Relations (has_many/has_one/belongs_to)](#relations-has_manyhas_onebelongs_to)
|
22
|
+
* [relations are recursive](#relations-are-recursive)
|
23
|
+
* [embedded content (again recursive)](#embedded-content-again-recursive)
|
24
|
+
* [relation name/type](#relation-nametype)
|
25
|
+
- [value-hashmap type of directives](#value-hashmap-type-of-directives)
|
26
|
+
* [adapter](#adapter)
|
27
|
+
* [primary_id](#primary_id)
|
28
|
+
* [type](#type)
|
29
|
+
- [name-value-hashmap type of directives](#name-value-hashmap-type-of-directives)
|
30
|
+
* [link](#link)
|
31
|
+
* [meta](#meta)
|
32
|
+
* [form](#form)
|
33
|
+
* [generic](#generic)
|
34
|
+
* [group of link/meta/form/generic](#group-of-linksmetasformsgenerics)
|
35
|
+
- [collection directive](#collection-directive)
|
36
|
+
* [Rendering DSL](#rendering-dsl)
|
37
|
+
- [includes vs relations](#includes-vs-relations)
|
38
|
+
- [Rendering collections](#rendering-collections)
|
39
|
+
- [Rendering options with values](#rendering-options-with-values)
|
40
|
+
- [Exposing methods inside the serializer, like helpers](#exposing-methods-inside-the-serializer-like-helpers)
|
41
|
+
* [Extended DSL show off](#extended-dsl-show-off)
|
42
|
+
3. [Development](#development)
|
43
|
+
4. [Contributing](#contributing)
|
44
|
+
|
45
|
+
|
9
46
|
## Installation
|
10
47
|
|
11
48
|
Add this line to your application's Gemfile:
|
@@ -23,103 +60,651 @@ Or install it yourself as:
|
|
23
60
|
$ gem install simple_ams
|
24
61
|
|
25
62
|
## Usage
|
26
|
-
The gem's interface has been inspired by ActiveModel Serializers 0.9.2,
|
27
|
-
|
28
|
-
|
63
|
+
The gem's interface has been inspired by ActiveModel Serializers 0.9.2,
|
64
|
+
0.10.stable, jsonapi-rb and Ember Data.
|
65
|
+
However, it has been built for POROs, **has zero dependencies** and does not
|
66
|
+
relate to Rails in any case other than some nostalgia for the (advanced at that
|
67
|
+
time) pre-0.10 ActiveModel Serialiers.
|
29
68
|
|
69
|
+
You can find the core ideas, the reasoning behind the architecture, use cases and examples [here](https://vasilakisfil.social/blog/2020/01/20/modern-ruby-serializers/).
|
30
70
|
|
31
71
|
### Simple case
|
32
|
-
|
33
72
|
You will rarely need all the advanced options. Usually you will have something like that:
|
34
73
|
|
35
74
|
```ruby
|
36
75
|
class UserSerializer
|
37
76
|
include SimpleAMS::DSL
|
38
77
|
|
39
|
-
#specify the adapter
|
40
|
-
adapter SimpleAMS::Adapters::JSONAPI
|
41
|
-
|
42
|
-
#specify available attributes/fields
|
43
|
-
attributes :id, :name, :email, :birth_date
|
44
|
-
|
45
|
-
#specify available relations
|
46
|
-
has_one :profile, serializer: ProfileSerializer
|
47
|
-
#belongs_to is just an alias to has_one
|
48
|
-
belongs_to :organization, serializer: OrganizationSerializer
|
49
|
-
has_many :videos, serializer: VideosSerializer do
|
50
|
-
#rarely used: if you need more options, you can pas a block
|
51
|
-
#which adheres to the same DSL as described here
|
52
|
-
#it goes to an option called `embedded`
|
53
|
-
#essentially these options here should be used for linking current resource
|
54
|
-
#with the relation (useful for JSONAPI for instance)
|
55
|
-
generic :include_data, false
|
56
|
-
end
|
78
|
+
#specify the adapter we want to use
|
79
|
+
adapter SimpleAMS::Adapters::JSONAPI
|
57
80
|
|
58
|
-
#specify
|
59
|
-
|
60
|
-
#links can also take other options, as specified by RFC 8288
|
61
|
-
link :root, '/api/v1/', rel: :user
|
62
|
-
#link values can be dynamic as well through lambdas
|
63
|
-
#lambdas take arguments the object to be serialized and the instantiated serializer
|
64
|
-
link :posts, ->(obj, s) { s.api_v1_user_followers_path(user_id: obj.id) }, rel: :user
|
65
|
-
#if you also need dynamic options, you can return an array from the lambda
|
66
|
-
link :followers, ->(obj, s) { ["/api/v1/users/#{obj.id}/followers/", rel: obj.type] }
|
67
|
-
|
68
|
-
#same with metas: can be static, dynamic and accept arbitrary options
|
69
|
-
meta :environment, ->(obj, s) { Rails.env.to_s }
|
70
|
-
|
71
|
-
#same with form: can be static, dynamic and accept arbitrary options
|
72
|
-
form :create, ->(obj, s) { User::CreateForm.for(obj) }
|
73
|
-
|
74
|
-
#or if you need something quite generic (and probably adapter-related)
|
75
|
-
#again it follows the same patterns as link
|
76
|
-
generic :include_embedded_data, true, {only: :collection}
|
77
|
-
|
78
|
-
#these are properties to the collection resource itself
|
79
|
-
#AND NOT to each resource separately, when applied inside a collection..
|
80
|
-
#It's a rarely used feature but definitely nice to have..
|
81
|
-
collection do
|
82
|
-
#collection accepts exactly the same aforementioned interface
|
83
|
-
#here we use only links and meta
|
84
|
-
link :root, '/api/v1/', rel: :user
|
85
|
-
type :users
|
86
|
-
meta :count, ->(collection, s) { collection.count }
|
87
|
-
end
|
88
|
-
|
89
|
-
#note that most probably the only thing that you will need here is the `type`,
|
90
|
-
#so there is a shortcut if you just need to specify the collection name/type:
|
91
|
-
#collection :users
|
81
|
+
#specify the attributes we want to serialize from the given object
|
82
|
+
attributes :id, :name, :email, :created_at, :role
|
92
83
|
|
93
|
-
#
|
94
|
-
|
95
|
-
|
96
|
-
|
84
|
+
#specify the type of the resource
|
85
|
+
type :user
|
86
|
+
#specify the name of the collection
|
87
|
+
collection :users
|
97
88
|
|
98
|
-
#
|
99
|
-
|
100
|
-
|
101
|
-
|
89
|
+
#specify a relation. Here microposts serves as both a name of the collection
|
90
|
+
#and the name of the method used to retrieve the values of the collection
|
91
|
+
#from the given object
|
92
|
+
has_many :microposts
|
102
93
|
end
|
103
94
|
```
|
104
95
|
|
105
|
-
|
96
|
+
|
97
|
+
#### Rendering a resource
|
98
|
+
Then you can just feed your serializer with data:
|
106
99
|
|
107
100
|
```ruby
|
108
|
-
SimpleAMS::Renderer.new(user
|
101
|
+
SimpleAMS::Renderer.new(user).to_json
|
109
102
|
```
|
110
103
|
`to_json` first calls `as_json`, which creates a ruby Hash and then `to_json` is called
|
111
104
|
on top of that hash.
|
112
105
|
|
106
|
+
If you want to filter the available options (defined by the serializer) when you
|
107
|
+
instantiate the serializer, `Renderer` accepts an options hash. In there you can
|
108
|
+
throw pretty much the same DSL:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
SimpleAMS::Renderer.new(user, {
|
112
|
+
serializer: UserSerializer, fields: [:id, :name, :email], includes: []
|
113
|
+
}).to_json
|
114
|
+
```
|
115
|
+
|
116
|
+
Here we say that we only want 3 specific fields, and no relations at all.
|
117
|
+
|
118
|
+
|
119
|
+
#### Rendering a collection
|
120
|
+
Rendering a collection is pretty similar, meaning that it reuses the same serializer
|
121
|
+
class, and accepts the same runtime options. The only difference is that you need
|
122
|
+
to call a different class.
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
SimpleAMS::Renderer::Collection.new(users, {
|
126
|
+
serializer: UserSerializer, fields: [:id, :email, :name], includes: []
|
127
|
+
}).to_json
|
128
|
+
```
|
129
|
+
|
130
|
+
### Serializer DSL
|
131
|
+
The serializer is a very robust, yet simple, with a hash-based internal
|
132
|
+
representation.
|
133
|
+
|
134
|
+
#### fields directive
|
135
|
+
Fields specify the attributes that the serializer will hold.
|
136
|
+
The values of each attribute is taken by the to-be serialized object,
|
137
|
+
unless the serializer has a method of the same name.
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
fields :id, :name, :email, :created_at, :role
|
141
|
+
```
|
142
|
+
|
143
|
+
|
144
|
+
Using `attributes` is also valid, it’s just an [alias](https://github.com/vasilakisfil/SimpleAMS/blob/master/lib/simple_ams/dsl.rb#L130-L137) after all:
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
attributes :id, :name, :email, :created_at, :role
|
148
|
+
```
|
149
|
+
|
150
|
+
|
151
|
+
Of course, any field can be overridden by defining a method of the same name
|
152
|
+
inside the serializer.
|
153
|
+
In there, you can have access to a method called object which holds the actual
|
154
|
+
resource to be serialized:
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
def name
|
158
|
+
"#{object.first_name} #{object.last_name}"
|
159
|
+
end
|
160
|
+
```
|
161
|
+
|
162
|
+
|
163
|
+
#### Relations (has_many/has_one/belongs_to)
|
164
|
+
These directives allows you to append relations in a resource.
|
165
|
+
`has_one` is just an alias of `belongs_to` since there is no real difference in
|
166
|
+
APIs (although internally and in adapters, SimpleAMS knows if you specified the
|
167
|
+
relation using`belongs_to` or `has_one`, making it future proof in case API specs
|
168
|
+
decide to support each one in a different way).
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
has_many :microposts
|
172
|
+
```
|
173
|
+
|
174
|
+
Again, it can be overridden by defining a method of the same name:
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
def microposts
|
178
|
+
Post.where(user_id: object.id).order(:created_at, :desc).limit(10)
|
179
|
+
end
|
180
|
+
```
|
181
|
+
|
182
|
+
##### relations are recursive
|
183
|
+
The relations directives can take the same options as the rendering.
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
#overriding the serializer
|
187
|
+
has_many :microposts, serializer: CustomPostsSerializer
|
188
|
+
#overriding the serializer and fields that should be included
|
189
|
+
has_many :microposts, serializer: CustomPostsSerializer, fields: [:content]
|
190
|
+
#overriding the serializer, fields and relations that should be included
|
191
|
+
has_many :microposts, serializer: CustomPostsSerializer, fields: [:content],
|
192
|
+
includes: []
|
193
|
+
#overriding the serializer, fields, relations and links
|
194
|
+
has_many :microposts, serializer: CustomPostsSerializer, fields: [:content],
|
195
|
+
includes: [], links: [:self]
|
196
|
+
```
|
197
|
+
|
198
|
+
|
199
|
+
When overriding from the relations directives (or when rendering in general) you
|
200
|
+
are able to override any directive defined in the serializer to acquire a subset
|
201
|
+
**but never a superset**.
|
202
|
+
|
203
|
+
##### embedded content (again recursive)
|
204
|
+
Sometimes, an annoying spec might define parts of a relation in the main body,
|
205
|
+
while parts of the relation somewhere else. For instance, JSON:API does that by
|
206
|
+
having some links in the main body and the rest in the included section.
|
207
|
+
That’s also possible if you pass a block in the relation directive:
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
has_many :microposts, serializer: MicropostsSerializer, fields: [:content] do
|
211
|
+
#these goes to a class named `Embedded`, attached to the relation
|
212
|
+
link :self, ->(obj){ "/api/v1/users/#{obj.id}/relationships/microposts" }
|
213
|
+
link :related, ->(obj){ ["/api/v1/users/1", rel: :user] }
|
214
|
+
end
|
215
|
+
```
|
216
|
+
|
217
|
+
Inside that block, you can pass any parameter the original DSL supports and will
|
218
|
+
be stored in an Embedded class under MicropostsSerializer.
|
219
|
+
Btw SimpleAMS is smart enough (one of the very few cases that acts like that) to
|
220
|
+
figure out that if a lambda returns something that’s not an array, then this must
|
221
|
+
be the value, while options are just empty.
|
222
|
+
|
223
|
+
##### relation name/type
|
224
|
+
Sometimes, we want to detach the relation’s name from the type. In the previous
|
225
|
+
example `microposts` is the relation name (whatever that means), while the type
|
226
|
+
is defined by the `MicropostsSerializer`, unless we override it, which can be
|
227
|
+
done either in the relation serializer itself, or when we use the relation from
|
228
|
+
the parent serializer:
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
has_many :microposts, serializer: MicropostsSerializer, fields: [:content], type: :feed do
|
232
|
+
link :self, ->(obj){ "/api/v1/users/#{obj.id}/relationships/microposts" }
|
233
|
+
link :related, ->(obj){ ["/api/v1/users/1", rel: :user] }
|
234
|
+
end
|
235
|
+
```
|
236
|
+
|
237
|
+
Internally SimpleAMS, differentiates type from name, and usually type is
|
238
|
+
something that’s semantically stronger (like a relation type) than name.
|
239
|
+
You can even inject the name of the relation using the name option:
|
240
|
+
|
241
|
+
```ruby
|
242
|
+
has_many :microposts, serializer: MicropostsSerializer, fields: [:content], type: :feed, name: :posts do
|
243
|
+
link :self, ->(obj){ "/api/v1/users/#{obj.id}/relationships/microposts" }
|
244
|
+
link :related, ->(obj){ ["/api/v1/users/1", rel: :user] }
|
245
|
+
end
|
246
|
+
```
|
247
|
+
|
248
|
+
As I said, the name, which is usually the name of the attribute that includes
|
249
|
+
the relation in the JSON format, doesn’t really have any semantic meaning in
|
250
|
+
most specs. At least I haven’t seen any spec to depend on the root attribute
|
251
|
+
name of the relation. Instead it’s the type that’s important, because type is
|
252
|
+
what the [web linking RFC defines](https://tools.ietf.org/html/rfc8288#section-2).
|
253
|
+
|
254
|
+
#### value-hashmap type of directives
|
255
|
+
These are directives like adapter. They take a value, and optionally a hashmap,
|
256
|
+
which are options to be passed down straight to the adapter, hence they are adapter specific.
|
257
|
+
Such options are `primary_id`, `type` and `adapter`
|
258
|
+
|
259
|
+
For instance, for adapter it could be:
|
260
|
+
```ruby
|
261
|
+
adapter SimpleAMS::Adapters::JSONAPI, root: true
|
262
|
+
```
|
263
|
+
|
264
|
+
Of course, since we are talking about Ruby here, it would be a huge restriction
|
265
|
+
to not allow dynamic value/hashmap combination. Basically any such directive
|
266
|
+
can accept a lambda (generally anything that responds to `call`) and should
|
267
|
+
return an array where the first part is the value and (optionally) the second part is the
|
268
|
+
options. There is an argument that is passed down to the function/lambda, and
|
269
|
+
that’s the actual object. For instance, to support polymorphic resources you
|
270
|
+
can have the type dynamic:
|
271
|
+
|
272
|
+
```ruby
|
273
|
+
type ->(obj, s){ obj.employee? ? [:employee, {}] : [:user, {}]}
|
274
|
+
```
|
275
|
+
|
276
|
+
One of the very few times that SimpleAMS acts smart is inside the lambda, that
|
277
|
+
if you have only a value (not an Array), it will take that as the value, while
|
278
|
+
the options will be taken by the second argument, after the lambda. So the
|
279
|
+
above is equivalent with:
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
type ->(obj, s){ obj.employee? ? :employee : :user}, {}
|
283
|
+
```
|
284
|
+
|
285
|
+
Note: you shouldn't use that in case of adapter, as that's the definition of UB :P
|
286
|
+
|
287
|
+
|
288
|
+
<details>
|
289
|
+
<summary id="adapter">adapter</summary>
|
290
|
+
|
291
|
+
Specifies the adapter to be used. The `adapter` method is the only one that does
|
292
|
+
not support lambda as it's value, as that would be the definition of undefined
|
293
|
+
behavior. If you want to support polymorphic collections, you should use the `type`
|
294
|
+
instead in combination with the `serializer`.
|
295
|
+
|
296
|
+
```ruby
|
297
|
+
#without options
|
298
|
+
adapter SimpleAMS::Adapters::JSONAPI
|
299
|
+
#with adapter-specific options
|
300
|
+
adapter SimpleAMS::Adapters::JSONAPI, {root: false}
|
301
|
+
```
|
302
|
+
|
303
|
+
Note that you can even specify your own adapter. Usually you will want to inherit
|
304
|
+
from an existing adapter (like `SimpleAMS::Adapters::AMS`), but that's not a
|
305
|
+
requirement. All you need is to duck type to 2 methods:
|
306
|
+
* `initialize(document, options = {})` be able to accept 2 arguments when your adapter
|
307
|
+
is instantiated. The first one is a document, while the second one is the adapter-specific
|
308
|
+
options (like the `{root: false}`.
|
309
|
+
* `as_json` method returns that returns the hash representation of the serialized result
|
310
|
+
|
311
|
+
The conversion of a Hash into raw JSON string is out of the scope of this library.
|
312
|
+
But you will probably want to use the fastest implementation possible like [oj](https://github.com/ohler55/oj).
|
313
|
+
</details>
|
314
|
+
|
315
|
+
<details>
|
316
|
+
<summary id="primary_id">primary_id</summary>
|
317
|
+
|
318
|
+
Specifies the `primary_id` to be used. There are many API specs that handle the
|
319
|
+
identifier of a resource in a different way than the rest of the attributes.
|
320
|
+
JSON:API is one of those.
|
321
|
+
|
322
|
+
```ruby
|
323
|
+
#without options
|
324
|
+
primary_id :id
|
325
|
+
#with adapter-specific options
|
326
|
+
adapter :id, {external: true}
|
327
|
+
#dynamic
|
328
|
+
adapter ->(obj, s) { [obj.class.primary_key, {}]}
|
329
|
+
```
|
330
|
+
|
331
|
+
|
332
|
+
</details>
|
333
|
+
|
334
|
+
<details>
|
335
|
+
<summary id="type">type</summary>
|
336
|
+
|
337
|
+
Specifies the `type` to be used. There are many API specs that handle the
|
338
|
+
type of a resource in a different way than the rest of the attributes.
|
339
|
+
JSON:API is one of those.
|
340
|
+
|
341
|
+
```ruby
|
342
|
+
#without options
|
343
|
+
type :user
|
344
|
+
#with adapter-specific options
|
345
|
+
type :user, {polymorphic: false}
|
346
|
+
#dynamic
|
347
|
+
type ->(obj, s){ obj.employee? ? [:employee, {}] : [:user, {}]}
|
348
|
+
```
|
349
|
+
|
350
|
+
</details>
|
351
|
+
|
352
|
+
|
353
|
+
|
354
|
+
#### name-value-hashmap type of directives
|
355
|
+
These are similar to the above, only that they also have an actual value, which
|
356
|
+
is converted to a representation through the adapter.
|
357
|
+
Such options are `link`, `meta`, `form` and the most generic directive `generic`.
|
358
|
+
|
359
|
+
For instance, think about a links. According to RFC [8288](https://tools.ietf.org/html/rfc8288#section-2), a link has
|
360
|
+
|
361
|
+
* a link context,
|
362
|
+
* a link relation type,
|
363
|
+
* a link target, and
|
364
|
+
* optionally, target attributes
|
365
|
+
|
366
|
+
Now, if we wanted to translate that to our serializers, a link could look like:
|
367
|
+
```ruby
|
368
|
+
link :feed, '/api/v1/me/feed', {style: :compact}
|
369
|
+
```
|
370
|
+
|
371
|
+
Here obviously the link context is the serializer itself, the link relation is
|
372
|
+
the feed, and the value is `/api/v1/me/feed`. Now you might say, feed should be
|
373
|
+
the name of the link which is different from the relation type.
|
374
|
+
The relation type could be `microposts`.
|
375
|
+
And actually, that’s the case for [JSONAPI v1.1](https://jsonapi.org/format/1.1/).
|
376
|
+
In that case, the feed should be treated barely as a name (whatever that means)
|
377
|
+
and relation type will be put inside the link options like:
|
378
|
+
|
379
|
+
```ruby
|
380
|
+
link :feed, '/api/v1/me/feed', {rel: :microposts, style: :compact}
|
381
|
+
```
|
382
|
+
|
383
|
+
Note however that this needs to be supported by the adapter you are using.
|
384
|
+
|
385
|
+
Similar to the case of value-hash directives, it is possible to have dynamic
|
386
|
+
value and options:
|
387
|
+
|
388
|
+
```ruby
|
389
|
+
#values can be dynamic through lambdas
|
390
|
+
#lambdas take arguments the object to be serialized and the instantiated serializer
|
391
|
+
link :feed, ->(obj, s) { [s.api_v1_user_feed_path(user_id: obj.id), {rel: :feed} }
|
392
|
+
#if the value inside the lambda is single (no array), the options will be taken from
|
393
|
+
#the second argument, after the lambda. So the above is equivelent to:
|
394
|
+
link :feed, ->(obj, s) { s.api_v1_user_feed_path(user_id: obj.id) }, rel: :feed
|
395
|
+
```
|
396
|
+
|
397
|
+
<details>
|
398
|
+
<summary id="link">link</summary>
|
399
|
+
|
400
|
+
Specifies a link to be used. There are many API specs that handle the links of a
|
401
|
+
resource in a special way (JSON:API is one of those).
|
402
|
+
You can specify multiple links, as long as each link name is unique.
|
403
|
+
|
404
|
+
```ruby
|
405
|
+
#specifying a link with without options
|
406
|
+
link :feed, "/api/v1/feed"
|
407
|
+
#specifying a link with options
|
408
|
+
link :feed, "/api/v1/feed", {rel: :feed, compact: true}
|
409
|
+
#values can be dynamic through lambdas
|
410
|
+
#lambdas take arguments the object to be serialized and the instantiated serializer
|
411
|
+
link :feed, ->(obj, s) { [s.api_v1_user_feed_path(user_id: obj.id), {rel: :feed, compact: true}] }
|
412
|
+
#if the value inside the lambda is single (no array), the options will be taken from
|
413
|
+
#the second argument, after the lambda. So the above is equivelent to:
|
414
|
+
link :feed, ->(obj, s) { s.api_v1_user_feed_path(user_id: obj.id) }, rel: :feed, compact: true
|
415
|
+
```
|
416
|
+
|
417
|
+
</details>
|
418
|
+
|
419
|
+
<details>
|
420
|
+
<summary id="meta">meta</summary>
|
421
|
+
|
422
|
+
Specifies a meta to be used. There are many API specs that handle the metas of a
|
423
|
+
resource in a special way (JSON:API is one of those).
|
424
|
+
You can specify multiple metas, as long as each link name is unique.
|
425
|
+
|
426
|
+
```ruby
|
427
|
+
#specifying a meta with without options
|
428
|
+
meta :total_count, 1
|
429
|
+
#specifying a meta with options
|
430
|
+
meta :total_count, 1, {compact: true}
|
431
|
+
#values can be dynamic through lambdas
|
432
|
+
#lambdas take arguments the object to be serialized and the instantiated serializer
|
433
|
+
#in this case an object is apparently a collection/array
|
434
|
+
meta :total_count, ->(obj, s) { [obj.count, {compact: true}] }
|
435
|
+
#if the value inside the lambda is single (no array), the options will be taken from
|
436
|
+
#the second argument, after the lambda. So the above is equivelent to:
|
437
|
+
meta :total_count, ->(obj, s) { obj.count }, {compact: true}
|
438
|
+
```
|
113
439
|
|
114
|
-
|
115
|
-
The DSL in the previous example is just syntactic sugar. In the basis, there is a very powerful
|
116
|
-
hash-based DSL that can be used in 3 different places:
|
440
|
+
</details>
|
117
441
|
|
118
|
-
|
119
|
-
|
120
|
-
* Through the DSL, powered with some syntactic sugar
|
442
|
+
<details>
|
443
|
+
<summary id="form">form</summary>
|
121
444
|
|
122
|
-
|
445
|
+
Specifies a form to be used. Unfortunately, there are very few API specs that
|
446
|
+
handle forms (the [Ion hypermedia type](https://ionspec.org) is one of those).
|
447
|
+
You can specify multiple forms, as long as each link name is unique.
|
448
|
+
|
449
|
+
```ruby
|
450
|
+
#specifying a form with without options
|
451
|
+
form :upload, {method: :get, url: "/api/v1/submit"}
|
452
|
+
#specifying a form with options
|
453
|
+
form :upload, {method: :get, url: "/api/v1/submit"}, compact: true
|
454
|
+
#values can be dynamic through lambdas
|
455
|
+
#lambdas take arguments the object to be serialized and the instantiated serializer
|
456
|
+
form :upload, ->(obj, s) { [obj.class.upload_form_options, {compact: true}] }
|
457
|
+
#if the value inside the lambda is single (no array), the options will be taken from
|
458
|
+
#the second argument, after the lambda. So the above is equivelent to:
|
459
|
+
form :upload, ->(obj, s) { obj.class.upload_form_options }, {compact: true}
|
460
|
+
```
|
461
|
+
|
462
|
+
</details>
|
463
|
+
|
464
|
+
<details>
|
465
|
+
<summary id="generic">generic</summary>
|
466
|
+
|
467
|
+
Specifies a generic to be used. A generic is just a placeholder for extensions
|
468
|
+
that are unknown to SimpleAMS (but maybe they make a lot of sense to you ^_^)
|
469
|
+
|
470
|
+
```ruby
|
471
|
+
#specifying a generic with without options
|
472
|
+
generic :pagination, :extended
|
473
|
+
#specifying a form with options
|
474
|
+
generic :pagination, :extended, compact: false
|
475
|
+
#values can be dynamic through lambdas
|
476
|
+
#lambdas take arguments the object to be serialized and the instantiated serializer
|
477
|
+
generic :pagination, ->(obj, s) { [obj.class.pagination_type, {compact: false}] }
|
478
|
+
#if the value inside the lambda is single (no array), the options will be taken from
|
479
|
+
#the second argument, after the lambda. So the above is equivelent to:
|
480
|
+
generic :pagination, ->(obj, s) { obj.class.pagination_type }, {compact: false}
|
481
|
+
```
|
482
|
+
|
483
|
+
</details>
|
484
|
+
|
485
|
+
##### group of links/metas/forms/generics
|
486
|
+
|
487
|
+
Each of the aforementioned options comes with a plural form as well.
|
488
|
+
For instance, if we want to specify multiple `links` at the same time:
|
489
|
+
|
490
|
+
```ruby
|
491
|
+
links {
|
492
|
+
self: ['/api/v1/me', {rel: :user}]
|
493
|
+
feed: ['/api/v1/me/feed', {rel: :feed}]
|
494
|
+
}
|
495
|
+
```
|
496
|
+
|
497
|
+
or if we want to specify multiple `metas`:
|
498
|
+
```ruby
|
499
|
+
metas {
|
500
|
+
total_count: ->(obj){ obj.count }
|
501
|
+
pages: ->(obj){ obj.pages_count }
|
502
|
+
}
|
503
|
+
```
|
504
|
+
|
505
|
+
Same goes for `forms` and `generics`.
|
506
|
+
|
507
|
+
#### collection directive
|
508
|
+
SimpleAMS has a unique ability to allow you specify different options when you are
|
509
|
+
rendering a collection. In its most simple use case it specified the plural name
|
510
|
+
of the resource, used when rendering a collection:
|
511
|
+
|
512
|
+
```ruby
|
513
|
+
collection :users
|
514
|
+
```
|
515
|
+
|
516
|
+
It’s needed, if your adapter serializes the collection using a root element.
|
517
|
+
But it can do much more than that: it allows you to define directives on the
|
518
|
+
collection level. For instance, if you want to have a link that should be
|
519
|
+
applied **only** to the collection level and not to each resource of the collection,
|
520
|
+
then you need to define it inside the collection’s block:
|
521
|
+
|
522
|
+
```ruby
|
523
|
+
collection :users do
|
524
|
+
link :self, "/api/v1/users"
|
525
|
+
end
|
526
|
+
```
|
527
|
+
|
528
|
+
Or if we also want to have the total count of the collection, that should go in
|
529
|
+
there actually:
|
530
|
+
|
531
|
+
```ruby
|
532
|
+
collection :users do
|
533
|
+
link :self, "/api/v1/users"
|
534
|
+
meta :count, ->(collection, s) { collection.count }
|
535
|
+
end
|
536
|
+
```
|
537
|
+
|
538
|
+
Again, inside that block you can define using the regular DSL, whatever you
|
539
|
+
would define in the resource level. It’s just yet another level of recursion
|
540
|
+
since, the same things that I show you here can be applied in the collection
|
541
|
+
level inside the block. For instance, in theory (and if the adapter supports
|
542
|
+
it), you can specify relations that apply only to the collection level:
|
543
|
+
|
544
|
+
```ruby
|
545
|
+
class UserSerializer
|
546
|
+
include SimpleAMS::DSL
|
547
|
+
|
548
|
+
adapter SimpleAMS::Adapters::JSONAPI
|
549
|
+
|
550
|
+
attributes :id, :name, :email, :created_at, :role
|
551
|
+
|
552
|
+
type :user
|
553
|
+
collection :users do
|
554
|
+
link :self, "/api/v1/users"
|
555
|
+
meta :count, ->(collection, s) { collection.count }
|
556
|
+
|
557
|
+
has_one :s3_uploader #whatever that means :P
|
558
|
+
end
|
559
|
+
|
560
|
+
has_many :microposts
|
561
|
+
end
|
562
|
+
```
|
563
|
+
|
564
|
+
### Rendering DSL
|
565
|
+
When rendering a resource, it should be straightforward:
|
566
|
+
|
567
|
+
```ruby
|
568
|
+
SimpleAMS::Renderer.new(user, { serializer: UserSerializer }).to_json
|
569
|
+
```
|
570
|
+
|
571
|
+
All you need is to specify a serializer. In the example above, the resulted
|
572
|
+
resource is a reflection of what is defined inside the serializer.
|
573
|
+
However, the serializer acts as a filtering mechanism, meaning that you can
|
574
|
+
override anything the serializer defines, given that the result creates a
|
575
|
+
**subset and not a superset** (any superset options will be ignored).
|
576
|
+
|
577
|
+
For instance, you can override the type during rendering:
|
578
|
+
|
579
|
+
```ruby
|
580
|
+
SimpleAMS::Renderer.new(user, {
|
581
|
+
serializer: UserSerializer, type: :person
|
582
|
+
}).to_json
|
583
|
+
```
|
584
|
+
or you can override the relations, and specify that you don’t want to include
|
585
|
+
any relation defined in the serializer:
|
586
|
+
|
587
|
+
```ruby
|
588
|
+
SimpleAMS::Renderer.new(user, {
|
589
|
+
serializer: UserSerializer, includes: []
|
590
|
+
}).to_json
|
591
|
+
```
|
592
|
+
|
593
|
+
or specify exactly what fields you want:
|
594
|
+
|
595
|
+
```ruby
|
596
|
+
SimpleAMS::Renderer.new(user, {
|
597
|
+
serializer: UserSerializer, fields: [:id, :email, :name, :created_at]
|
598
|
+
}).to_json
|
599
|
+
```
|
600
|
+
|
601
|
+
or even specify the links subset that you want:
|
602
|
+
|
603
|
+
```ruby
|
604
|
+
SimpleAMS::Renderer.new(user, {
|
605
|
+
serializer: UserSerializer, fields: [:id, :email, :name, :created_at],
|
606
|
+
links: [:self, :comments, :posts]
|
607
|
+
}).to_json
|
608
|
+
```
|
609
|
+
|
610
|
+
and the list goes on.. basically the rendering DSL is identical
|
611
|
+
|
612
|
+
#### includes vs relations
|
613
|
+
There might be some confusion between `includes` and `relations`, so to clear things up:
|
614
|
+
* `includes`: specifies which relations you want to include, out of the available relations.
|
615
|
+
* `relations`: specifies the available relations, so it's not just an array of
|
616
|
+
symbols, but rather full relation objects which are generated through the dsl.
|
617
|
+
The raw representation of relations is an array of objects where each object
|
618
|
+
is `[relation_type, name, options, embedded_options]`. Here
|
619
|
+
`relation_type` is the type of the relation (`has_many`, `belongs_to` etc),
|
620
|
+
`name` is the name of the relation (like users), `options`, any relation options,
|
621
|
+
and `embedded_options` relevant to embedded options.
|
622
|
+
|
623
|
+
So when rendering, if you don't want any relations at all, the correct way is to
|
624
|
+
specify `includes: []`. In practice you can use `relations: []` as well, but that
|
625
|
+
will mean that the serializer has no relations at all (takes precedence over
|
626
|
+
`includes`). But that's not the correct way to do it. For instance, thing about
|
627
|
+
another scenario: you want to specify only one relation, the `feed` relation.
|
628
|
+
With `includes` you would have `includes: [:feed]`. With relations, you would
|
629
|
+
have to specify the relation at runtime (
|
630
|
+
`relations: [[:has_one, :feed, {serializer: FeedSerializer, fields: [:id, :content]}, {}]]`
|
631
|
+
) and then also specify that you only want that: `includes: [:feed]`.
|
632
|
+
|
633
|
+
In general, there is no reason why you should use `relations` at rendering time,
|
634
|
+
instead you should leave that to the serializer, and only specify the `includes`.
|
635
|
+
|
636
|
+
Btw you might have noticed `includes` only as a rendering option, but SimpleAMS
|
637
|
+
DSL is used all over the place, and actually it's a serializer option as well
|
638
|
+
(just that it's not very useful ^_^).
|
639
|
+
|
640
|
+
|
641
|
+
#### Rendering collections
|
642
|
+
Rendering a collection is similar, only that you need to call
|
643
|
+
`SimpleAMS::Renderer::Collection` instead of just `SimpleAMS::Renderer`:
|
644
|
+
|
645
|
+
```ruby
|
646
|
+
SimpleAMS::Renderer::Collection.new(users, {
|
647
|
+
serializer: UserSerializer, fields: [:id, :email, :name, :created_at],
|
648
|
+
links: [:self, :comments, :posts]
|
649
|
+
}).to_json
|
650
|
+
```
|
651
|
+
|
652
|
+
Note that even with collection, by default everything goes to the resource.
|
653
|
+
If you need to specify options for the collection itself, you need to use the
|
654
|
+
collection key. For instance, having some metas inside the collection:
|
655
|
+
|
656
|
+
```ruby
|
657
|
+
SimpleAMS::Renderer::Collection.new(users, {
|
658
|
+
serializer: UserSerializer, fields: [:id, :email, :name, :created_at],
|
659
|
+
links: [:self, :comments, :posts],
|
660
|
+
collection: {
|
661
|
+
metas: [:total_count]
|
662
|
+
}
|
663
|
+
}).to_json
|
664
|
+
```
|
665
|
+
|
666
|
+
#### Rendering options with values
|
667
|
+
If you want to specify the actual values when rendering the resource, rather
|
668
|
+
than taking into account the serializer, you can inject a hashmap:
|
669
|
+
|
670
|
+
```ruby
|
671
|
+
SimpleAMS::Renderer::Collection.new(users, {
|
672
|
+
serializer: UserSerializer, fields: [:id, :email, :name, :created_at],
|
673
|
+
links: [:self, :comments, :posts],
|
674
|
+
collection: {
|
675
|
+
metas: {
|
676
|
+
total_count: users.count,
|
677
|
+
}
|
678
|
+
}).to_json
|
679
|
+
```
|
680
|
+
|
681
|
+
Of course, you can also pass a lambda there, but not sure what’s the point since
|
682
|
+
the lambda parameter is the resource that you already try to render so it’s not
|
683
|
+
going to give you anything more (and will be slower actually).
|
684
|
+
|
685
|
+
#### Exposing methods inside the serializer, like helpers
|
686
|
+
When rendering you can expose a couple of objects in the serializer:
|
687
|
+
|
688
|
+
```ruby
|
689
|
+
SimpleAMS::Renderer::Collection.new(users, {
|
690
|
+
serializer: UserSerializer, fields: [:id, :email, :name, :created_at],
|
691
|
+
#exposing helpers that will be available inside the serializer
|
692
|
+
expose: {
|
693
|
+
#a class
|
694
|
+
current_user: User.first
|
695
|
+
#or a module
|
696
|
+
helpers: CommonHelpers
|
697
|
+
},
|
698
|
+
}).to_json
|
699
|
+
```
|
700
|
+
|
701
|
+
The expose attribute is also available through DSL, although usually that’s not
|
702
|
+
very useful. Just wanted to mentions that there is actually parity on everything,
|
703
|
+
since everything has been built on the same building blocks :)
|
704
|
+
|
705
|
+
### Extended DSL show off
|
706
|
+
Here is an extended example of the DSL. It's not a real use case of course, but
|
707
|
+
shows what's possible with SimpleAMS and its powerful DSL.
|
123
708
|
|
124
709
|
```ruby
|
125
710
|
{
|
@@ -133,13 +718,14 @@ In any case, we have the following options:
|
|
133
718
|
fields: [:id, :name, posts: [:id, :text], videos: [:id, :title, comments: [:id, :text]]] #overrides includes when association is specified
|
134
719
|
relations: [
|
135
720
|
[:belongs_to, :company, {
|
136
|
-
|
137
|
-
|
721
|
+
serializer: CompanySerializer,
|
722
|
+
fields: Company.column_names.map(&:to_sym)
|
138
723
|
}
|
139
724
|
],
|
140
725
|
[:has_many, :followers, {
|
141
|
-
|
142
|
-
|
726
|
+
serializer: UserSerializer,
|
727
|
+
fields: User.column_names.map(&:to_sym)
|
728
|
+
}
|
143
729
|
],
|
144
730
|
]
|
145
731
|
#the serializer that should be used
|
@@ -199,45 +785,6 @@ In any case, we have the following options:
|
|
199
785
|
}
|
200
786
|
```
|
201
787
|
|
202
|
-
Now let those options be `OPTIONS`. These can be fed to either the `SimpleAMS::Renderer`
|
203
|
-
or to the serializer class itself using the `with_options` class method. Let's see how:
|
204
|
-
|
205
|
-
```ruby
|
206
|
-
class UserSerializer
|
207
|
-
include SimpleAMS::DSL
|
208
|
-
|
209
|
-
with_options({ #you can pass the same options as above ;)
|
210
|
-
primary_id: :id,
|
211
|
-
# ...
|
212
|
-
# ...
|
213
|
-
# ...
|
214
|
-
})
|
215
|
-
|
216
|
-
def name
|
217
|
-
"#{object.first_name} #{object.last_name}"
|
218
|
-
end
|
219
|
-
|
220
|
-
def videos
|
221
|
-
Videos.where(user_id: object.id).published
|
222
|
-
end
|
223
|
-
end
|
224
|
-
```
|
225
|
-
|
226
|
-
The same options can be passed when calling the `Renderer`. `Renderer` can override
|
227
|
-
some properties, however in all properties that act as sets/arrays (like
|
228
|
-
attributes/fields, includes, links etc.), **specified serializer options take precedence** over
|
229
|
-
`Renderer` options.
|
230
|
-
|
231
|
-
```ruby
|
232
|
-
SimpleAMS::Renderer.new(user, {
|
233
|
-
primary_id: :id,
|
234
|
-
serializer: UserSerializer,
|
235
|
-
# ...
|
236
|
-
# ...
|
237
|
-
# ...
|
238
|
-
}).to_json
|
239
|
-
```
|
240
|
-
|
241
788
|
## Development
|
242
789
|
|
243
790
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|