Skip to content

Commit 2443af1

Browse files
committed
Initial commit
0 parents  commit 2443af1

14 files changed

+711
-0
lines changed

.gitignore

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
*.gem
2+
*.rbc
3+
/.config
4+
/coverage/
5+
/InstalledFiles
6+
/pkg/
7+
/spec/reports/
8+
/spec/examples.txt
9+
/test/tmp/
10+
/test/version_tmp/
11+
/tmp/
12+
13+
## Specific to RubyMotion:
14+
.dat*
15+
.repl_history
16+
build/
17+
18+
## Documentation cache and generated files:
19+
/.yardoc/
20+
/_yardoc/
21+
/doc/
22+
/rdoc/
23+
24+
## Environment normalization:
25+
/.bundle/
26+
/vendor/bundle
27+
/lib/bundler/man/
28+
29+
Gemfile.lock
30+
.ruby-version
31+
.ruby-gemset
32+
33+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34+
.rvmrc

.travis.yml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
language: ruby
2+
sudo: false
3+
before_install:
4+
- bundle update
5+
rvm:
6+
- 2.1
7+
- 2.2
8+
- 2.3.0
9+
- ruby-head
10+
matrix:
11+
allow_failures:
12+
- rvm: ruby-head

Gemfile

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
source 'https://rubygems.org'
2+
3+
gemspec

README.md

