json_api_ruby 0.0.1.alpha
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 +1 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +92 -0
- data/Guardfile +69 -0
- data/Rakefile +1 -0
- data/json_api_ruby.gemspec +27 -0
- data/lib/json_api_ruby/configuration.rb +15 -0
- data/lib/json_api_ruby/error_resource.rb +11 -0
- data/lib/json_api_ruby/exceptions.rb +3 -0
- data/lib/json_api_ruby/resource.rb +55 -0
- data/lib/json_api_ruby/resources/base.rb +61 -0
- data/lib/json_api_ruby/resources/discovery.rb +40 -0
- data/lib/json_api_ruby/resources/dsl.rb +54 -0
- data/lib/json_api_ruby/resources/relationships.rb +106 -0
- data/lib/json_api_ruby/serializer.rb +100 -0
- data/lib/json_api_ruby/version.rb +3 -0
- data/lib/json_api_ruby.rb +8 -0
- data/spec/json_api_ruby/resource_spec.rb +90 -0
- data/spec/json_api_ruby/resources/discover_spec.rb +23 -0
- data/spec/json_api_ruby/resources/relationships_spec.rb +110 -0
- data/spec/json_api_ruby/serializer_spec.rb +82 -0
- data/spec/spec_helper.rb +102 -0
- data/spec/support/json_api_matchers.rb +54 -0
- data/spec/support/resource_objects.rb +114 -0
- metadata +160 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f137dccac6caa31e15ca4519ed3b6a9e45682739
|
4
|
+
data.tar.gz: 5256e906efe7899bfacd0c6916620bdc066d8dc6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 76ce05324913fb8100783b4a3f274c98c1e4a8afa6d2ec0fc90db58f8d1aac39379012de2bd6dd0f178343d11f805a867fb9b1a715df3021ea76c5d50f62ba83
|
7
|
+
data.tar.gz: 1bd8c66e4ebb6c7ddf1f0137f90fe5050b08c5e81d0b4c9503f42cf095fc2124d94b86ec7b44954a4c888634aa37294e207c2d3b55342060ee77e4074edca2d7
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
json_api (0.0.1.beta)
|
5
|
+
activesupport
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activesupport (4.2.5)
|
11
|
+
i18n (~> 0.7)
|
12
|
+
json (~> 1.7, >= 1.7.7)
|
13
|
+
minitest (~> 5.1)
|
14
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
15
|
+
tzinfo (~> 1.1)
|
16
|
+
byebug (8.2.1)
|
17
|
+
coderay (1.1.0)
|
18
|
+
diff-lcs (1.2.5)
|
19
|
+
ffi (1.9.10)
|
20
|
+
formatador (0.2.5)
|
21
|
+
guard (2.13.0)
|
22
|
+
formatador (>= 0.2.4)
|
23
|
+
listen (>= 2.7, <= 4.0)
|
24
|
+
lumberjack (~> 1.0)
|
25
|
+
nenv (~> 0.1)
|
26
|
+
notiffany (~> 0.0)
|
27
|
+
pry (>= 0.9.12)
|
28
|
+
shellany (~> 0.0)
|
29
|
+
thor (>= 0.18.1)
|
30
|
+
guard-compat (1.2.1)
|
31
|
+
guard-rspec (4.6.4)
|
32
|
+
guard (~> 2.1)
|
33
|
+
guard-compat (~> 1.1)
|
34
|
+
rspec (>= 2.99.0, < 4.0)
|
35
|
+
i18n (0.7.0)
|
36
|
+
json (1.8.3)
|
37
|
+
listen (3.0.5)
|
38
|
+
rb-fsevent (>= 0.9.3)
|
39
|
+
rb-inotify (>= 0.9)
|
40
|
+
lumberjack (1.0.10)
|
41
|
+
method_source (0.8.2)
|
42
|
+
minitest (5.8.3)
|
43
|
+
nenv (0.2.0)
|
44
|
+
notiffany (0.0.8)
|
45
|
+
nenv (~> 0.1)
|
46
|
+
shellany (~> 0.0)
|
47
|
+
pry (0.10.3)
|
48
|
+
coderay (~> 1.1.0)
|
49
|
+
method_source (~> 0.8.1)
|
50
|
+
slop (~> 3.4)
|
51
|
+
pry-byebug (3.3.0)
|
52
|
+
byebug (~> 8.0)
|
53
|
+
pry (~> 0.10)
|
54
|
+
rb-fsevent (0.9.7)
|
55
|
+
rb-inotify (0.9.5)
|
56
|
+
ffi (>= 0.5.0)
|
57
|
+
rspec (3.4.0)
|
58
|
+
rspec-core (~> 3.4.0)
|
59
|
+
rspec-expectations (~> 3.4.0)
|
60
|
+
rspec-mocks (~> 3.4.0)
|
61
|
+
rspec-core (3.4.1)
|
62
|
+
rspec-support (~> 3.4.0)
|
63
|
+
rspec-expectations (3.4.0)
|
64
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
65
|
+
rspec-support (~> 3.4.0)
|
66
|
+
rspec-its (1.2.0)
|
67
|
+
rspec-core (>= 3.0.0)
|
68
|
+
rspec-expectations (>= 3.0.0)
|
69
|
+
rspec-mocks (3.4.0)
|
70
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
71
|
+
rspec-support (~> 3.4.0)
|
72
|
+
rspec-support (3.4.1)
|
73
|
+
shellany (0.0.1)
|
74
|
+
slop (3.6.0)
|
75
|
+
thor (0.19.1)
|
76
|
+
thread_safe (0.3.5)
|
77
|
+
tzinfo (1.2.2)
|
78
|
+
thread_safe (~> 0.1)
|
79
|
+
|
80
|
+
PLATFORMS
|
81
|
+
ruby
|
82
|
+
|
83
|
+
DEPENDENCIES
|
84
|
+
bundler (~> 1.3)
|
85
|
+
guard-rspec
|
86
|
+
json_api!
|
87
|
+
pry-byebug
|
88
|
+
rspec (~> 3)
|
89
|
+
rspec-its
|
90
|
+
|
91
|
+
BUNDLED WITH
|
92
|
+
1.10.6
|
data/Guardfile
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
directories %w(lib spec).select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
6
|
+
|
7
|
+
## Note: if you are using the `directories` clause above and you are not
|
8
|
+
## watching the project directory ('.'), then you will want to move
|
9
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
10
|
+
#
|
11
|
+
# $ mkdir config
|
12
|
+
# $ mv Guardfile config/
|
13
|
+
# $ ln -s config/Guardfile .
|
14
|
+
#
|
15
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
16
|
+
|
17
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
18
|
+
# rspec may be run, below are examples of the most common uses.
|
19
|
+
# * bundler: 'bundle exec rspec'
|
20
|
+
# * bundler binstubs: 'bin/rspec'
|
21
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
22
|
+
# installed the spring binstubs per the docs)
|
23
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
24
|
+
# * 'just' rspec: 'rspec'
|
25
|
+
|
26
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
27
|
+
require "guard/rspec/dsl"
|
28
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
29
|
+
|
30
|
+
# Feel free to open issues for suggestions and improvements
|
31
|
+
|
32
|
+
# RSpec files
|
33
|
+
rspec = dsl.rspec
|
34
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
35
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
36
|
+
watch(rspec.spec_files)
|
37
|
+
|
38
|
+
# Ruby files
|
39
|
+
ruby = dsl.ruby
|
40
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
41
|
+
|
42
|
+
# Rails files
|
43
|
+
rails = dsl.rails(view_extensions: %w(erb haml slim))
|
44
|
+
dsl.watch_spec_files_for(rails.app_files)
|
45
|
+
dsl.watch_spec_files_for(rails.views)
|
46
|
+
|
47
|
+
watch(rails.controllers) do |m|
|
48
|
+
[
|
49
|
+
rspec.spec.("routing/#{m[1]}_routing"),
|
50
|
+
rspec.spec.("controllers/#{m[1]}_controller"),
|
51
|
+
rspec.spec.("acceptance/#{m[1]}")
|
52
|
+
]
|
53
|
+
end
|
54
|
+
|
55
|
+
# Rails config changes
|
56
|
+
watch(rails.spec_helper) { rspec.spec_dir }
|
57
|
+
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
|
58
|
+
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
|
59
|
+
|
60
|
+
# Capybara features specs
|
61
|
+
watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
|
62
|
+
watch(rails.layouts) { |m| rspec.spec.("features/#{m[1]}") }
|
63
|
+
|
64
|
+
# Turnip features and steps
|
65
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
66
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
|
67
|
+
Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
|
68
|
+
end
|
69
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'json_api_ruby/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "json_api_ruby"
|
8
|
+
spec.version = JsonApi::VERSION
|
9
|
+
spec.authors = ["Tracey Eubanks"]
|
10
|
+
spec.email = ["tracey@bypassmobile.com"]
|
11
|
+
spec.description = %q{Create JSON API resources when you don't have Rails 4+ available}
|
12
|
+
spec.summary = %q{Create JSON API resources when you don't have Rails 4+ available}
|
13
|
+
spec.homepage = "https://github.com/teubanks/jsonapi_ruby"
|
14
|
+
spec.license = "Free For All"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "activesupport", "~> 3"
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rspec", "~> 3"
|
24
|
+
spec.add_development_dependency "rspec-its", "~> 1"
|
25
|
+
spec.add_development_dependency "pry-byebug", "~> 3"
|
26
|
+
spec.add_development_dependency "guard-rspec", "~> 4"
|
27
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative 'resources/base'
|
2
|
+
require_relative 'resources/relationships'
|
3
|
+
require_relative 'resources/dsl'
|
4
|
+
|
5
|
+
module JsonApi
|
6
|
+
class Resource
|
7
|
+
include Resources::Base
|
8
|
+
extend Resources::DSL
|
9
|
+
|
10
|
+
def self.inherited(subclass)
|
11
|
+
subclass.send(:id_field, :id)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Can be set using `id_field` in the created resource class like so:
|
15
|
+
#
|
16
|
+
# class ObjectResource < JsonApi::Resource
|
17
|
+
# id_field :uuid
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# defaults to :id
|
21
|
+
def id
|
22
|
+
object.public_send(self.class._id_field).to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
# Can be overridden in a subclass
|
26
|
+
def type
|
27
|
+
_model.class.to_s.underscore.pluralize
|
28
|
+
end
|
29
|
+
|
30
|
+
# Makes the underlying object available to subclasses so we can do things
|
31
|
+
# like
|
32
|
+
#
|
33
|
+
# class PersonResource < JsonApi::Resource
|
34
|
+
# attribute :email
|
35
|
+
# attribute :full_name
|
36
|
+
#
|
37
|
+
# def full_name
|
38
|
+
# "#{object.first_name} #{object.last_name}"
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
def object
|
42
|
+
_model
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_accessor :_model
|
46
|
+
|
47
|
+
attr_reader :includes
|
48
|
+
|
49
|
+
def initialize(model, options={})
|
50
|
+
@_model = model
|
51
|
+
@includes = options.fetch(:include, []).map(&:to_s)
|
52
|
+
build_object_graph # base module method
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative 'discovery'
|
2
|
+
module JsonApi
|
3
|
+
module Resources
|
4
|
+
|
5
|
+
module Base
|
6
|
+
attr_reader :relationships
|
7
|
+
def to_hash(options={})
|
8
|
+
options.symbolize_keys
|
9
|
+
|
10
|
+
resource_hash = identifier_hash
|
11
|
+
resource_hash['attributes'] = attributes_hash
|
12
|
+
|
13
|
+
relationships.each do |relationship|
|
14
|
+
resource_hash['relationships'] ||= {}
|
15
|
+
resource_hash['relationships'][relationship.name] = relationship.to_hash
|
16
|
+
end
|
17
|
+
|
18
|
+
resource_hash['links'] = links_hash
|
19
|
+
resource_hash
|
20
|
+
end
|
21
|
+
|
22
|
+
# Very basic. Eventually this will need to parse things like
|
23
|
+
# "article.comments" and "article-comments", so, leaving the method here
|
24
|
+
# but only supporting the most basic of things
|
25
|
+
def parse_for_includes(includes)
|
26
|
+
Array(includes).map {|inc| inc.to_s }
|
27
|
+
end
|
28
|
+
|
29
|
+
def identifier_hash
|
30
|
+
{ 'id' => self.id, 'type' => self.type }
|
31
|
+
end
|
32
|
+
|
33
|
+
def links_hash
|
34
|
+
{ 'self' => JsonApi.configuration.base_url + self_link_path }
|
35
|
+
end
|
36
|
+
|
37
|
+
def self_link_path
|
38
|
+
"/#{self.type}/#{self.id}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def attributes_hash
|
42
|
+
attrs = {}
|
43
|
+
self.class.fields.each do |attr|
|
44
|
+
attrs[attr.to_s] = send(attr)
|
45
|
+
end
|
46
|
+
attrs
|
47
|
+
end
|
48
|
+
|
49
|
+
# Builds relationship resource classes
|
50
|
+
def build_object_graph
|
51
|
+
@relationships ||= []
|
52
|
+
Array(self.class.relationships).each do |relationship|
|
53
|
+
included = includes.include?(relationship.name)
|
54
|
+
relationship.build_resources({parent_resource: self, included: included})
|
55
|
+
@relationships << relationship
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module JsonApi
|
2
|
+
module Resources
|
3
|
+
class Discovery
|
4
|
+
def self.resource_for_name(model, options={})
|
5
|
+
namespace = options.fetch(:namespace, nil)
|
6
|
+
klass = options.fetch(:resource_class, nil)
|
7
|
+
parent = options.fetch(:parent_resource, nil)
|
8
|
+
|
9
|
+
if klass.blank?
|
10
|
+
# discover it
|
11
|
+
klass = resource_class(model.class.to_s.underscore, namespace: namespace, parent: parent)
|
12
|
+
end
|
13
|
+
|
14
|
+
Object.const_get(klass)
|
15
|
+
rescue NameError
|
16
|
+
fail ::JsonApi::ResourceNotFound.new("Could not find resource class `#{klass}'")
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.resource_class(model_name, namespace:, parent:)
|
20
|
+
if namespace
|
21
|
+
klass = [
|
22
|
+
namespace.to_s.underscore,
|
23
|
+
"#{model_name.to_s.underscore}_resource"
|
24
|
+
].join('/').classify
|
25
|
+
else
|
26
|
+
klass = resource_path(model_name, parent).join.classify
|
27
|
+
end
|
28
|
+
klass
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.resource_path(model_name, parent)
|
32
|
+
current_namespace = parent.class.to_s.underscore.split('/')
|
33
|
+
current_namespace.pop
|
34
|
+
current_namespace << '/' if current_namespace.present?
|
35
|
+
current_namespace << "#{model_name.to_s}_resource"
|
36
|
+
current_namespace
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module JsonApi
|
2
|
+
module Resources
|
3
|
+
module DSL
|
4
|
+
attr :_id_field
|
5
|
+
attr :fields
|
6
|
+
attr :relationships
|
7
|
+
|
8
|
+
def attributes(*attrs)
|
9
|
+
attrs.each do |attr|
|
10
|
+
attribute(attr)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def attribute(attr)
|
15
|
+
@fields ||= []
|
16
|
+
@fields << attr
|
17
|
+
create_accessor_methods(attr)
|
18
|
+
end
|
19
|
+
|
20
|
+
def has_one(object_name, options={})
|
21
|
+
add_relationship(object_name, :one, options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_many(object_name, options={})
|
25
|
+
add_relationship(object_name, :many, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def id_field(key)
|
29
|
+
@_id_field = key
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def add_relationship(object_name, cardinality, options)
|
34
|
+
@relationships ||= []
|
35
|
+
if cardinality == :one
|
36
|
+
@relationships << ToOneRelationship.new(object_name, options)
|
37
|
+
else
|
38
|
+
@relationships << ToManyRelationship.new(object_name, options)
|
39
|
+
end
|
40
|
+
create_accessor_methods(object_name)
|
41
|
+
end
|
42
|
+
|
43
|
+
def create_accessor_methods(attr)
|
44
|
+
define_method(attr) do
|
45
|
+
object.public_send(attr)
|
46
|
+
end unless method_defined?(attr)
|
47
|
+
|
48
|
+
define_method("#{attr}=") do |value|
|
49
|
+
object.public_send("#{attr}=", value)
|
50
|
+
end unless method_defined?("#{attr}=")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module JsonApi
|
2
|
+
module Resources
|
3
|
+
|
4
|
+
class Relationships
|
5
|
+
# The name of this relationship.
|
6
|
+
# This name comes from the resource object that defines the
|
7
|
+
# relationship. Example:
|
8
|
+
# class ArticleResource < JsonApi::Resource
|
9
|
+
# has_one :author # this is the name of this relationship
|
10
|
+
# end
|
11
|
+
attr_reader :name
|
12
|
+
|
13
|
+
# The resource object that "owns" this relationship
|
14
|
+
#
|
15
|
+
# Example:
|
16
|
+
# class ArticleResource < JsonApi::Resource
|
17
|
+
# has_one :author
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# `ArticleResource` is the parent of the author object
|
21
|
+
attr_reader :parent
|
22
|
+
|
23
|
+
# Determines whether the `data` attribute should be filled out and
|
24
|
+
# included
|
25
|
+
attr_reader :included
|
26
|
+
|
27
|
+
# The resource object that represents this relationship
|
28
|
+
attr_reader :resources
|
29
|
+
|
30
|
+
attr_reader :parent_model
|
31
|
+
|
32
|
+
def initialize(name, options)
|
33
|
+
@name = name.to_s
|
34
|
+
@resources = []
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_hash
|
38
|
+
return_hash = links
|
39
|
+
return_hash.merge!(data) if included?
|
40
|
+
return_hash
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_resources(options)
|
44
|
+
@parent = options.fetch(:parent_resource)
|
45
|
+
@parent_model = parent._model
|
46
|
+
@included = options.fetch(:included, false)
|
47
|
+
end
|
48
|
+
|
49
|
+
def included?
|
50
|
+
included == true
|
51
|
+
end
|
52
|
+
|
53
|
+
def links
|
54
|
+
{
|
55
|
+
'links' => {
|
56
|
+
'self' => JsonApi.configuration.base_url + parent.self_link_path + "/relationships/#{name}",
|
57
|
+
'related' => JsonApi.configuration.base_url + parent.self_link_path + "/#{name}"
|
58
|
+
}
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# convenience classes
|
64
|
+
class ToOneRelationship < Relationships
|
65
|
+
def data(options={})
|
66
|
+
identifier_hash = resource_object.identifier_hash if resource_object
|
67
|
+
{'data' => Hash(identifier_hash)}
|
68
|
+
end
|
69
|
+
|
70
|
+
def build_resources(options)
|
71
|
+
super
|
72
|
+
return unless included?
|
73
|
+
resource_model = parent_model.send(name)
|
74
|
+
return if resource_model.blank?
|
75
|
+
resource_class = Discovery.resource_for_name(resource_model, options.merge(parent_resource: parent))
|
76
|
+
@resources << resource_class.new(resource_model)
|
77
|
+
end
|
78
|
+
|
79
|
+
def resource_object
|
80
|
+
@resources.first
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class ToManyRelationship < Relationships
|
85
|
+
def data(options={})
|
86
|
+
data = resource_objects.map do |object|
|
87
|
+
object.identifier_hash
|
88
|
+
end
|
89
|
+
{'data' => data}
|
90
|
+
end
|
91
|
+
|
92
|
+
def build_resources(options)
|
93
|
+
super
|
94
|
+
return unless included?
|
95
|
+
parent_model.send(name).each do |resource_model|
|
96
|
+
resource_class = Discovery.resource_for_name(resource_model, options.merge(parent_resource: parent))
|
97
|
+
@resources << resource_class.new(resource_model)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def resource_objects
|
102
|
+
@resources
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module JsonApi
|
2
|
+
extend self
|
3
|
+
|
4
|
+
def serialize_errors(*errors)
|
5
|
+
resource_hashes = errors.flatten.map do |error|
|
6
|
+
ErrorResource.new(error).to_hash
|
7
|
+
end
|
8
|
+
{ errors: resource_hashes }
|
9
|
+
end
|
10
|
+
|
11
|
+
def serialize(object, options = {})
|
12
|
+
options.stringify_keys!
|
13
|
+
# assume it's a collection
|
14
|
+
if object.present? && object.respond_to?(:to_a)
|
15
|
+
serializer = CollectionSerializer.new(object, options)
|
16
|
+
else
|
17
|
+
serializer = Serializer.new(object, options)
|
18
|
+
end
|
19
|
+
serializer.to_hash
|
20
|
+
end
|
21
|
+
|
22
|
+
class Serializer
|
23
|
+
def initialize(object, options)
|
24
|
+
@meta = options.fetch('meta', Hash.new).stringify_keys
|
25
|
+
@object = object
|
26
|
+
@includes = options.fetch('include', [])
|
27
|
+
resource_name = "#{@object.class.to_s.underscore}_resource".classify
|
28
|
+
@klass_name = options.fetch('class_name', resource_name)
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_hash
|
32
|
+
resource_klass = Resources::Discovery.resource_for_name(@object, resource_class: @klass_name)
|
33
|
+
resource = resource_klass.new(@object, include: @includes)
|
34
|
+
serialized = { 'data' => resource.to_hash }
|
35
|
+
relationships = resource.relationships
|
36
|
+
included_data = assemble_included_data(relationships)
|
37
|
+
|
38
|
+
if included_data.present?
|
39
|
+
included_data.uniq! do |inc_data|
|
40
|
+
inc_data['id'] + inc_data['type']
|
41
|
+
end
|
42
|
+
serialized['included'] = included_data
|
43
|
+
end
|
44
|
+
|
45
|
+
serialized['meta'] = @meta if @meta.present?
|
46
|
+
serialized
|
47
|
+
end
|
48
|
+
|
49
|
+
def assemble_included_data(relationships)
|
50
|
+
relationships.flat_map do |relationship|
|
51
|
+
next if relationship.resources.blank?
|
52
|
+
relationship.resources.map(&:to_hash)
|
53
|
+
end.compact
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class CollectionSerializer
|
58
|
+
def initialize(objects, options = {})
|
59
|
+
@meta = options.fetch('meta', Hash.new).stringify_keys
|
60
|
+
@objects = objects
|
61
|
+
@includes = options.fetch('include', [])
|
62
|
+
@klass_name = options.fetch('class_name', nil)
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_hash
|
66
|
+
serialized = {}
|
67
|
+
included_data = []
|
68
|
+
|
69
|
+
data_array = @objects.map do |object|
|
70
|
+
resource_name = "#{object.class.to_s.underscore}_resource".classify
|
71
|
+
klass_name = @klass_name || resource_name
|
72
|
+
resource_klass = Resources::Discovery.resource_for_name(object, resource_class: klass_name)
|
73
|
+
resource = resource_klass.new(object, include: @includes)
|
74
|
+
included_data += assemble_included_data(resource.relationships)
|
75
|
+
resource.to_hash
|
76
|
+
end
|
77
|
+
|
78
|
+
serialized['data'] = data_array
|
79
|
+
|
80
|
+
serialized['meta'] = @meta if @meta
|
81
|
+
|
82
|
+
if included_data.present?
|
83
|
+
included_data.uniq! do |inc_data|
|
84
|
+
inc_data['id'] + inc_data['type']
|
85
|
+
end
|
86
|
+
serialized['included'] = included_data
|
87
|
+
end
|
88
|
+
|
89
|
+
serialized
|
90
|
+
end
|
91
|
+
|
92
|
+
def assemble_included_data(relationships)
|
93
|
+
relationships.flat_map do |relationship|
|
94
|
+
next if relationship.resources.blank?
|
95
|
+
relationship.resources.map(&:to_hash)
|
96
|
+
end.compact
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|