ivy-serializers 0.1.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 +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
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'ivy/serializers/formats/json'
|
2
|
+
|
3
|
+
module Ivy
|
4
|
+
module Serializers
|
5
|
+
module Formats
|
6
|
+
class ActiveModelSerializers < JSON
|
7
|
+
def belongs_to(name, resource, options={})
|
8
|
+
if options[:polymorphic]
|
9
|
+
if resource
|
10
|
+
@hash_gen.store_object(name) { polymorphic_resource(resource) }
|
11
|
+
else
|
12
|
+
@hash_gen.store(name, nil)
|
13
|
+
end
|
14
|
+
else
|
15
|
+
id(:"#{name}_id", resource)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def has_many(name, resources, options={})
|
20
|
+
if options[:polymorphic]
|
21
|
+
@hash_gen.store_array(name) { polymorphic_resources(resources) }
|
22
|
+
else
|
23
|
+
ids(:"#{ActiveSupport::Inflector.singularize(name.to_s)}_ids", resources)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def polymorphic_resource(resource)
|
30
|
+
id(:id, resource)
|
31
|
+
type(:type, resource)
|
32
|
+
end
|
33
|
+
|
34
|
+
def polymorphic_resources(resources)
|
35
|
+
resources.each { |resource| @hash_gen.push_object { polymorphic_resource(resource) } }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
require 'hash_generator'
|
3
|
+
|
4
|
+
module Ivy
|
5
|
+
module Serializers
|
6
|
+
module Formats
|
7
|
+
class JSON
|
8
|
+
def initialize(document)
|
9
|
+
@document = document
|
10
|
+
@hash_gen = HashGenerator.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def as_json(*)
|
14
|
+
@document.generate(self)
|
15
|
+
@hash_gen.to_h
|
16
|
+
end
|
17
|
+
|
18
|
+
def attribute(key, value)
|
19
|
+
@hash_gen.store(key, value)
|
20
|
+
end
|
21
|
+
|
22
|
+
def belongs_to(name, resource, options={})
|
23
|
+
id(name, resource)
|
24
|
+
end
|
25
|
+
|
26
|
+
def document(document)
|
27
|
+
document.generate_primary_resource(self)
|
28
|
+
document.generate_linked(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_many(name, resources, options={})
|
32
|
+
ids(name, resources)
|
33
|
+
end
|
34
|
+
|
35
|
+
def id(key, resource)
|
36
|
+
attribute(key, extract_id(resource))
|
37
|
+
end
|
38
|
+
|
39
|
+
def ids(key, resources)
|
40
|
+
attribute(key, resources.map { |resource| extract_id(resource) })
|
41
|
+
end
|
42
|
+
|
43
|
+
def linked(document)
|
44
|
+
document.generate_linked_resources(self)
|
45
|
+
end
|
46
|
+
|
47
|
+
def linked_resources(resource_class, resources)
|
48
|
+
key = key_for_collection(resource_class).to_sym
|
49
|
+
@hash_gen.store_array(key) { resources(resources) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def links(resource)
|
53
|
+
@document.generate_links(self, resource)
|
54
|
+
end
|
55
|
+
|
56
|
+
def primary_resource(primary_resource_name, primary_resource)
|
57
|
+
@hash_gen.store_object(primary_resource_name) { resource(primary_resource) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def primary_resources(primary_resources_name, primary_resources)
|
61
|
+
@hash_gen.store_array(primary_resources_name) { resources(primary_resources) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def resource(resource)
|
65
|
+
@document.generate_resource(self, resource)
|
66
|
+
end
|
67
|
+
|
68
|
+
def resources(resources)
|
69
|
+
resources.each { |resource| @hash_gen.push_object { resource(resource) } }
|
70
|
+
end
|
71
|
+
|
72
|
+
def type(key, resource)
|
73
|
+
attribute(key, extract_type(resource))
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def extract_id(resource)
|
79
|
+
resource.id
|
80
|
+
end
|
81
|
+
|
82
|
+
def extract_type(resource)
|
83
|
+
ActiveSupport::Inflector.dasherize(ActiveSupport::Inflector.underscore(resource.class.name))
|
84
|
+
end
|
85
|
+
|
86
|
+
def key_for_collection(resource_class)
|
87
|
+
ActiveSupport::Inflector.pluralize(key_for_individual(resource_class))
|
88
|
+
end
|
89
|
+
|
90
|
+
def key_for_individual(resource_class)
|
91
|
+
ActiveSupport::Inflector.underscore(resource_class.name)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'ivy/serializers/formats/json'
|
2
|
+
|
3
|
+
module Ivy
|
4
|
+
module Serializers
|
5
|
+
module Formats
|
6
|
+
class JSONAPI < JSON
|
7
|
+
def attribute(key, value)
|
8
|
+
value = coerce_id(value) if key == :id
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def belongs_to(name, resource, options={})
|
13
|
+
@hash_gen.store_object(name) { linkage(resource) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def has_many(name, resources, options={})
|
17
|
+
@hash_gen.store_object(name) { linkages(resources) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def linked(document)
|
21
|
+
@hash_gen.store_object(:included) { super }
|
22
|
+
end
|
23
|
+
|
24
|
+
def links(document)
|
25
|
+
@hash_gen.store_object(:links) { super }
|
26
|
+
end
|
27
|
+
|
28
|
+
def primary_resource(primary_resource_name, primary_resource)
|
29
|
+
@hash_gen.store_object(:data) { resource(primary_resource) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def resource(resource)
|
33
|
+
super
|
34
|
+
type(:type, resource)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def coerce_id(id)
|
40
|
+
id.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def extract_id(resource)
|
44
|
+
coerce_id(super)
|
45
|
+
end
|
46
|
+
|
47
|
+
def linkage(resource)
|
48
|
+
@hash_gen.store_object(:linkage) { linkage_object(resource) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def linkage_object(resource)
|
52
|
+
id(:id, resource)
|
53
|
+
type(:type, resource)
|
54
|
+
end
|
55
|
+
|
56
|
+
def linkages(resources)
|
57
|
+
@hash_gen.store_array(:linkage) { linkage_objects(resources) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def linkage_objects(resources)
|
61
|
+
resources.each { |resource| @hash_gen.push_object { linkage_object(resource) } }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'ivy/serializers/attribute'
|
2
|
+
require 'ivy/serializers/relationships'
|
3
|
+
|
4
|
+
module Ivy
|
5
|
+
module Serializers
|
6
|
+
class Mapping
|
7
|
+
def initialize(klass)
|
8
|
+
@attrs = {}
|
9
|
+
@links = {}
|
10
|
+
@klass = klass
|
11
|
+
|
12
|
+
attribute(:id)
|
13
|
+
end
|
14
|
+
|
15
|
+
def attribute(name, &block)
|
16
|
+
@attrs[name] = Attribute.new(name, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def attributes(*names)
|
20
|
+
names.each { |name| attribute(name) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def belongs_to(name, options={}, &block)
|
24
|
+
@links[name] = Relationships::BelongsTo.new(name, options, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def has_many(name, options={}, &block)
|
28
|
+
@links[name] = Relationships::HasMany.new(name, options, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def links(generator, resource)
|
32
|
+
@links.each_value { |link| link.generate(generator, resource) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def resource(generator, resource)
|
36
|
+
@attrs.each_value { |attr| attr.generate(generator, resource) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'ivy/serializers/action_controller/serialization_support'
|
2
|
+
|
3
|
+
module Ivy
|
4
|
+
module Serializers
|
5
|
+
class Railtie < ::Rails::Railtie
|
6
|
+
initializer 'ivy.serializers.controllers' do
|
7
|
+
ActiveSupport.on_load(:action_controller) do
|
8
|
+
include ::Ivy::Serializers::ActionController::SerializationSupport
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'ivy/serializers/mapping'
|
2
|
+
|
3
|
+
module Ivy
|
4
|
+
module Serializers
|
5
|
+
class Registry
|
6
|
+
def initialize
|
7
|
+
@mappings = Hash.new { |hash, klass| hash[klass] = new_mapping(klass) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def links(generator, resource)
|
11
|
+
mapping_for(resource.class).links(generator, resource)
|
12
|
+
end
|
13
|
+
|
14
|
+
def map(klass, &block)
|
15
|
+
mapping_for(klass).instance_eval(&block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def resource(generator, resource)
|
19
|
+
mapping_for(resource.class).resource(generator, resource)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def mapping_for(klass)
|
25
|
+
@mappings[klass]
|
26
|
+
end
|
27
|
+
|
28
|
+
def new_mapping(klass)
|
29
|
+
Mapping.new(klass)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'ivy/serializers/relationships/relationship'
|
2
|
+
|
3
|
+
module Ivy
|
4
|
+
module Serializers
|
5
|
+
module Relationships
|
6
|
+
class BelongsTo < Relationship
|
7
|
+
def generate(generator, resource)
|
8
|
+
generator.belongs_to(@name, get(resource), @options)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'ivy/serializers/relationships/relationship'
|
2
|
+
|
3
|
+
module Ivy
|
4
|
+
module Serializers
|
5
|
+
module Relationships
|
6
|
+
class HasMany < Relationship
|
7
|
+
def generate(generator, resource)
|
8
|
+
generator.has_many(@name, get(resource), @options)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Ivy
|
2
|
+
module Serializers
|
3
|
+
module Relationships
|
4
|
+
class Relationship
|
5
|
+
def initialize(name, options={}, &getter)
|
6
|
+
@name = name
|
7
|
+
@options = options
|
8
|
+
@getter = getter || method(:default_getter)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def default_getter(resource)
|
14
|
+
resource.public_send(@name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(resource)
|
18
|
+
@getter.call(resource)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'ivy/serializers/registry'
|
2
|
+
|
3
|
+
module Ivy
|
4
|
+
module Serializers
|
5
|
+
class Serializer
|
6
|
+
class << self
|
7
|
+
attr_accessor :_registry
|
8
|
+
|
9
|
+
def inherited(base)
|
10
|
+
base._registry = Registry.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def links(generator, resource)
|
14
|
+
_registry.links(generator, resource)
|
15
|
+
end
|
16
|
+
|
17
|
+
def map(klass, &block)
|
18
|
+
_registry.map(klass, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def resource(generator, resource)
|
22
|
+
_registry.resource(generator, resource)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def links(generator, links)
|
27
|
+
self.class.links(generator, links)
|
28
|
+
end
|
29
|
+
|
30
|
+
def resource(generator, resource)
|
31
|
+
self.class.resource(generator, resource)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'ivy/serializers'
|
2
|
+
|
3
|
+
RSpec.describe Ivy::Serializers::Formats::ActiveModelSerializers do
|
4
|
+
let(:format) { described_class.new(document) }
|
5
|
+
|
6
|
+
describe '#as_json' do
|
7
|
+
let(:registry) { Ivy::Serializers::Registry.new }
|
8
|
+
let(:document) { Ivy::Serializers::Documents.create(registry, :post, post) }
|
9
|
+
|
10
|
+
subject { format.as_json }
|
11
|
+
|
12
|
+
context 'with default mapping' do
|
13
|
+
let(:post) { double('post', :id => 1) }
|
14
|
+
|
15
|
+
it { should eq(:post => {:id => 1}) }
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'with an attribute' do
|
19
|
+
let(:post_class) { double('Post') }
|
20
|
+
let(:post) { double('post', :class => post_class, :id => 1, :title => 'title') }
|
21
|
+
|
22
|
+
context 'with default options' do
|
23
|
+
before do
|
24
|
+
registry.map post_class do
|
25
|
+
attribute :title
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it { should eq(:post => {:id => 1, :title => 'title'}) }
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'with a block provided' do
|
33
|
+
before do
|
34
|
+
registry.map post_class do
|
35
|
+
attribute(:headline) { |post| post.title }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it { should eq(:post => {:id => 1, :headline => 'title'}) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'with a belongs_to relationship' do
|
44
|
+
let(:author_class) { double('Author', :name => 'Author') }
|
45
|
+
let(:author) { double('author', :class => author_class, :id => 1) }
|
46
|
+
let(:post_class) { double('Post') }
|
47
|
+
let(:post) { double('post', :author => author, :class => post_class, :id => 1) }
|
48
|
+
|
49
|
+
context 'with default options' do
|
50
|
+
before do
|
51
|
+
registry.map post_class do
|
52
|
+
belongs_to :author
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it { should eq(:post => {:author_id => 1, :id => 1}) }
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'with a block provided' do
|
60
|
+
before do
|
61
|
+
registry.map post_class do
|
62
|
+
belongs_to(:user) { |post| post.author }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it { should eq(:post => {:id => 1, :user_id => 1}) }
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'with the :embed_in_root option' do
|
70
|
+
before do
|
71
|
+
registry.map post_class do
|
72
|
+
belongs_to :author, :embed_in_root => true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it { should eq(
|
77
|
+
:authors => [{:id => 1}],
|
78
|
+
:post => {:author_id => 1, :id => 1}
|
79
|
+
) }
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'with the :polymorphic option' do
|
83
|
+
before do
|
84
|
+
registry.map post_class do
|
85
|
+
belongs_to :author, :polymorphic => true
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
it { should eq(:post => {:author => {:id => 1, :type => 'author'}, :id => 1}) }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'with a has_many relationship' do
|
94
|
+
let(:comment_class) { double('Comment', :name => 'Comment') }
|
95
|
+
let(:comment) { double('comment', :class => comment_class, :id => 1) }
|
96
|
+
let(:post_class) { double('Post') }
|
97
|
+
let(:post) { double('post', :class => post_class, :comments => [comment], :id => 1) }
|
98
|
+
|
99
|
+
context 'with default options' do
|
100
|
+
before do
|
101
|
+
registry.map post_class do
|
102
|
+
has_many :comments
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
it { should eq(:post => {:comment_ids => [1], :id => 1}) }
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'with a block provided' do
|
110
|
+
before do
|
111
|
+
registry.map post_class do
|
112
|
+
has_many(:replies) { |post| post.comments }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it { should eq(:post => {:id => 1, :reply_ids => [1]}) }
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'with the :embed_in_root option' do
|
120
|
+
before do
|
121
|
+
registry.map post_class do
|
122
|
+
has_many :comments, :embed_in_root => true
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
it { should eq(
|
127
|
+
:comments => [{:id => 1}],
|
128
|
+
:post => {:comment_ids => [1], :id => 1}
|
129
|
+
) }
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'with the :polymorphic option' do
|
133
|
+
before do
|
134
|
+
registry.map post_class do
|
135
|
+
has_many :comments, :polymorphic => true
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
it { should eq(:post => {:comments => [{:id => 1, :type => 'comment'}], :id => 1}) }
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|