jsonapi-serializable 0.1.3 → 0.2.1
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 +4 -4
- data/lib/jsonapi/serializable/error.rb +6 -0
- data/lib/jsonapi/serializable/relationship.rb +2 -1
- data/lib/jsonapi/serializable/relationship/dsl.rb +4 -24
- data/lib/jsonapi/serializable/renderer.rb +62 -53
- data/lib/jsonapi/serializable/resource.rb +71 -16
- data/lib/jsonapi/serializable/resource/conditional_fields.rb +82 -42
- data/lib/jsonapi/serializable/resource/dsl.rb +173 -0
- data/lib/jsonapi/serializable/resource/key_format.rb +47 -29
- data/lib/jsonapi/serializable/resource/relationship.rb +62 -0
- data/lib/jsonapi/serializable/resource/relationship/dsl.rb +103 -0
- data/lib/jsonapi/serializable/resource_builder.rb +23 -43
- metadata +12 -12
- data/lib/jsonapi/serializable/fieldset.rb +0 -7
- data/lib/jsonapi/serializable/resource/attributes.rb +0 -73
- data/lib/jsonapi/serializable/resource/id.rb +0 -48
- data/lib/jsonapi/serializable/resource/links.rb +0 -67
- data/lib/jsonapi/serializable/resource/meta.rb +0 -62
- data/lib/jsonapi/serializable/resource/relationships.rb +0 -95
- data/lib/jsonapi/serializable/resource/temp_id.rb +0 -13
- data/lib/jsonapi/serializable/resource/temp_id.rb~ +0 -0
- data/lib/jsonapi/serializable/resource/type.rb +0 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a2bfb6228e6b03c8bde3724af76fdbcedc097e0e
|
4
|
+
data.tar.gz: 28e75ac8293502f8e2a617c79fff3f803ca172d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 909fa65c618b4507da112491cc7b84646c07146dd027c5ba52386509ed9ea090916b413e78940f3fd4dad8cc2c518d56bd727953ad871ef4b404205ba5c9f39d
|
7
|
+
data.tar.gz: 8d924e246526821a8ab66d571054cdbe50b235c3d909e5c4c3ca935d6f57fac9b6221e3dcde1ce81eeebfcd32e99bf40bbc87e980a1113c0811bf7c76a32133a
|
@@ -6,9 +6,10 @@ module JSONAPI
|
|
6
6
|
class Relationship
|
7
7
|
include DSL
|
8
8
|
|
9
|
-
def initialize(exposures = {}, &block)
|
9
|
+
def initialize(exposures = {}, options = {}, &block)
|
10
10
|
exposures.each { |k, v| instance_variable_set("@#{k}", v) }
|
11
11
|
@_exposures = exposures
|
12
|
+
@_options = options
|
12
13
|
@_links = {}
|
13
14
|
instance_eval(&block)
|
14
15
|
end
|
@@ -5,14 +5,13 @@ module JSONAPI
|
|
5
5
|
class Relationship
|
6
6
|
module DSL
|
7
7
|
# Declare the related resources for this relationship.
|
8
|
-
# @param [String,Constant,Hash{Symbol=>String,Constant}] resource_class
|
9
8
|
# @yieldreturn The related resources for this relationship.
|
10
9
|
# If it is nil, an object implementing the Serializable::Resource
|
11
10
|
# interface, an empty array, or an array of objects implementing the
|
12
11
|
# Serializable::Resource interface, then it is used as is.
|
13
12
|
# Otherwise an appropriate Serializable::Resource subclass is inferred
|
14
|
-
# from the object(s)' namespace/class, the
|
15
|
-
#
|
13
|
+
# from the object(s)' namespace/class, the `class` relationship
|
14
|
+
# option, and the @_resource_builder.
|
16
15
|
#
|
17
16
|
# @example
|
18
17
|
# data do
|
@@ -29,21 +28,11 @@ module JSONAPI
|
|
29
28
|
# @user.posts
|
30
29
|
# end
|
31
30
|
# end
|
32
|
-
|
33
|
-
# @example
|
34
|
-
# data SerializablePost do
|
35
|
-
# @user.posts
|
36
|
-
# end
|
37
|
-
#
|
38
|
-
# @example
|
39
|
-
# data "SerializableUser" do
|
40
|
-
# @post.author
|
41
|
-
# end
|
42
|
-
def data(resource_class = nil)
|
31
|
+
def data
|
43
32
|
# NOTE(beauby): Lazify computation since it is only needed when
|
44
33
|
# the corresponding relationship is included.
|
45
34
|
@_resources_block = proc do
|
46
|
-
|
35
|
+
@_resource_builder.build(yield, @_exposures, @_options[:class])
|
47
36
|
end
|
48
37
|
end
|
49
38
|
|
@@ -106,15 +95,6 @@ module JSONAPI
|
|
106
95
|
def link(name, &block)
|
107
96
|
@_links[name] = Link.as_jsonapi(@_exposures, &block)
|
108
97
|
end
|
109
|
-
|
110
|
-
private
|
111
|
-
|
112
|
-
# @api private
|
113
|
-
def _resources_for(objects, resource_class)
|
114
|
-
resource_class ||= @_resource_inferrer
|
115
|
-
|
116
|
-
ResourceBuilder.build(objects, @_exposures, resource_class)
|
117
|
-
end
|
118
98
|
end
|
119
99
|
end
|
120
100
|
end
|
@@ -3,77 +3,86 @@ require 'jsonapi/serializable/resource_builder'
|
|
3
3
|
|
4
4
|
module JSONAPI
|
5
5
|
module Serializable
|
6
|
-
class
|
7
|
-
def
|
8
|
-
|
6
|
+
class SuccessRenderer
|
7
|
+
def initialize(renderer = JSONAPI::Renderer.new)
|
8
|
+
@renderer = renderer
|
9
9
|
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
11
|
+
# Serialize resources into a JSON API document.
|
12
|
+
#
|
13
|
+
# @param resources [nil,Object,Array]
|
14
|
+
# @param options [Hash]@see JSONAPI.render
|
15
|
+
# @option class [Class,Symbol,String,Hash{Symbol,String=>Class,Symbol,String}]
|
16
|
+
# The serializable resource class(es) to use for the primary resources.
|
17
|
+
# @option namespace [String] The namespace in which to look for
|
18
|
+
# serializable resource classes.
|
19
|
+
# @option inferrer [#call] The callable used for inferring a serializable
|
20
|
+
# resource class name from a resource class name.
|
21
|
+
# @option expose [Hash] The exposures made available in serializable
|
22
|
+
# resource class instances as instance variables.
|
23
|
+
# @return [Hash]
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# renderer.render(nil)
|
27
|
+
# # => { data: nil }
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
# renderer.render(user)
|
31
|
+
# # => {
|
32
|
+
# data: {
|
33
|
+
# type: 'users',
|
34
|
+
# id: 'foo',
|
35
|
+
# attributes: { ... },
|
36
|
+
# relationships: { ... }
|
37
|
+
# }
|
38
|
+
# }
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# renderer.render([user1, user2])
|
42
|
+
# # => { data: [{ type: 'users', id: 'foo', ... }, ...] }
|
43
|
+
def render(resources, options = {})
|
44
|
+
options = options.dup
|
45
|
+
klass = options.delete(:class)
|
46
|
+
namespace = options.delete(:namespace)
|
47
|
+
inferrer = options.delete(:inferrer) || namespace_inferrer(namespace)
|
48
|
+
expose = options.delete(:expose) || {}
|
49
|
+
resource_builder = JSONAPI::Serializable::ResourceBuilder.new(inferrer)
|
50
|
+
exposures = expose.merge(_resource_builder: resource_builder)
|
19
51
|
|
20
|
-
|
21
|
-
end
|
52
|
+
resources = resource_builder.build(resources, exposures, klass)
|
22
53
|
|
23
|
-
|
24
|
-
JSONAPI.render(jsonapi_params.merge(data: jsonapi_resources)).to_json
|
54
|
+
@renderer.render(options.merge(data: resources))
|
25
55
|
end
|
26
56
|
|
27
57
|
private
|
28
58
|
|
29
59
|
# @api private
|
30
|
-
def
|
31
|
-
@options
|
32
|
-
end
|
33
|
-
|
34
|
-
# @api private
|
35
|
-
def jsonapi_resources
|
36
|
-
toplevel_inferrer = @klass || @inferrer
|
37
|
-
JSONAPI::Serializable::ResourceBuilder.build(@objects,
|
38
|
-
@exposures,
|
39
|
-
toplevel_inferrer)
|
40
|
-
end
|
41
|
-
|
42
|
-
# @api private
|
43
|
-
def namespace_inferrer
|
44
|
-
return nil unless @namespace
|
60
|
+
def namespace_inferrer(namespace)
|
45
61
|
proc do |class_name|
|
46
62
|
names = class_name.split('::')
|
47
63
|
klass = names.pop
|
48
|
-
[
|
64
|
+
[namespace, *names, "Serializable#{klass}"].reject(&:nil?).join('::')
|
49
65
|
end
|
50
66
|
end
|
51
67
|
end
|
52
68
|
|
53
|
-
class
|
54
|
-
def
|
55
|
-
|
56
|
-
end
|
57
|
-
|
58
|
-
def initialize(errors, options)
|
59
|
-
@errors = errors
|
60
|
-
@options = options.dup
|
69
|
+
class ErrorsRenderer
|
70
|
+
def initialize(renderer = JSONAPI::Renderer.new)
|
71
|
+
@renderer = renderer
|
61
72
|
end
|
62
73
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
# @
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
def jsonapi_errors
|
76
|
-
@errors
|
74
|
+
# Serialize errors into a JSON API document.
|
75
|
+
#
|
76
|
+
# @param errors [Array]
|
77
|
+
# @param options [Hash] @see JSONAPI.render
|
78
|
+
# @return [Hash]
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
# error = JSONAPI::Serializable::Error.create(id: 'foo', title: 'bar')
|
82
|
+
# renderer.render([error])
|
83
|
+
# # => { errors: [{ id: 'foo', title: 'bar' }] }
|
84
|
+
def render(errors, options = {})
|
85
|
+
@renderer.render(options.merge(errors: errors))
|
77
86
|
end
|
78
87
|
end
|
79
88
|
end
|
@@ -1,9 +1,9 @@
|
|
1
|
-
require 'jsonapi/serializable/
|
2
|
-
|
3
|
-
require 'jsonapi/serializable/resource/
|
4
|
-
|
5
|
-
require 'jsonapi/serializable/
|
6
|
-
require 'jsonapi/serializable/
|
1
|
+
require 'jsonapi/serializable/resource_builder'
|
2
|
+
|
3
|
+
require 'jsonapi/serializable/resource/dsl'
|
4
|
+
|
5
|
+
require 'jsonapi/serializable/link'
|
6
|
+
require 'jsonapi/serializable/relationship'
|
7
7
|
|
8
8
|
require 'jsonapi/serializable/resource/conditional_fields'
|
9
9
|
require 'jsonapi/serializable/resource/key_format'
|
@@ -11,24 +11,61 @@ require 'jsonapi/serializable/resource/key_format'
|
|
11
11
|
module JSONAPI
|
12
12
|
module Serializable
|
13
13
|
class Resource
|
14
|
-
|
15
|
-
prepend Type
|
16
|
-
prepend Meta
|
17
|
-
prepend Links
|
18
|
-
prepend Attributes
|
19
|
-
prepend Relationships
|
14
|
+
extend DSL
|
20
15
|
|
21
16
|
# Default the value of id.
|
22
17
|
id { @object.public_send(:id).to_s }
|
23
18
|
|
19
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
24
20
|
def initialize(exposures = {})
|
25
|
-
|
26
|
-
|
21
|
+
@_exposures = {
|
22
|
+
_resource_builder: JSONAPI::Serializable::ResourceBuilder.new
|
23
|
+
}.merge(exposures)
|
24
|
+
@_exposures.each { |k, v| instance_variable_set("@#{k}", v) }
|
25
|
+
|
26
|
+
@_id = instance_eval(&self.class.id_block).to_s
|
27
|
+
@_type = if (b = self.class.type_block)
|
28
|
+
instance_eval(&b).to_sym
|
29
|
+
else
|
30
|
+
self.class.type_val || :unknown
|
31
|
+
end
|
32
|
+
@_relationships = self.class.relationship_blocks
|
33
|
+
.each_with_object({}) do |(k, v), h|
|
34
|
+
opts = self.class.relationship_options[k] || {}
|
35
|
+
h[k] = Relationship.new(@_exposures, opts, &v)
|
36
|
+
end
|
37
|
+
@_meta = if (b = self.class.meta_block)
|
38
|
+
instance_eval(&b)
|
39
|
+
else
|
40
|
+
self.class.meta_val
|
41
|
+
end
|
42
|
+
|
43
|
+
freeze
|
27
44
|
end
|
45
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
28
46
|
|
29
|
-
|
30
|
-
|
47
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
48
|
+
def as_jsonapi(fields: nil, include: [])
|
49
|
+
attrs = requested_attributes(fields).each_with_object({}) do |(k, v), h|
|
50
|
+
h[k] = instance_eval(&v)
|
51
|
+
end
|
52
|
+
rels = requested_relationships(fields)
|
53
|
+
.each_with_object({}) do |(k, v), h|
|
54
|
+
h[k] = v.as_jsonapi(include.include?(k))
|
55
|
+
end
|
56
|
+
links = link_blocks.each_with_object({}) do |(k, v), h|
|
57
|
+
h[k] = Link.as_jsonapi(@_exposures, &v)
|
58
|
+
end
|
59
|
+
{}.tap do |hash|
|
60
|
+
hash[:id] = @_id
|
61
|
+
hash[:type] = @_type
|
62
|
+
hash[:attributes] = attrs if attrs.any?
|
63
|
+
hash[:relationships] = rels if rels.any?
|
64
|
+
hash[:links] = links if links.any?
|
65
|
+
hash[:meta] = @_meta unless @_meta.nil?
|
66
|
+
end
|
31
67
|
end
|
68
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
32
69
|
|
33
70
|
def jsonapi_type
|
34
71
|
@_type
|
@@ -43,6 +80,24 @@ module JSONAPI
|
|
43
80
|
.select { |k, _| include.include?(k) }
|
44
81
|
.each_with_object({}) { |(k, v), h| h[k] = v.related_resources }
|
45
82
|
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
# @api private
|
87
|
+
def requested_attributes(fields)
|
88
|
+
self.class.attribute_blocks
|
89
|
+
.select { |k, _| fields.nil? || fields.include?(k) }
|
90
|
+
end
|
91
|
+
|
92
|
+
# @api private
|
93
|
+
def requested_relationships(fields)
|
94
|
+
@_relationships.select { |k, _| fields.nil? || fields.include?(k) }
|
95
|
+
end
|
96
|
+
|
97
|
+
# @api private
|
98
|
+
def link_blocks
|
99
|
+
self.class.link_blocks
|
100
|
+
end
|
46
101
|
end
|
47
102
|
end
|
48
103
|
end
|
@@ -5,7 +5,7 @@ module JSONAPI
|
|
5
5
|
#
|
6
6
|
# @usage
|
7
7
|
# class SerializableUser < JSONAPI::Serializable::Resource
|
8
|
-
#
|
8
|
+
# extend JSONAPI::Serializable::Resource::ConditionalFields
|
9
9
|
#
|
10
10
|
# attribute :email, if: -> { @current_user.admin? }
|
11
11
|
# has_many :friends, unless: -> { @object.private_profile? }
|
@@ -13,67 +13,107 @@ module JSONAPI
|
|
13
13
|
#
|
14
14
|
module ConditionalFields
|
15
15
|
def self.prepended(klass)
|
16
|
+
warn <<-EOT
|
17
|
+
DERPRECATION WARNING (called from #{caller_locations(1...2).first}):
|
18
|
+
Prepending `#{name}' is deprecated and will be removed in future releases. Use `Object#extend' instead.
|
19
|
+
EOT
|
20
|
+
|
21
|
+
klass.extend self
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.extended(klass)
|
16
25
|
klass.class_eval do
|
17
|
-
|
26
|
+
include InstanceMethods
|
18
27
|
class << self
|
19
|
-
attr_accessor :
|
28
|
+
attr_accessor :field_condition_blocks
|
29
|
+
attr_accessor :link_condition_blocks
|
20
30
|
end
|
21
|
-
self.
|
31
|
+
self.field_condition_blocks ||= {}
|
32
|
+
self.link_condition_blocks ||= {}
|
22
33
|
end
|
23
34
|
end
|
24
35
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
36
|
+
def inherited(klass)
|
37
|
+
super
|
38
|
+
klass.field_condition_blocks = field_condition_blocks.dup
|
39
|
+
klass.link_condition_blocks = link_condition_blocks.dup
|
40
|
+
end
|
31
41
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
42
|
+
# Handle the `if` and `unless` options for attributes.
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# attribute :email, if: -> { @current_user.admin? }
|
46
|
+
#
|
47
|
+
def attribute(name, options = {}, &block)
|
48
|
+
super
|
49
|
+
_register_condition(field_condition_blocks, name, options)
|
50
|
+
end
|
41
51
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
+
# Handle the `if` and `unless` options for relationships (has_one,
|
53
|
+
# belongs_to, has_many).
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# has_many :friends, unless: -> { @object.private_profile? }
|
57
|
+
#
|
58
|
+
def relationship(name, options = {}, &block)
|
59
|
+
super
|
60
|
+
_register_condition(field_condition_blocks, name, options)
|
61
|
+
end
|
52
62
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
63
|
+
# Handle the `if` and `unless` options for links.
|
64
|
+
#
|
65
|
+
# @example
|
66
|
+
#
|
67
|
+
# link :self, if: -> { @object.render_self_link? } do
|
68
|
+
# "..."
|
69
|
+
# end
|
70
|
+
def link(name, options = {}, &block)
|
71
|
+
super(name, &block)
|
72
|
+
_register_condition(link_condition_blocks, name, options)
|
73
|
+
end
|
74
|
+
|
75
|
+
# NOTE(beauby): Re-aliasing those is necessary for the
|
76
|
+
# overridden `#relationship` method to be called.
|
77
|
+
alias has_many relationship
|
78
|
+
alias has_one relationship
|
79
|
+
alias belongs_to relationship
|
80
|
+
|
81
|
+
# @api private
|
82
|
+
def _register_condition(condition_blocks, name, options)
|
83
|
+
condition_blocks[name.to_sym] =
|
84
|
+
if options.key?(:if)
|
85
|
+
options[:if]
|
86
|
+
elsif options.key?(:unless)
|
87
|
+
proc { !instance_exec(&options[:unless]) }
|
88
|
+
end
|
62
89
|
end
|
90
|
+
end
|
63
91
|
|
92
|
+
module InstanceMethods
|
64
93
|
# @api private
|
65
94
|
def requested_attributes(fields)
|
66
|
-
super.select
|
95
|
+
super.select do |k, _|
|
96
|
+
_conditionally_included?(self.class.field_condition_blocks, k)
|
97
|
+
end
|
67
98
|
end
|
68
99
|
|
69
100
|
# @api private
|
70
101
|
def requested_relationships(fields)
|
71
|
-
super.select
|
102
|
+
super.select do |k, _|
|
103
|
+
_conditionally_included?(self.class.field_condition_blocks, k)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# @api private
|
108
|
+
def link_blocks
|
109
|
+
super.select do |k, _|
|
110
|
+
_conditionally_included?(self.class.link_condition_blocks, k)
|
111
|
+
end
|
72
112
|
end
|
73
113
|
|
74
114
|
# @api private
|
75
|
-
def _conditionally_included?(field)
|
76
|
-
condition =
|
115
|
+
def _conditionally_included?(condition_blocks, field)
|
116
|
+
condition = condition_blocks[field]
|
77
117
|
condition.nil? || instance_exec(&condition)
|
78
118
|
end
|
79
119
|
end
|