+234
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
# jsonapi-deserializable
2+
Ruby gem for deserializing [JSON API](http://jsonapi.org) payloads into custom
3+
hashes.
4+
5+
## Status
6+
7+
[![Gem Version](https://badge.fury.io/rb/jsonapi-deserializable.svg)](https://badge.fury.io/rb/jsonapi-deserializable)
8+
[![Build Status](https://secure.travis-ci.org/jsonapi-rb/deserializable.svg?branch=master)](http://travis-ci.org/jsonapi-rb/deserializable?branch=master)
9+
10+
## Table of Contents
11+
12+
- [Installation](#installation)
13+
- [Usage/Examples](#usageexamples)
14+
- [Documentation](#documentation)
15+
- [Common methods](#common-methods)
16+
- [`JSONAPI::Deserializable::Resource` DSL](#jsonapideserializableresource-dsl)
17+
- [`JSONAPI::Deserializable::Relationship` DSL](#jsonapideserializablerelationship-dsl)
18+
- [License](#license)
19+
20+
## Installation
21+
```ruby
22+
# In Gemfile
23+
gem 'jsonapi-deserializable'
24+
```
25+
then
26+
```
27+
$ bundle
28+
```
29+
or manually via
30+
```
31+
$ gem install jsonapi-deserializable
32+
```
33+
34+
## Usage/Examples
35+
36+
First, require the gem:
37+
```ruby
38+
require 'jsonapi/deserializable'
39+
```
40+
41+
Then, define some resource/relationship deserializable classes:
42+
43+
### Resource Example
44+
45+
```ruby
46+
class DeserializablePost < JSONAPI::Deserializable::Resource
47+
type
48+
attribute :title
49+
attribute :date { |date| field date: DateTime.parse(date) }
50+
has_one :author do |rel, id, type|
51+
field author_id: id
52+
field author_type: type
53+
end
54+
has_many :comments do |rel, ids, types|
55+
field comment_ids: ids
56+
field comment_types: types.map do |type|
57+
camelize(singularize(type))
58+
end
59+
end
60+
end
61+
```
62+
63+
which can then be used to deserialize post payloads:
64+
```ruby
65+
DeserializablePost.(payload)
66+
# => {
67+
# id: '1',
68+
# title: 'Title',
69+
# date: #<DateTime: 2016-01-10T02:30:00+00:00 ((2457398j,9000s,0n),+0s,2299161j)>,
70+
# author_id: '1337',
71+
# author_type: 'users',
72+
# comment_ids: ['123', '234', '345']
73+
# comment_types: ['Comment', 'Comment', 'Comment']
74+
# }
75+
```
76+
77+
### Relationship Example
78+
79+
```ruby
80+
class DeserializablePostComments < JSONAPI::Deserializable::Relationship
81+
has_many do |rel, ids, types|
82+
field comment_ids: ids
83+
field comment_types: types.map do |ri|
84+
camelize(singularize(type))
85+
end
86+
field comments_meta: rel['meta']
87+
end
88+
end
89+
```
90+
```ruby
91+
DeserializablePostComments.(payload)
92+
# => {
93+
# comment_ids: ['123', '234', '345']
94+
# comment_types: ['Comment', 'Comment', 'Comment']
95+
# }
96+
```
97+
98+
## Documentation
99+
100+
Whether deserializaing a resource or a relationship, the base idea is the same:
101+
for every part of the payload, simply declare the fields you want to build from
102+
their value. You can create as many fields as you want out of any one part of
103+
the payload.
104+
105+
It works according to a whitelisting mechanism: should the corresponding part of
106+
the payload not be present, the fields will simply not be created on the result
107+
hash.
108+
109+
Note however that the library expects well formed JSONAPI payloads (which you
110+
can ensure using, for instance,
111+
[jsonapi-parser](https://github.com/beauby/jsonapi/tree/master/parser)),
112+
and that deserialization does not substitute itself to validation of the
113+
resulting hash (which you can handle using, for instance,
114+
[dry-validation](http://dry-rb.org/gems/dry-validation/)).
115+
116+
### Common Methods
117+
118+
+ `::field(hash)`
119+
120+
The `field` DSL method is the base of jsonapi-deserializable. It simply declares
121+
a field of the result hash, with its value. The syntax is:
122+
```ruby
123+
field key: value
124+
```
125+
126+
It is mainly used within the following DSL contexts, but can be used outside of
127+
any to declare custom non payload-related fields.
128+
129+
+ `#initialize(payload)`
130+
131+
Build a deserializable instance, ready to be deserialized by calling `#to_h`.
132+
133+
+ `#to_h`
134+
135+
In order to deserialize a payload, simply do:
136+
```ruby
137+
DeserializablePost.new(payload).to_h
138+
```
139+
or use the shorthand syntax:
140+
```ruby
141+
DeserializablePost.(payload)
142+
```
143+
144+
### `JSONAPI::Deserializable::Resource` DSL
145+
146+
+ `::type(&block)`
147+
```ruby
148+
type do |type|
149+
field my_type_field: type
150+
end
151+
```
152+
153+
Shorthand syntax:
154+
```ruby
155+
type
156+
```
157+
158+
+ `::id(&block)`
159+
```ruby
160+
id do |id|
161+
field my_id_field: id
162+
end
163+
```
164+
165+
Shorthand syntax:
166+
```ruby
167+
id
168+
```
169+
170+
+ `::attribute(key, &block)`
171+
```ruby
172+
attribute :title do |title|
173+
field my_title_field: title
174+
end
175+
```
176+
177+
Shorthand syntax:
178+
```ruby
179+
attribute :title
180+
```
181+
182+
+ `::has_one(key, &block)`
183+
```ruby
184+
has_one :author do |rel, id, type|
185+
field my_author_type_field: type
186+
field my_author_id_field: id
187+
field my_author_meta_field: rel['meta']
188+
end
189+
```
190+
191+
Shorthand syntax:
192+
```ruby
193+
has_one :author
194+
```
195+
Note: this creates a field `:author` with value the whole relationship hash.
196+
197+
+ `::has_many(key, &block)`
198+
```ruby
199+
has_many :comments do |rel, ids, types|
200+
field my_comment_types_field: types
201+
field my_comment_ids_field: ids
202+
field my_comment_meta_field: rel['meta']
203+
end
204+
```
205+
206+
Shorthand syntax:
207+
```ruby
208+
has_many :comments
209+
```
210+
Note: this creates a field `:comments` with value the whole relationship hash.
211+
212+
### `JSONAPI::Deserializable::Relationship` DSL
213+
214+
+ `::has_one(key, &block)`
215+
```ruby
216+
has_one do |rel, id, type|
217+
field my_relationship_id_field: id
218+
field my_relationship_type_field: type
219+
field my_relationship_meta_field: rel['meta']
220+
end
221+
```
222+
223+
+ `has_many(key, &block)`
224+
```ruby
225+
has_many do |rel, ids, types|
226+
field my_relationship_ids_field: ids
227+
field my_relationship_types_field: types
228+
field my_relationship_meta_field: rel['meta']
229+
end
230+
```
231+
232+
## License
233+
234+
jsonapi-deserializable is released under the [MIT License](http://www.opensource.org/licenses/MIT).

Rakefile

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
require 'bundler/gem_tasks'
2+
require 'rspec/core/rake_task'
3+
4+
RSpec::Core::RakeTask.new(:spec) do |t|
5+
t.pattern = Dir.glob('spec/**/*_spec.rb')
6+
end
7+
8+
task default: :test
9+
task test: :spec

VERSION

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.1.1.beta3

jsonapi-deserializable.gemspec

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
version = File.read(File.expand_path('../VERSION', __FILE__)).strip
2+
3+
Gem::Specification.new do |spec|
4+
spec.name = 'jsonapi-deserializable'
5+
spec.version = version
6+
spec.author = 'Lucas Hosseini'
7+
spec.email = '[email protected]'
8+
spec.summary = 'Deserialize JSON API payloads.'
9+
spec.description = 'DSL for deserializing incoming JSON API payloads ' \
10+
'into custom hashes.'
11+
spec.homepage = 'https://github.com/jsonapi-rb/deserializable'
12+
spec.license = 'MIT'
13+
14+
spec.files = Dir['README.md', 'lib/**/*']
15+
spec.require_path = 'lib'
16+
17+
spec.add_development_dependency 'rake', '>=0.9'
18+
spec.add_development_dependency 'rspec', '~>3.4'
19+
end

lib/jsonapi/deserializable.rb

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require 'jsonapi/deserializable/relationship'
2+
require 'jsonapi/deserializable/resource'
+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
require 'jsonapi/deserializable/relationship_dsl'
2+
3+
module JSONAPI
4+
module Deserializable
5+
class Relationship
6+
include RelationshipDSL
7+
8+
class << self
9+
attr_accessor :has_one_block, :has_many_block
10+
end
11+
12+
def self.inherited(klass)
13+
klass.has_one_block = has_one_block
14+
klass.has_many_block = has_many_block
15+
end
16+
17+
def self.call(payload)
18+
new(payload).to_h
19+
end
20+
21+
def initialize(payload)
22+
@document = payload
23+
@data = payload['data']
24+
deserialize!
25+
end
26+
27+
def to_h
28+
@hash
29+
end
30+
31+
private
32+
33+
def deserialize!
34+
@hash = {}
35+
if @data.is_a?(Array)
36+
ids = @data.map { |ri| ri['id'] }
37+
types = @data.map { |ri| ri['type'] }
38+
instance_exec(@document, ids, types, &self.class.has_many_block)
39+
else
40+
id = @data && @data['id']
41+
type = @data && @data['type']
42+
instance_exec(@document, id, type, &self.class.has_one_block)
43+
end
44+
end
45+
46+
def field(hash)
47+
@hash.merge!(hash)
48+
end
49+
end
50+
end
51+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module JSONAPI
2+
module Deserializable
3+
module RelationshipDSL
4+
def self.included(base)
5+
base.extend(ClassMethods)
6+
end
7+
8+
module ClassMethods
9+
def has_one(&block)
10+
block ||= proc { |rel| field key.to_sym => rel }
11+
self.has_one_block = block
12+
end
13+
14+
def has_many(&block)
15+
block ||= proc { |rel| field key.to_sym => rel }
16+
self.has_many_block = block
17+
end
18+
end
19+
end
20+
end
21+
end

0 commit comments

Comments
 (0)