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
@@ -0,0 +1,173 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
module Serializable
|
3
|
+
class Resource
|
4
|
+
module DSL
|
5
|
+
def self.extended(klass)
|
6
|
+
class << klass
|
7
|
+
attr_accessor :id_block, :type_val, :type_block, :attribute_blocks,
|
8
|
+
:relationship_blocks, :relationship_options,
|
9
|
+
:link_blocks, :meta_val, :meta_block
|
10
|
+
end
|
11
|
+
|
12
|
+
klass.attribute_blocks = {}
|
13
|
+
klass.relationship_blocks = {}
|
14
|
+
klass.relationship_options = {}
|
15
|
+
klass.link_blocks = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
# rubocop:disable Metrics/AbcSize
|
19
|
+
def inherited(klass)
|
20
|
+
klass.id_block = id_block
|
21
|
+
klass.type_val = type_val
|
22
|
+
klass.type_block = type_block
|
23
|
+
klass.attribute_blocks = attribute_blocks.dup
|
24
|
+
klass.relationship_blocks = relationship_blocks.dup
|
25
|
+
klass.relationship_options = relationship_options.dup
|
26
|
+
klass.link_blocks = link_blocks.dup
|
27
|
+
klass.meta_val = meta_val
|
28
|
+
klass.meta_block = meta_block
|
29
|
+
end
|
30
|
+
# rubocop:enable Metrics/AbcSize
|
31
|
+
|
32
|
+
# Declare the JSON API id of this resource.
|
33
|
+
#
|
34
|
+
# @yieldreturn [String] The id of the resource.
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
# id { @user.id.to_s }
|
38
|
+
def id(&block)
|
39
|
+
self.id_block = block
|
40
|
+
end
|
41
|
+
|
42
|
+
# @overload type(value)
|
43
|
+
# Declare the JSON API type of this resource.
|
44
|
+
# @param [String] value The value of the type.
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# type 'users'
|
48
|
+
#
|
49
|
+
# @overload type(&block)
|
50
|
+
# Declare the JSON API type of this resource.
|
51
|
+
# @yieldreturn [String] The value of the type.
|
52
|
+
#
|
53
|
+
# @example
|
54
|
+
# type { @object.type }
|
55
|
+
def type(value = nil, &block)
|
56
|
+
self.type_val = value.to_sym if value
|
57
|
+
self.type_block = block
|
58
|
+
end
|
59
|
+
|
60
|
+
# Declare an attribute for this resource.
|
61
|
+
#
|
62
|
+
# @param [Symbol] name The key of the attribute.
|
63
|
+
# @yieldreturn [Hash, String, nil] The block to compute the value.
|
64
|
+
#
|
65
|
+
# @example
|
66
|
+
# attribute(:name) { @object.name }
|
67
|
+
def attribute(name, _options = {}, &block)
|
68
|
+
block ||= proc { @object.public_send(name) }
|
69
|
+
attribute_blocks[name.to_sym] = block
|
70
|
+
end
|
71
|
+
|
72
|
+
# Declare a list of attributes for this resource.
|
73
|
+
#
|
74
|
+
# @param [Array] *args The attributes keys.
|
75
|
+
#
|
76
|
+
# @example
|
77
|
+
# attributes :title, :body, :date
|
78
|
+
def attributes(*args)
|
79
|
+
args.each do |attr|
|
80
|
+
attribute(attr)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Declare a link for this resource. The properties of the link are set
|
85
|
+
# by providing a block in which the DSL methods of
|
86
|
+
# +JSONAPI::Serializable::Link+ are called, or the value of the link
|
87
|
+
# is returned directly.
|
88
|
+
# @see JSONAPI::Serialiable::Link
|
89
|
+
#
|
90
|
+
# @param [Symbol] name The key of the link.
|
91
|
+
# @yieldreturn [Hash, String, nil] The block to compute the value, if
|
92
|
+
# any.
|
93
|
+
#
|
94
|
+
# @example
|
95
|
+
# link(:self) do
|
96
|
+
# "http://api.example.com/users/#{@user.id}"
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# @example
|
100
|
+
# link(:self) do
|
101
|
+
# href "http://api.example.com/users/#{@user.id}"
|
102
|
+
# meta is_self: true
|
103
|
+
# end
|
104
|
+
def link(name, &block)
|
105
|
+
link_blocks[name] = block
|
106
|
+
end
|
107
|
+
|
108
|
+
# @overload meta(value)
|
109
|
+
# Declare the meta information for this resource.
|
110
|
+
# @param [Hash] value The meta information hash.
|
111
|
+
#
|
112
|
+
# @example
|
113
|
+
# meta key: value
|
114
|
+
#
|
115
|
+
# @overload meta(&block)
|
116
|
+
# Declare the meta information for this resource.
|
117
|
+
# @yieldreturn [String] The meta information hash.
|
118
|
+
# @example
|
119
|
+
# meta do
|
120
|
+
# { key: value }
|
121
|
+
# end
|
122
|
+
def meta(value = nil, &block)
|
123
|
+
self.meta_val = value
|
124
|
+
self.meta_block = block
|
125
|
+
end
|
126
|
+
|
127
|
+
# Declare a relationship for this resource. The properties of the
|
128
|
+
# relationship are set by providing a block in which the DSL methods
|
129
|
+
# of +JSONAPI::Serializable::Relationship+ are called.
|
130
|
+
# @see JSONAPI::Serializable::Relationship
|
131
|
+
#
|
132
|
+
# @param [Symbol] name The key of the relationship.
|
133
|
+
# @param [Hash] options The options for the relationship.
|
134
|
+
#
|
135
|
+
# @example
|
136
|
+
# relationship :posts do
|
137
|
+
# resources { @user.posts.map { |p| PostResource.new(post: p) } }
|
138
|
+
# end
|
139
|
+
#
|
140
|
+
# @example
|
141
|
+
# relationship :author do
|
142
|
+
# resources do
|
143
|
+
# @post.author && UserResource.new(user: @post.author)
|
144
|
+
# end
|
145
|
+
# data do
|
146
|
+
# { type: 'users', id: @post.author_id }
|
147
|
+
# end
|
148
|
+
# link(:self) do
|
149
|
+
# "http://api.example.com/posts/#{@post.id}/relationships/author"
|
150
|
+
# end
|
151
|
+
# link(:related) do
|
152
|
+
# "http://api.example.com/posts/#{@post.id}/author"
|
153
|
+
# end
|
154
|
+
# meta do
|
155
|
+
# { author_online: @post.author.online? }
|
156
|
+
# end
|
157
|
+
# end
|
158
|
+
def relationship(name, options = {}, &block)
|
159
|
+
rel_block = proc do
|
160
|
+
data { @object.public_send(name) }
|
161
|
+
instance_eval(&block) unless block.nil?
|
162
|
+
end
|
163
|
+
relationship_blocks[name.to_sym] = rel_block
|
164
|
+
relationship_options[name.to_sym] = options
|
165
|
+
end
|
166
|
+
|
167
|
+
alias has_many relationship
|
168
|
+
alias has_one relationship
|
169
|
+
alias belongs_to relationship
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -4,10 +4,10 @@ module JSONAPI
|
|
4
4
|
# Extension for handling automatic key formatting of
|
5
5
|
# attributes/relationships.
|
6
6
|
#
|
7
|
-
# @
|
7
|
+
# @example
|
8
8
|
# class SerializableUser < JSONAPI::Serializable::Resource
|
9
|
-
#
|
10
|
-
#
|
9
|
+
# extend JSONAPI::Serializable::Resource::KeyFormat
|
10
|
+
# key_format -> (key) { key.camelize }
|
11
11
|
#
|
12
12
|
# attribute :user_name
|
13
13
|
# has_many :close_friends
|
@@ -15,42 +15,60 @@ module JSONAPI
|
|
15
15
|
# # => will modify the serialized keys to `UserName` and `CloseFriends`.
|
16
16
|
module KeyFormat
|
17
17
|
def self.prepended(klass)
|
18
|
+
warn <<-EOT
|
19
|
+
DERPRECATION WARNING (called from #{caller_locations(1...2).first}):
|
20
|
+
Prepending `#{name}' is deprecated and will be removed in future releases. Use `Object#extend' instead.
|
21
|
+
EOT
|
22
|
+
|
23
|
+
klass.extend self
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.extended(klass)
|
18
27
|
klass.class_eval do
|
19
|
-
extend DSL
|
20
28
|
class << self
|
21
|
-
attr_accessor :
|
29
|
+
attr_accessor :_key_formatter
|
22
30
|
end
|
23
31
|
end
|
24
32
|
end
|
25
33
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
klass.key_format = key_format
|
31
|
-
end
|
34
|
+
def inherited(klass)
|
35
|
+
super
|
36
|
+
klass._key_formatter = _key_formatter
|
37
|
+
end
|
32
38
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
39
|
+
# Set the callable responsible for formatting keys, either directly, or
|
40
|
+
# via a block.
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# key_format -> (key) { key.capitalize }
|
44
|
+
#
|
45
|
+
# @example
|
46
|
+
# key_format { |key| key.capitalize }
|
47
|
+
#
|
48
|
+
def key_format(callable = nil, &block)
|
49
|
+
self._key_formatter = callable || block
|
50
|
+
end
|
38
51
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
45
|
-
super(key_format.call(name), options, &rel_block)
|
46
|
-
end
|
52
|
+
# Handles automatic key formatting for attributes.
|
53
|
+
def attribute(name, options = {}, &block)
|
54
|
+
block ||= proc { @object.public_send(name) }
|
55
|
+
super(_key_formatter.call(name), options, &block)
|
56
|
+
end
|
47
57
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
58
|
+
# Handles automatic key formatting for relationships.
|
59
|
+
def relationship(name, options = {}, &block)
|
60
|
+
rel_block = proc do
|
61
|
+
data { @object.public_send(name) }
|
62
|
+
instance_eval(&block) unless block.nil?
|
63
|
+
end
|
64
|
+
super(_key_formatter.call(name), options, &rel_block)
|
53
65
|
end
|
66
|
+
|
67
|
+
# NOTE(beauby): Re-aliasing those is necessary for the
|
68
|
+
# overridden `#relationship` method to be called.
|
69
|
+
alias has_many relationship
|
70
|
+
alias has_one relationship
|
71
|
+
alias belongs_to relationship
|
54
72
|
end
|
55
73
|
end
|
56
74
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'jsonapi/serializable/link'
|
2
|
+
require 'jsonapi/serializable/resource/relationship/dsl'
|
3
|
+
|
4
|
+
module JSONAPI
|
5
|
+
module Serializable
|
6
|
+
class Resource
|
7
|
+
class Relationship
|
8
|
+
include DSL
|
9
|
+
|
10
|
+
def initialize(exposures = {}, options = {}, &block)
|
11
|
+
exposures.each { |k, v| instance_variable_set("@#{k}", v) }
|
12
|
+
@_exposures = exposures
|
13
|
+
@_options = options
|
14
|
+
@_links = {}
|
15
|
+
instance_eval(&block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def as_jsonapi(included)
|
19
|
+
{}.tap do |hash|
|
20
|
+
hash[:links] = @_links if @_links.any?
|
21
|
+
hash[:data] = linkage_data if included || @_include_linkage
|
22
|
+
hash[:meta] = @_meta unless @_meta.nil?
|
23
|
+
hash[:meta] = { included: false } if hash.empty?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
def related_resources
|
29
|
+
@_related_resources ||= Array(resources)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @api private
|
33
|
+
def links
|
34
|
+
@_links
|
35
|
+
end
|
36
|
+
|
37
|
+
# @api private
|
38
|
+
def meta
|
39
|
+
@_meta
|
40
|
+
end
|
41
|
+
|
42
|
+
# @api private
|
43
|
+
def resources
|
44
|
+
@_resources ||= @_resources_block.call
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# @api private
|
50
|
+
def linkage_data
|
51
|
+
return @_linkage_block.call if @_linkage_block
|
52
|
+
|
53
|
+
linkage_data = related_resources.map do |res|
|
54
|
+
{ type: res.jsonapi_type, id: res.jsonapi_id }
|
55
|
+
end
|
56
|
+
|
57
|
+
resources.respond_to?(:each) ? linkage_data : linkage_data.first
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'jsonapi/serializable/resource_builder'
|
2
|
+
|
3
|
+
module JSONAPI
|
4
|
+
module Serializable
|
5
|
+
class Resource
|
6
|
+
class Relationship
|
7
|
+
module DSL
|
8
|
+
# Declare the related resources for this relationship.
|
9
|
+
# @yieldreturn The related resources for this relationship.
|
10
|
+
# If it is nil, an object implementing the Serializable::Resource
|
11
|
+
# interface, an empty array, or an array of objects implementing the
|
12
|
+
# Serializable::Resource interface, then it is used as is.
|
13
|
+
# Otherwise an appropriate Serializable::Resource subclass is
|
14
|
+
# inferred from the object(s)' namespace/class, the `class`
|
15
|
+
# relationship option, and the @_resource_builder.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# data do
|
19
|
+
# @user.posts.map { |p| PostResource.new(post: p) }
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# data do
|
24
|
+
# @post.author && UserResource.new(user: @user.author)
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# data do
|
29
|
+
# @user.posts
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
def data
|
33
|
+
# NOTE(beauby): Lazify computation since it is only needed when
|
34
|
+
# the corresponding relationship is included.
|
35
|
+
@_resources_block = proc do
|
36
|
+
@_resource_builder.build(yield, @_exposures, @_options[:class])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @overload linkage(options = {}, &block)
|
41
|
+
# Explicitly declare linkage data.
|
42
|
+
# @yieldreturn The resource linkage.
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# linkage do
|
46
|
+
# @object.posts.map { |p| { id: p.id.to_s, type: 'posts' } }
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# @overload linkage(options = {})
|
50
|
+
# Forces standard linkage even if relationship not included.
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# linkage always: true
|
54
|
+
def linkage(always: false, &block)
|
55
|
+
@_include_linkage = always
|
56
|
+
@_linkage_block = block
|
57
|
+
end
|
58
|
+
|
59
|
+
# @overload meta(value)
|
60
|
+
# Declare the meta information for this relationship.
|
61
|
+
# @param [Hash] value The meta information hash.
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# meta paginated: true
|
65
|
+
#
|
66
|
+
# @overload meta(&block)
|
67
|
+
# Declare the meta information for this relationship.
|
68
|
+
# @yieldreturn [Hash] The meta information hash.
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# meta do
|
72
|
+
# { paginated: true }
|
73
|
+
# end
|
74
|
+
def meta(value = nil)
|
75
|
+
@_meta = value || yield
|
76
|
+
end
|
77
|
+
|
78
|
+
# Declare a link for this relationship. The properties of the link are set
|
79
|
+
# by providing a block in which the DSL methods of
|
80
|
+
# +JSONAPI::Serializable::Link+ are called.
|
81
|
+
# @see JSONAPI::Serialiable::Link
|
82
|
+
#
|
83
|
+
# @param [Symbol] name The key of the link.
|
84
|
+
# @yieldreturn [Hash, String, nil] The block to compute the value, if any.
|
85
|
+
#
|
86
|
+
# @example
|
87
|
+
# link(:self) do
|
88
|
+
# "http://api.example.com/users/#{@user.id}/relationships/posts"
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# @example
|
92
|
+
# link(:related) do
|
93
|
+
# href "http://api.example.com/users/#{@user.id}/posts"
|
94
|
+
# meta authorization_needed: true
|
95
|
+
# end
|
96
|
+
def link(name, &block)
|
97
|
+
@_links[name] = Link.as_jsonapi(@_exposures, &block)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -1,74 +1,54 @@
|
|
1
1
|
module JSONAPI
|
2
2
|
module Serializable
|
3
3
|
class ResourceBuilder
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
klass_name = [namespace, "Serializable#{klass_name}"]
|
10
|
-
.reject(&:nil?)
|
11
|
-
.reject(&:empty?)
|
12
|
-
.join('::'.freeze)
|
4
|
+
# @api private
|
5
|
+
def initialize(inferrer = nil)
|
6
|
+
@inferrer = inferrer
|
7
|
+
@lookup_cache = {}
|
13
8
|
|
14
|
-
|
9
|
+
freeze
|
15
10
|
end
|
16
11
|
|
17
|
-
|
12
|
+
# @api private
|
13
|
+
def build(objects, expose, klass = nil)
|
18
14
|
return objects if objects.nil? ||
|
19
15
|
Array(objects).first.respond_to?(:as_jsonapi)
|
20
16
|
|
21
17
|
if objects.respond_to?(:to_ary)
|
22
|
-
Array(objects).map { |obj|
|
18
|
+
Array(objects).map { |obj| _build(obj, expose, klass) }
|
23
19
|
else
|
24
|
-
|
20
|
+
_build(objects, expose, klass)
|
25
21
|
end
|
26
22
|
end
|
27
23
|
|
28
|
-
attr_reader :resource
|
29
|
-
|
30
|
-
def initialize(object, expose, klass)
|
31
|
-
@object = object
|
32
|
-
@expose = expose || {}
|
33
|
-
@klass = klass
|
34
|
-
@resource = serializable_class.new(serializable_params)
|
35
|
-
freeze
|
36
|
-
end
|
37
|
-
|
38
24
|
private
|
39
25
|
|
40
26
|
# @api private
|
41
|
-
def
|
42
|
-
|
27
|
+
def _build(object, expose, klass)
|
28
|
+
serializable_class(object.class.name, klass)
|
29
|
+
.new(expose.merge(object: object))
|
43
30
|
end
|
44
31
|
|
45
32
|
# @api private
|
46
|
-
|
47
|
-
|
48
|
-
klass =
|
49
|
-
if @klass.respond_to?(:call)
|
50
|
-
@klass.call(@object.class.name)
|
51
|
-
elsif @klass.is_a?(Hash)
|
52
|
-
@klass[@object.class.name.to_sym]
|
53
|
-
elsif @klass.nil?
|
54
|
-
DEFAULT_RESOURCE_INFERER.call(@object.class.name)
|
55
|
-
else
|
56
|
-
@klass
|
57
|
-
end
|
33
|
+
def serializable_class(object_class_name, klass)
|
34
|
+
klass = klass[object_class_name.to_sym] if klass.is_a?(Hash)
|
58
35
|
|
59
|
-
|
36
|
+
@lookup_cache[[object_class_name, klass.to_s]] ||=
|
37
|
+
reify_class(klass || @inferrer.call(object_class_name))
|
60
38
|
end
|
61
|
-
# rubocop:enable Metrics/MethodLength
|
62
39
|
|
63
40
|
# @api private
|
64
41
|
def reify_class(klass)
|
65
42
|
if klass.is_a?(Class)
|
66
43
|
klass
|
67
|
-
elsif klass.is_a?(String)
|
68
|
-
|
44
|
+
elsif klass.is_a?(String) || klass.is_a?(Symbol)
|
45
|
+
begin
|
46
|
+
Object.const_get(klass)
|
47
|
+
rescue NameError
|
48
|
+
raise NameError, "Undefined serializable class #{klass}"
|
49
|
+
end
|
69
50
|
else
|
70
|
-
|
71
|
-
raise
|
51
|
+
raise ArgumentError, "Invalid serializable class #{klass}"
|
72
52
|
end
|
73
53
|
end
|
74
54
|
end
|