ivy-serializers 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +14 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +155 -0
  8. data/Rakefile +7 -0
  9. data/ivy-serializers.gemspec +24 -0
  10. data/lib/ivy-serializers.rb +1 -0
  11. data/lib/ivy/serializers.rb +5 -0
  12. data/lib/ivy/serializers/action_controller/serialization_support.rb +35 -0
  13. data/lib/ivy/serializers/attribute.rb +24 -0
  14. data/lib/ivy/serializers/documents.rb +17 -0
  15. data/lib/ivy/serializers/documents/document.rb +55 -0
  16. data/lib/ivy/serializers/documents/individual_resource.rb +18 -0
  17. data/lib/ivy/serializers/documents/resource_collection.rb +18 -0
  18. data/lib/ivy/serializers/formats.rb +3 -0
  19. data/lib/ivy/serializers/formats/active_model_serializers.rb +40 -0
  20. data/lib/ivy/serializers/formats/json.rb +96 -0
  21. data/lib/ivy/serializers/formats/json_api.rb +66 -0
  22. data/lib/ivy/serializers/mapping.rb +40 -0
  23. data/lib/ivy/serializers/railtie.rb +13 -0
  24. data/lib/ivy/serializers/registry.rb +33 -0
  25. data/lib/ivy/serializers/relationships.rb +2 -0
  26. data/lib/ivy/serializers/relationships/belongs_to.rb +13 -0
  27. data/lib/ivy/serializers/relationships/has_many.rb +13 -0
  28. data/lib/ivy/serializers/relationships/relationship.rb +23 -0
  29. data/lib/ivy/serializers/serializer.rb +35 -0
  30. data/lib/ivy/serializers/version.rb +5 -0
  31. data/spec/integration/formats/active_model_serializers_spec.rb +143 -0
  32. data/spec/integration/formats/json_api_spec.rb +254 -0
  33. data/spec/integration/serializer_spec.rb +47 -0
  34. data/spec/spec_helper.rb +93 -0
  35. 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
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1
7
+ - 2.2
8
+
9
+ cache: bundler
10
+
11
+ notifications:
12
+ email:
13
+ on_success: change
14
+ on_failure: always
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
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,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ desc 'Default: run specs.'
7
+ task :default => :spec
@@ -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,5 @@
1
+ require 'ivy/serializers/documents'
2
+ require 'ivy/serializers/formats'
3
+ require 'ivy/serializers/serializer'
4
+ require 'ivy/serializers/version'
5
+ require 'ivy/serializers/railtie' if defined?(::Rails)
@@ -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