jsonapi_serializer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 21d607d922e598bf9866f8b829def185ff0f1f72
4
+ data.tar.gz: 5a80e053e723f7180018d582d2871426f713723f
5
+ SHA512:
6
+ metadata.gz: 1c727a0615f60c522a665e6725e91b8a5770d8c959cf60f4ebb176da8db2d5322e2bb8faeb6eb239e106e3a9d71a4b7bcfcae2f4163b5c72c7274dab0cdd0820
7
+ data.tar.gz: 88855eeaf4da64bd790379072c66881512ca675c5d0137600314200df71795ce94cbb58072a83363407557ce533ab416964ceea647855ef3207e89813c66b805
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.13.6
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at ivan.youroff@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in jsonapi_serializer.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Ivan Youroff
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,278 @@
1
+ # Alt JSON API
2
+ [![Build Status](https://travis-ci.org/youroff/jsonapi_serializer.svg?branch=master)](https://travis-ci.org/youroff/jsonapi_serializer)
3
+
4
+ JSONApi serializer for Ruby objects. Inspired by [Fast JSON API](https://github.com/Netflix/fast_jsonapi).
5
+
6
+ ### Features
7
+
8
+ * Flexible mapping of attributes
9
+ * Custom computed attributes
10
+ * Custom ID-mapping
11
+ * Manual type setting
12
+ * Key and type transforms
13
+ * Polymorphic associations
14
+ * Sparse fieldsets support
15
+ * Nested includes (arbitrary depth)
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ ```ruby
22
+ gem 'oj'
23
+ gem 'jsonapi_serializer'
24
+ ```
25
+
26
+ And then execute:
27
+
28
+ ```bash
29
+ $ bundle
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ ### Record identification
35
+
36
+ Records in JSONApi are identified by `type` and `id`. Normally these attributes can be derived implicitly from serializer name and id by default is just `object.id`. But you can redefine this behavior by using following DSL-methods:
37
+
38
+ ```ruby
39
+ class MovieSerializer
40
+ include JsonapiSerializer::Base
41
+ # Without type hook, type of the record will be derived
42
+ # from the serializer name. In this case it would be :movie
43
+ type :film
44
+
45
+ # Using id hook, you can define how id would be retrieved.
46
+ # By default it will be taken from `id` attribute of the model.
47
+ id { |object| object.slug }
48
+
49
+ # You can also pass a symbol of attribute of the model that represents id
50
+ id :slug
51
+ end
52
+ ```
53
+
54
+ You can use public method `MovieSerializer.new.id_hash(record)` to get identification object (for example): `{id: 'matrix', type: :film}`
55
+
56
+ ### Types and keys transforms
57
+
58
+ JSON API spec does not define how exactly `keys` and `types` should be formatted. For example, if you have a model defined in `BlockbusterMovie`, it can be represented by `blockbuster_movie`, `blockbuster-movie` or `blockbusterMovie`, or anything else that makes sense for your application. `JsonapiSerializer` allows for customization of this behavior, but only globally so far. If you use Ruby on Rails, you can put these settings in `config/initializers/jsonapi_serializer.rb`:
59
+
60
+ ```ruby
61
+ # You can pass a symbol matching on of predefined transforms (:underscore, :dasherize or :camelize)
62
+ # or implement your own logic, using block.
63
+ # The following example will convert all attribute keys into dasherized-form:
64
+ JsonapiSerializer.set_key_transform :dasherize
65
+
66
+ # This will drop all non alphabet characters and upcase everything:
67
+ JsonapiSerializer.set_key_transform do |str|
68
+ str.to_s.upcase.gsub(/[^A-Z]/, "").to_sym
69
+ end
70
+
71
+ # The same applies to type transform:
72
+ JsonapiSerializer.set_type_transform :camelize
73
+
74
+ # There is also still unresolved debate on how to treat namespaces in json-api.
75
+ # For example, you have `Library::AuthorSerializer` and corresponding model.
76
+ # Most of serializers would just drop namespace while trying to retrieve the type.
77
+ # We can either drop it too, or replace `::` with defined separator:
78
+ JsonapiSerializer.set_type_namespace_separator "-_-"
79
+
80
+ # Or if you want to ignore (drop) it:
81
+ JsonapiSerializer.set_type_namespace_separator :ignore
82
+
83
+ # The default option is "_"
84
+ # Bear in mind that only " " (space), "_" and "-" or any combination of these
85
+ # are allowed as per json-api spec and attempt to set anything else
86
+ # will cause an error.
87
+ ```
88
+
89
+ ### Attributes configuration
90
+
91
+ Alt JSON API supports direct mapping of attributes, as well as remapping and custom attributes that are generated by lambda.
92
+
93
+ ```ruby
94
+ class MovieSerializer
95
+ include JsonapiSerializer::Base
96
+ # attributes accepts names of attributes
97
+ # and/or pairs (hash) of attributes of serialized model
98
+ # pointing to attributes of target model
99
+ attributes :name, release_year: :year
100
+
101
+ # attribute accepts a block with serializable record as parameter
102
+ attribute :rating do |movie|
103
+ "%.2g" % (movie.rating / 10.0)
104
+ end
105
+
106
+ # you can call class methods of serializer to split complex calculations
107
+ attribute :comlex_attribute do |movie|
108
+ do_heavy_calc(movie)
109
+ end
110
+
111
+ def self.do_heavy_calc(movie)
112
+ #...
113
+ end
114
+ end
115
+ ```
116
+
117
+ In this example, serializer will access `record.name` and `record.year` to fill attributes `name` and `release_year` respectively. Rating block will convert 95 into 9.5 and make it string.
118
+
119
+ ### Relationships configuration
120
+
121
+ In order to define relationships, you can use `belongs_to` and `has_many` DSL methods. They accept options `serializer` and `from`. By default, serializer will try to guess relationship serializer by the name of relation. In case of `:director` relation, it would try to use `DirectorSerializer` and crash since it's not defined. Use `serializer` parameter to set serializer explicitly. Option `from` is used to point at the attribute of the model that actually returns the relation object(s) if it's different.
122
+
123
+ ```ruby
124
+ class MovieSerializer
125
+ include JsonapiSerializer::Base
126
+ belongs_to :director, serializer: PersonSerializer
127
+ has_many :actors, from: :cast
128
+ end
129
+ ```
130
+
131
+ From the perspective of serializer, there is no distinction between `belongs_to` and `has_one` relations. Current implementation does not use ActiveRecord's specifics, such as `object.{relation}_id` or `object.{relation}_ids` to access relation ids, which means you will have to preload these relations to avoid DB-calls during serialization. This is done deliberately for two reasons:
132
+
133
+ * Identifier of serialized object (`id`) can be remapped to another attribute, for example `slug`.
134
+ * This library is intended to be ORM-agnostic, you can easily use it to serialize some graph structures.
135
+
136
+ ### Polymorphic models and relationships
137
+
138
+ There are two kinds of polymorphism that `jsonapi_serializer` supports. First is polymorphic model (STI models in ActiveRecord), where most attributes are shared, but children have different types. Ultimately it is still one kind of entity: think of `Vehicle` base class inherited by `Car`, `Truck` and `Motorcycle`. Second kind is polymorphic relationship, where one relationship can contain entirely different models. Let's say you have `Post` and `Product`, and both can have comments, hence from the perspective of individual comment it belongs to `Commentable`. Even though `Post` and `Model` can share some attributes, their serializers will be used mostly along from comments.
139
+
140
+ These types of serializers share most of the implementation implemented similarly, they share most of the logic and both need a `resolver`, which is implicitly defined as a lambda, that applies `JsonapiSerializer.type_transform` to the record class name.
141
+
142
+ #### Polymorphic Models
143
+
144
+ To create a serializer for STI models:
145
+
146
+ ```ruby
147
+ class VehicleSerializer
148
+ include JsonapiSerializer::Polymorphic
149
+ attributes :name, :num_of_wheels
150
+ end
151
+
152
+ class CarSerializer < VehicleSerializer
153
+ attributes :trunk_volume
154
+ end
155
+
156
+ class TruckSerializer < VehicleSerializer
157
+ attributes :bed_size
158
+ end
159
+
160
+ class MotorcycleSerializer < VehicleSerializer
161
+ end
162
+ ```
163
+
164
+ In this case common attributes will be inherited from `VehicleSerializer` and children will be registered automatically. Optionally you can add a `resolver` to the parent:
165
+
166
+ ```ruby
167
+ resolver do |model|
168
+ case model
169
+ when Motorcycle then :motorcycle
170
+ when Truck then :truck
171
+ when Car then :car
172
+ end
173
+ end
174
+ ```
175
+
176
+ But usually you don't need to, the implicit resolver does the same for you. To specify the rules of type transform and how the namespace is treated, read `Types and keys transforms` section.
177
+
178
+ #### Polymorphic Relationships
179
+
180
+ With polymorphic relationships we usually have several independent serializers for models that can appear in one relationships. In order to teach polymorphic serializer to use them, we just need to register these classes using `polymorphic_for`.
181
+
182
+ ```ruby
183
+ class CommentableSerializer
184
+ include JsonapiSerializer::Polymorphic
185
+ # Here we register standalone serializers
186
+ # as targets for our polymorphic relationship.
187
+ # Be aware that in this case attributes will have no effect.
188
+ polymorphic_for PostSerializer, ProductSerializer
189
+
190
+ # You can set up a resolver here as well!
191
+ end
192
+
193
+ class PostSerializer
194
+ include JsonapiSerializer::Base
195
+ attributes :title, :body
196
+ end
197
+
198
+ class ProductSerializer
199
+ include JsonapiSerializer::Base
200
+ attributes :sku, :name, :description
201
+ end
202
+ ```
203
+
204
+ Then use it exactly the same as regular serializers. But keep in mind that you cannot randomly inherit serializer classes, an attempt to inherit regular serializer's class will cause an error.
205
+
206
+ ### Initialization and serialization
207
+
208
+ Once serializers are defined, you can instantiate them with several options. Currently supported options are: `fields` and `include`.
209
+
210
+ `fields` must be a hash, where keys represent record types and values are list of attributes and relationships of the corresponding type that will be present in serialized object. If some type is missing, that means all attributes and relationships defined in serializer will be serialized. In case of `polymorphic` serializer, you can supply shared fields under polymorphic type. **_There is a caveat, though: if you define a fieldset for a parent polymorphic class and omit fieldsets for subclasses it will be considered that you did not want any of attributes and relationships defined in subclass to be serialized._** It works the same fashion for polymorphic relationships, so if you want only `title` from `Post` and `name` from `Product`, you can supply `{commentable: ["title", "name"]}` as a `fields` parameter for `CommentableSerializer`.
211
+
212
+ `include` defines an arbitrary depth tree of included relationships in a similar way as ActiveRecord's `includes`. Bear in mind that `fields` has precedence, which means that if some relationship is missing in fields, it will not be included either.
213
+
214
+ ```ruby
215
+ options = {}
216
+
217
+ # We're omitting fieldset for ItemSerializer here,
218
+ # only attributes/relationships defined in CommentableSerializer
219
+ # will be serialized for Item objects
220
+ options[:fields] = {
221
+ commentable: [:title],
222
+ post: [:body]
223
+ }
224
+
225
+ # You can define arbitrary nesting here
226
+ options[:include] = [:tags, author: :some_authors_relation]
227
+
228
+ serializer = CommentableSerializer.new(options)
229
+ ```
230
+
231
+ Then you can just reuse serializer's instance to serialize appropriate datasets, while supplying optional parameters, such as meta object.
232
+
233
+ ```ruby
234
+ serializer.serialazable_hash(movies, meta: meta)
235
+ # or
236
+ serializer.serialized_json(movies, meta: meta)
237
+ ```
238
+
239
+ ## Performance
240
+
241
+ By running `bin/benchmark` you can launch performance test locally, however numbers are fluctuating widely. The example output is as follows:
242
+
243
+ ### Base case
244
+
245
+ | Adapters | 10 hash/json (ms) | 100 hash/json (ms) | 1000 hash/json (ms) | 10000 hash/json (ms) |
246
+ | -------------------- |:--------------------:|:--------------------:|:--------------------:|:--------------------:|
247
+ | JsonapiSerializerTest | 0.36 / 1.17 | 1.55 / 1.98 | 13.74 / 20.18 | 156.31 / 208.86 |
248
+ | FastJsonapiTest | 0.16 / 0.19 | 1.14 / 1.75 | 11.86 / 18.13 | 124.13 / 176.97 |
249
+
250
+ ### With includes
251
+
252
+ | Adapters | 10 hash/json (ms) | 100 hash/json (ms) | 1000 hash/json (ms) | 10000 hash/json (ms) |
253
+ | -------------------- |:--------------------:|:--------------------:|:--------------------:|:--------------------:|
254
+ | JsonapiSerializerTest | 0.51 / 0.46 | 2.05 / 2.50 | 15.28 / 21.59 | 159.89 / 214.49 |
255
+ | FastJsonapiTest | 0.26 / 0.25 | 2.01 / 2.47 | 15.54 / 20.11 | 154.82 / 211.48 |
256
+
257
+ Performance tests do not include any advanced features, such as fieldsets, nested includes or polymorphic serializers, and were mostly intended to make sure that adding these features did not make serializer slower (or at least significantly slower), but there are models prepared to extend these tests. PRs are welcome.
258
+
259
+ ## Roadmap
260
+
261
+ * Removing as many dependencies as possible. Opt-in JSON-library. Possibly removing dependency on `active_support`.
262
+ * Creating jsonapi_serializer_rails to make rails integration simple
263
+ * ...
264
+
265
+ ## Development
266
+
267
+ 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.
268
+
269
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
270
+
271
+ ## Contributing
272
+
273
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/jsonapi_serializer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
274
+
275
+
276
+ ## License
277
+
278
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/benchmark ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "jsonapi_serializer"
5
+ require "./perf/runner"
6
+ require "./perf/jsonapi_serializer_test"
7
+ require "./perf/fast_jsonapi_test"
8
+ GC.disable
9
+
10
+ runner = Runner.new(10_000)
11
+ runner.set_modules(JsonapiSerializerTest, FastJsonapiTest)
12
+ runner.set_tests(:base, :with_included)
13
+ runner.set_takes(10, 100, 1000, 10_000)
14
+ runner.run
15
+
16
+ print "\n"
17
+ print "### Base case\n"
18
+ print "\n"
19
+ runner.print_table(:base)
20
+ print "\n"
21
+ print "### With includes\n"
22
+ print "\n"
23
+ runner.print_table(:with_included)
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "jsonapi_serializer"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start