ivy-serializers 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/.travis.yml +14 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +155 -0
- data/Rakefile +7 -0
- data/ivy-serializers.gemspec +24 -0
- data/lib/ivy-serializers.rb +1 -0
- data/lib/ivy/serializers.rb +5 -0
- data/lib/ivy/serializers/action_controller/serialization_support.rb +35 -0
- data/lib/ivy/serializers/attribute.rb +24 -0
- data/lib/ivy/serializers/documents.rb +17 -0
- data/lib/ivy/serializers/documents/document.rb +55 -0
- data/lib/ivy/serializers/documents/individual_resource.rb +18 -0
- data/lib/ivy/serializers/documents/resource_collection.rb +18 -0
- data/lib/ivy/serializers/formats.rb +3 -0
- data/lib/ivy/serializers/formats/active_model_serializers.rb +40 -0
- data/lib/ivy/serializers/formats/json.rb +96 -0
- data/lib/ivy/serializers/formats/json_api.rb +66 -0
- data/lib/ivy/serializers/mapping.rb +40 -0
- data/lib/ivy/serializers/railtie.rb +13 -0
- data/lib/ivy/serializers/registry.rb +33 -0
- data/lib/ivy/serializers/relationships.rb +2 -0
- data/lib/ivy/serializers/relationships/belongs_to.rb +13 -0
- data/lib/ivy/serializers/relationships/has_many.rb +13 -0
- data/lib/ivy/serializers/relationships/relationship.rb +23 -0
- data/lib/ivy/serializers/serializer.rb +35 -0
- data/lib/ivy/serializers/version.rb +5 -0
- data/spec/integration/formats/active_model_serializers_spec.rb +143 -0
- data/spec/integration/formats/json_api_spec.rb +254 -0
- data/spec/integration/serializer_spec.rb +47 -0
- data/spec/spec_helper.rb +93 -0
- metadata +165 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d908e19f0785161aaf56472b179ef00f1e82946d
|
4
|
+
data.tar.gz: cb49a7e6f89ff4d47457da9371f5857421f09683
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5d798714be3d66d8e57bf66919706b03bae4ff46b1925387a13d65a764e05f4c61cf8fa98cbb4de155ae20e4a07d27fce0e2cabc823b0b668d68af95e417e4c4
|
7
|
+
data.tar.gz: 520e783e3b0c2860f9dd2a1997dfd1fe8bf9df9cc8667b663b0a3a9814c8ed4477e111499ee949378940f04d1626be3e1ca9473fd2199e7d0228a42bf14f6f63
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.a
|
2
|
+
*.bundle
|
3
|
+
*.gem
|
4
|
+
*.o
|
5
|
+
*.rbc
|
6
|
+
*.so
|
7
|
+
.bundle
|
8
|
+
.config
|
9
|
+
.yardoc
|
10
|
+
Gemfile.lock
|
11
|
+
InstalledFiles
|
12
|
+
_yardoc
|
13
|
+
coverage
|
14
|
+
doc/
|
15
|
+
lib/bundler/man
|
16
|
+
mkmf.log
|
17
|
+
pkg
|
18
|
+
rdoc
|
19
|
+
spec/reports
|
20
|
+
test/tmp
|
21
|
+
test/version_tmp
|
22
|
+
tmp
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Envy Labs, LLC.
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
# Ivy::Serializers
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/IvyApp/ivy-serializers.svg?branch=master)](https://travis-ci.org/IvyApp/ivy-serializers)
|
4
|
+
|
5
|
+
JSON serialization for client-side apps, with multiple output formats. Ships
|
6
|
+
with [ActiveModel::Serializers][ams] and [JSON-API][jsonapi] RC3 support
|
7
|
+
out of the box.
|
8
|
+
|
9
|
+
[ams]: https://github.com/rails-api/active_model_serializers
|
10
|
+
[jsonapi]: http://jsonapi.org/
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem 'ivy-serializers'
|
18
|
+
```
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
```sh
|
23
|
+
bundle
|
24
|
+
```
|
25
|
+
|
26
|
+
Or install it yourself:
|
27
|
+
|
28
|
+
```sh
|
29
|
+
gem install ivy-serializers
|
30
|
+
```
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
|
34
|
+
### Defining a Serializer
|
35
|
+
|
36
|
+
Assuming we have `Post` and `Comment` models:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
class Post < ActiveRecord::Base
|
40
|
+
has_many :comments
|
41
|
+
end
|
42
|
+
|
43
|
+
class Comment < ActiveRecord::Base
|
44
|
+
belongs_to :post
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
Define a serializer in `app/serializers/serializer.rb`:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
class Serializer < Ivy::Serializers::Serializer
|
52
|
+
map Post do
|
53
|
+
attributes :id, :title
|
54
|
+
has_many :comments
|
55
|
+
end
|
56
|
+
|
57
|
+
map Comment do
|
58
|
+
attributes :id, :body
|
59
|
+
belongs_to :post
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
Then set up your controllers to use the serializer:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
class ApplicationController < ActionController::Base
|
68
|
+
self.serializer = Serializer
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
Note that you're not limited to a single serializer. If you have multiple
|
73
|
+
serialization formats, such as one for admin and one for public-facing, you can
|
74
|
+
define alternate serializers in `app/serializers` and use them as well.
|
75
|
+
|
76
|
+
### Sideloading
|
77
|
+
|
78
|
+
The `#belongs_to` and `#has_many` methods support an optional `:embed_in_root`
|
79
|
+
option, which will load the associated record into the root of the payload. For
|
80
|
+
instance, if we wanted the list of comments to be included when fetching
|
81
|
+
a post, we could define the `has_many` relationship like so:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
map Post do
|
85
|
+
has_many :comments, :embed_in_root => true
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
The same thing also works with `belongs_to`, so if we wanted to ensure the post
|
90
|
+
was included when fetching a comment:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
map Comment do
|
94
|
+
belongs_to :post, :embed_in_root => true
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
### Polymorphic Associations
|
99
|
+
|
100
|
+
There is also support for polymorphic associations. To use it, pass the
|
101
|
+
`:polymorphic => true` option to the `#belongs_to` or `#has_many` methods:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
map Post do
|
105
|
+
has_many :replies, :polymorphic => true
|
106
|
+
end
|
107
|
+
|
108
|
+
map Comment do
|
109
|
+
belongs_to :commentable, :polymorphic => true
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
### Customizing Attributes
|
114
|
+
|
115
|
+
By default, attributes are mapped directly to methods on the record being
|
116
|
+
serialized. So defining:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
map Post do
|
120
|
+
attributes :id, :title
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
will read `id` and `title` from the post and write it into the hash under the
|
125
|
+
`:id` and `:title` keys. If you want to customize the value, you can use the
|
126
|
+
`#attribute` method instead, and pass it a block:
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
map Post do
|
130
|
+
attribute(:title) { |post| post.headline }
|
131
|
+
end
|
132
|
+
```
|
133
|
+
|
134
|
+
In the above example, we read the `headline` attribute from the post and write
|
135
|
+
it into the payload under the `:title` key.
|
136
|
+
|
137
|
+
### Alternate Output Formats
|
138
|
+
|
139
|
+
By default the ActiveModel::Serializers output format is used. If you'd rather
|
140
|
+
use JSON-API (or a custom format), you can do so by setting
|
141
|
+
`serialization_format` in your controller. For instance, to use JSON-API:
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
class ApplicationController < ActionController::Base
|
145
|
+
self.serialization_format = Ivy::Serializers::Formats::JSONAPI
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
## Contributing
|
150
|
+
|
151
|
+
1. Fork it ( https://github.com/[my-github-username]/ivy-serializers/fork )
|
152
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
153
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
154
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
155
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.expand_path('../lib/ivy/serializers/version', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'ivy-serializers'
|
5
|
+
spec.version = Ivy::Serializers::VERSION
|
6
|
+
spec.authors = ['Dray Lacy']
|
7
|
+
spec.email = ['dray@envylabs.com']
|
8
|
+
spec.summary = 'JSON serialization for client-side apps.'
|
9
|
+
spec.homepage = ''
|
10
|
+
spec.license = 'MIT'
|
11
|
+
|
12
|
+
spec.files = `git ls-files -z`.split("\x0")
|
13
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
14
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
15
|
+
spec.require_paths = ['lib']
|
16
|
+
|
17
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
18
|
+
spec.add_development_dependency 'rake'
|
19
|
+
spec.add_development_dependency 'rspec', '~> 3.2.0'
|
20
|
+
spec.add_development_dependency 'simplecov', '~> 0.10.0'
|
21
|
+
|
22
|
+
spec.add_dependency 'activesupport', '>= 2.2.1'
|
23
|
+
spec.add_dependency 'hash_generator', '~> 1.1'
|
24
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'ivy/serializers'
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Ivy
|
2
|
+
module Serializers
|
3
|
+
module ActionController
|
4
|
+
module SerializationSupport
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
class SerializerNotFound < ::StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
included do
|
11
|
+
class_attribute :serializer, :instance_writer => false
|
12
|
+
class_attribute :serialization_format, :instance_writer => false
|
13
|
+
self.serialization_format = Formats::ActiveModelSerializers
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
%w[ _render_option_json _render_with_renderer_json ].each do |renderer_method|
|
19
|
+
define_method(renderer_method) do |resource, options|
|
20
|
+
unless serializer
|
21
|
+
raise SerializerNotFound, "No serializer found in #{self.class.name}"
|
22
|
+
end
|
23
|
+
|
24
|
+
unless resource.kind_of?(::Hash)
|
25
|
+
resource = Documents.create(serializer, controller_name, resource)
|
26
|
+
resource = serialization_format.new(resource)
|
27
|
+
end
|
28
|
+
|
29
|
+
super(resource, options)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Ivy
|
2
|
+
module Serializers
|
3
|
+
class Attribute
|
4
|
+
def initialize(name, &getter)
|
5
|
+
@name = name
|
6
|
+
@getter = getter || method(:default_getter)
|
7
|
+
end
|
8
|
+
|
9
|
+
def generate(generator, resource)
|
10
|
+
generator.attribute(@name, get(resource))
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def default_getter(resource)
|
16
|
+
resource.public_send(@name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def get(resource)
|
20
|
+
@getter.call(resource)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'ivy/serializers/documents/individual_resource'
|
2
|
+
require 'ivy/serializers/documents/resource_collection'
|
3
|
+
|
4
|
+
module Ivy
|
5
|
+
module Serializers
|
6
|
+
module Documents
|
7
|
+
def self.create(serializer, primary_resource_name, primary_resource)
|
8
|
+
klass = document_class_for(primary_resource)
|
9
|
+
klass.new(serializer, primary_resource_name, primary_resource)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.document_class_for(resource)
|
13
|
+
resource.respond_to?(:to_ary) ? ResourceCollection : IndividualResource
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Ivy
|
4
|
+
module Serializers
|
5
|
+
module Documents
|
6
|
+
class Document
|
7
|
+
def initialize(serializer, primary_resource_name, primary_resource)
|
8
|
+
@serializer = serializer
|
9
|
+
@primary_resource_name = primary_resource_name
|
10
|
+
@primary_resource = primary_resource
|
11
|
+
@linked_resources = Hash.new { |hash, klass| hash[klass] = Set.new }
|
12
|
+
end
|
13
|
+
|
14
|
+
def belongs_to(name, resource, options={})
|
15
|
+
link(resource) if options[:embed_in_root]
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate(generator)
|
19
|
+
generator.document(self)
|
20
|
+
end
|
21
|
+
|
22
|
+
def generate_linked(generator)
|
23
|
+
generator.linked(self) unless @linked_resources.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
def generate_linked_resources(generator)
|
27
|
+
@linked_resources.each_pair { |klass, resources| generator.linked_resources(klass, resources) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate_links(generator, resource)
|
31
|
+
@serializer.links(generator, resource)
|
32
|
+
end
|
33
|
+
|
34
|
+
def generate_resource(generator, resource)
|
35
|
+
@serializer.resource(generator, resource)
|
36
|
+
generator.links(resource)
|
37
|
+
end
|
38
|
+
|
39
|
+
def has_many(name, resources, options={})
|
40
|
+
link_many(resources) if options[:embed_in_root]
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def link(resource)
|
46
|
+
@serializer.links(self, resource) if @linked_resources[resource.class].add?(resource)
|
47
|
+
end
|
48
|
+
|
49
|
+
def link_many(resources)
|
50
|
+
resources.each { |resource| link(resource) }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'ivy/serializers/documents/document'
|
2
|
+
|
3
|
+
module Ivy
|
4
|
+
module Serializers
|
5
|
+
module Documents
|
6
|
+
class IndividualResource < Document
|
7
|
+
def generate_linked(generator)
|
8
|
+
@serializer.links(self, @primary_resource)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def generate_primary_resource(generator)
|
13
|
+
generator.primary_resource(@primary_resource_name, @primary_resource)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'ivy/serializers/documents/document'
|
2
|
+
|
3
|
+
module Ivy
|
4
|
+
module Serializers
|
5
|
+
module Documents
|
6
|
+
class ResourceCollection < Document
|
7
|
+
def generate_linked(generator)
|
8
|
+
@primary_resource.each { |resource| @serializer.links(self, resource) }
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def generate_primary_resource(generator)
|
13
|
+
generator.primary_resources(@primary_resource_name, @primary_resource)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|