jsonapionify 0.10.2 → 0.11.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 +8 -8
- data/jsonapionify.gemspec +1 -0
- data/lib/jsonapionify/api/action/documentation.rb +4 -6
- data/lib/jsonapionify/api/attribute.rb +8 -1
- data/lib/jsonapionify/api/relationship.rb +10 -4
- data/lib/jsonapionify/api/relationship/many.rb +4 -4
- data/lib/jsonapionify/api/relationship/one.rb +2 -2
- data/lib/jsonapionify/api/resource/builders.rb +18 -129
- data/lib/jsonapionify/api/resource/builders/attributes_builder.rb +29 -0
- data/lib/jsonapionify/api/resource/builders/base_builder.rb +17 -0
- data/lib/jsonapionify/api/resource/builders/cursor_builder.rb +33 -0
- data/lib/jsonapionify/api/resource/builders/fields_builder.rb +26 -0
- data/lib/jsonapionify/api/resource/builders/identity_helper.rb +17 -0
- data/lib/jsonapionify/api/resource/builders/relationship_builder.rb +49 -0
- data/lib/jsonapionify/api/resource/builders/relationships_builder.rb +36 -0
- data/lib/jsonapionify/api/resource/builders/resource_builder.rb +66 -0
- data/lib/jsonapionify/api/resource/builders/resource_identifer_builder.rb +25 -0
- data/lib/jsonapionify/api/resource/definitions/actions.rb +58 -89
- data/lib/jsonapionify/api/resource/definitions/attributes.rb +3 -43
- data/lib/jsonapionify/api/resource/definitions/pagination.rb +4 -4
- data/lib/jsonapionify/api/resource/definitions/relationships.rb +1 -6
- data/lib/jsonapionify/api/resource/definitions/sorting.rb +1 -14
- data/lib/jsonapionify/structure/objects/base.rb +2 -0
- data/lib/jsonapionify/version.rb +1 -1
- metadata +25 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
Y2Y5ZTgxYWU1NTJhOTMyOGY3OWQyMDQ1Y2RlZTZmZmFjYzQwOTRiZA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
Yzg1ZmQyZmViOWZhYjM1MjQxOGUwZDc3YjdjMzJkYzdmZWUzMjJlZg==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NTZiMTc4NjZhZWU5MDk4Yjg5MzhlNmNkMTU0MThjNTRmMDY4YjlmY2M5ZWNj
|
10
|
+
Nzc1Njk0MTYzMTAxOTM4ODQ4NTVhZjljY2YyYTI3NTRiM2U4MjM5YWEzMThi
|
11
|
+
OThkYmMyODc0MDA3YTcwODllZTVlZTZkZTE4OTdkOTRjNGI4ZTg=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MGYxNzUyZDcwMjdkMmY5MDUyMmQwZThjNGY2MzRiODRlZjUwMjQxZWNkYWZm
|
14
|
+
Yzc2OGZhNDEzNTU1NTg0MGFmNGZjZjg4Yzc3MDI1ZWJlYWFiOWI2YWE4OTNh
|
15
|
+
YzllY2E3YmEzYTVjNmFhYWI3YjQxZjRiMDVmNDA2NjdiYzg1OWM=
|
data/jsonapionify.gemspec
CHANGED
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.add_dependency 'mime-types'
|
37
37
|
|
38
38
|
spec.add_development_dependency 'pry'
|
39
|
+
spec.add_development_dependency 'pry-byebug'
|
39
40
|
spec.add_development_dependency 'rocco'
|
40
41
|
spec.add_development_dependency 'bundler', '~> 1.10'
|
41
42
|
spec.add_development_dependency 'rake', '~> 10.0'
|
@@ -20,17 +20,15 @@ module JSONAPIonify::Api
|
|
20
20
|
when :resource
|
21
21
|
{
|
22
22
|
'data' => resource.build_resource(
|
23
|
-
context,
|
24
|
-
resource.example_instance_for_action(name, context),
|
25
|
-
|
26
|
-
links: false,
|
27
|
-
fields: resource.fields_for_action(name, context)
|
23
|
+
context: context,
|
24
|
+
instance: resource.example_instance_for_action(name, context),
|
25
|
+
links: false
|
28
26
|
).as_json
|
29
27
|
}.to_json
|
30
28
|
when :resource_identifier
|
31
29
|
{
|
32
30
|
'data' => resource.build_resource_identifier(
|
33
|
-
resource.example_instance_for_action(name, context)
|
31
|
+
instance: resource.example_instance_for_action(name, context)
|
34
32
|
).as_json
|
35
33
|
}.to_json
|
36
34
|
when Proc
|
@@ -7,7 +7,7 @@ module JSONAPIonify::Api
|
|
7
7
|
|
8
8
|
include Documentation
|
9
9
|
using UnstrictProc
|
10
|
-
attr_reader :name, :type, :description, :read, :write, :required, :block
|
10
|
+
attr_reader :name, :type, :description, :read, :write, :required, :hidden, :block
|
11
11
|
|
12
12
|
def initialize(
|
13
13
|
name,
|
@@ -17,6 +17,7 @@ module JSONAPIonify::Api
|
|
17
17
|
write: true,
|
18
18
|
required: false,
|
19
19
|
example: nil,
|
20
|
+
hidden: false,
|
20
21
|
&block
|
21
22
|
)
|
22
23
|
unless type.is_a? JSONAPIonify::Types::BaseType
|
@@ -33,6 +34,7 @@ module JSONAPIonify::Api
|
|
33
34
|
@block = block&.freeze
|
34
35
|
@writeable_actions = write
|
35
36
|
@readable_actions = read
|
37
|
+
@hidden = !!hidden && (hidden == true || Array.wrap(hidden))
|
36
38
|
|
37
39
|
freeze
|
38
40
|
end
|
@@ -42,6 +44,11 @@ module JSONAPIonify::Api
|
|
42
44
|
self.name == other.name
|
43
45
|
end
|
44
46
|
|
47
|
+
def hidden_for_action?(action_name)
|
48
|
+
return false if hidden == false
|
49
|
+
Array.wrap(hidden).any? { |h| h == true || h.to_s == action_name.to_s }
|
50
|
+
end
|
51
|
+
|
45
52
|
def supports_read_for_action?(action_name, context)
|
46
53
|
case (setting = @readable_actions)
|
47
54
|
when TrueClass, FalseClass
|
@@ -63,15 +63,21 @@ module JSONAPIonify::Api
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
-
attr_reader :owner, :class_proc, :name, :resolve
|
66
|
+
attr_reader :owner, :class_proc, :name, :resolve, :hidden
|
67
67
|
|
68
|
-
def initialize(owner, name, resource: nil, includable: false, resolve: proc { |
|
68
|
+
def initialize(owner, name, resource: nil, includable: false, hidden: :index, resolve: proc { |n, o| o.send(n) }, &block)
|
69
69
|
@class_proc = block || proc {}
|
70
70
|
@owner = owner
|
71
71
|
@name = name
|
72
72
|
@includable = includable
|
73
73
|
@resource = resource || name
|
74
74
|
@resolve = resolve
|
75
|
+
@hidden = !!hidden && (hidden == true || Array.wrap(hidden))
|
76
|
+
end
|
77
|
+
|
78
|
+
def hidden_for_action?(action_name)
|
79
|
+
return false if hidden == false
|
80
|
+
Array.wrap(hidden).any? { |h| h == true || h.to_s == action_name.to_s }
|
75
81
|
end
|
76
82
|
|
77
83
|
def resource_class
|
@@ -82,9 +88,9 @@ module JSONAPIonify::Api
|
|
82
88
|
rel
|
83
89
|
end
|
84
90
|
|
85
|
-
rel.class_prepends.each { |prepend| class_eval
|
91
|
+
rel.class_prepends.each { |prepend| class_eval(&prepend) }
|
86
92
|
class_eval(&rel.class_proc)
|
87
|
-
rel.class_appends.each { |append| class_eval
|
93
|
+
rel.class_appends.each { |append| class_eval(&append) }
|
88
94
|
end
|
89
95
|
end
|
90
96
|
end
|
@@ -16,7 +16,7 @@ module JSONAPIonify::Api
|
|
16
16
|
prepend: 'relationships'
|
17
17
|
}
|
18
18
|
define_action(:show, 'GET', **options, &block).response status: 200 do |context|
|
19
|
-
context.response_object[:data] =
|
19
|
+
context.response_object[:data] = build_resource_identifier_collection(collection: context.collection)
|
20
20
|
context.response_object.to_json
|
21
21
|
end
|
22
22
|
end
|
@@ -30,7 +30,7 @@ module JSONAPIonify::Api
|
|
30
30
|
example_input: :resource_identifier
|
31
31
|
}
|
32
32
|
define_action(:replace, 'PATCH', **options, &block).response status: 200 do |context|
|
33
|
-
context.response_object[:data] =
|
33
|
+
context.response_object[:data] = build_resource_identifier_collection(collection: context.collection)
|
34
34
|
context.response_object.to_json
|
35
35
|
end
|
36
36
|
end
|
@@ -44,7 +44,7 @@ module JSONAPIonify::Api
|
|
44
44
|
example_input: :resource_identifier
|
45
45
|
}
|
46
46
|
define_action(:add, 'POST', **options, &block).response status: 200 do |context|
|
47
|
-
context.response_object[:data] =
|
47
|
+
context.response_object[:data] = build_resource_identifier_collection(collection: context.collection)
|
48
48
|
context.response_object.to_json
|
49
49
|
end
|
50
50
|
end
|
@@ -59,7 +59,7 @@ module JSONAPIonify::Api
|
|
59
59
|
}
|
60
60
|
options[:prepend] = 'relationships'
|
61
61
|
define_action(:remove, 'DELETE', **options, &block).response status: 200 do |context|
|
62
|
-
context.response_object[:data] =
|
62
|
+
context.response_object[:data] = build_resource_identifier_collection(collection: context.collection)
|
63
63
|
context.response_object.to_json
|
64
64
|
end
|
65
65
|
end
|
@@ -17,7 +17,7 @@ module JSONAPIonify::Api
|
|
17
17
|
prepend: 'relationships'
|
18
18
|
}
|
19
19
|
define_action(:show, 'GET', **options, &block).response status: 200 do |context|
|
20
|
-
context.response_object[:data] = build_resource_identifier(context.instance)
|
20
|
+
context.response_object[:data] = build_resource_identifier(instance: context.instance)
|
21
21
|
context.response_object.to_json
|
22
22
|
end
|
23
23
|
end
|
@@ -30,7 +30,7 @@ module JSONAPIonify::Api
|
|
30
30
|
prepend: 'relationships'
|
31
31
|
}
|
32
32
|
define_action(:replace, 'PATCH', **options, &block).response status: 200 do |context|
|
33
|
-
context.response_object[:data] = build_resource_identifier(context.instance)
|
33
|
+
context.response_object[:data] = build_resource_identifier(instance: context.instance)
|
34
34
|
context.response_object.to_json
|
35
35
|
end
|
36
36
|
end
|
@@ -1,159 +1,48 @@
|
|
1
1
|
module JSONAPIonify::Api
|
2
2
|
module Resource::Builders
|
3
3
|
extend ActiveSupport::Concern
|
4
|
+
extend JSONAPIonify::Autoload
|
5
|
+
|
6
|
+
autoload_all
|
7
|
+
|
4
8
|
FALSEY_STRINGS = JSONAPIonify::FALSEY_STRINGS
|
5
9
|
TRUTHY_STRINGS = JSONAPIonify::TRUTHY_STRINGS
|
6
|
-
Structure = JSONAPIonify::Structure
|
7
10
|
|
8
11
|
module ClassMethods
|
12
|
+
include JSONAPIonify::Structure
|
9
13
|
|
10
|
-
def build_resource(
|
11
|
-
|
12
|
-
instance,
|
13
|
-
fields:,
|
14
|
-
relationships: true,
|
15
|
-
links: true,
|
16
|
-
include_cursor: false, &block
|
17
|
-
)
|
18
|
-
example_id = generate_id
|
19
|
-
include_rel_param = context.params['include-relationships']
|
20
|
-
relationships = false if FALSEY_STRINGS.include?(include_rel_param)
|
21
|
-
return nil unless instance
|
22
|
-
resource_url = build_url(context, instance)
|
23
|
-
id = build_id(instance)
|
24
|
-
Structure::Objects::Resource.new.tap do |resource|
|
25
|
-
resource[:id] = id
|
26
|
-
resource[:type] = type
|
27
|
-
|
28
|
-
resource[:attributes] = fields[type.to_sym].each_with_object(
|
29
|
-
Structure::Objects::Attributes.new
|
30
|
-
) do |member, attributes|
|
31
|
-
attribute = self.attributes.find { |a| a.name == member.to_sym }
|
32
|
-
unless attribute.supports_read_for_action?(context.action_name, context)
|
33
|
-
error_block =
|
34
|
-
context.resource.class.error_definitions[:internal_server_error]
|
35
|
-
context.errors.evaluate(
|
36
|
-
name,
|
37
|
-
error_block: error_block,
|
38
|
-
runtime_block: proc {}
|
39
|
-
)
|
40
|
-
end
|
41
|
-
attributes[member.to_sym] = attribute.resolve(
|
42
|
-
instance, context, example_id: example_id
|
43
|
-
)
|
44
|
-
end
|
45
|
-
|
46
|
-
resource[:links] = Structure::Objects::Links.new(
|
47
|
-
self: resource_url
|
48
|
-
) if links
|
49
|
-
|
50
|
-
resource[:meta] = {
|
51
|
-
cursor: build_cursor_from_instance(context, instance)
|
52
|
-
} if include_cursor
|
53
|
-
|
54
|
-
resource[:relationships] = relationship_definitions.each_with_object(
|
55
|
-
Structure::Maps::Relationships.new
|
56
|
-
) do |rel, hash|
|
57
|
-
hash[rel.name] = build_relationship(context, instance, rel.name)
|
58
|
-
end if relationships || context.includes.present?
|
59
|
-
|
60
|
-
block.call(resource, instance) if block
|
61
|
-
end
|
14
|
+
def build_resource(**options, &block)
|
15
|
+
ResourceBuilder.build(self, **options)
|
62
16
|
end
|
63
17
|
|
64
|
-
def
|
65
|
-
Structure::Objects::ResourceIdentifier.new(
|
66
|
-
id: build_id(instance),
|
67
|
-
type: type.to_s
|
68
|
-
)
|
69
|
-
end
|
70
|
-
|
71
|
-
def build_collection(
|
72
|
-
context,
|
73
|
-
collection,
|
74
|
-
fields:,
|
75
|
-
include_cursors: false,
|
76
|
-
&block
|
77
|
-
)
|
78
|
-
include_rel_param = context.params['include-relationships']
|
79
|
-
relationships = TRUTHY_STRINGS.include? include_rel_param
|
18
|
+
def build_resource_collection(context:, collection:, include_cursors: false, &block)
|
80
19
|
collection.each_with_object(
|
81
|
-
|
20
|
+
Collections::Resources.new
|
82
21
|
) do |instance, resources|
|
83
|
-
resources << build_resource(
|
84
|
-
context,
|
85
|
-
instance,
|
86
|
-
fields: fields,
|
87
|
-
relationships: relationships,
|
88
|
-
include_cursor: include_cursors,
|
89
|
-
&block
|
90
|
-
)
|
22
|
+
resources << build_resource(context: context, instance: instance, include_cursor: include_cursors, &block)
|
91
23
|
end
|
92
24
|
end
|
93
25
|
|
94
|
-
def
|
95
|
-
|
96
|
-
sort_fields = sort_fields_from_sort_string(sort_string)
|
97
|
-
attrs_with_values = sort_fields.each_with_object({}) do |field, hash|
|
98
|
-
hash[field.name] = instance.send(field.name)
|
99
|
-
end
|
100
|
-
Base64.urlsafe_encode64(JSON.dump(
|
101
|
-
{
|
102
|
-
t: type,
|
103
|
-
s: sort_string,
|
104
|
-
a: attrs_with_values
|
105
|
-
}
|
106
|
-
))
|
26
|
+
def build_resource_identifier(instance:)
|
27
|
+
ResourceIdentifierBuilder.build(self, instance: instance)
|
107
28
|
end
|
108
29
|
|
109
|
-
def
|
30
|
+
def build_resource_identifier_collection(collection:)
|
110
31
|
collection.each_with_object(
|
111
|
-
|
32
|
+
Collections::ResourceIdentifiers.new
|
112
33
|
) do |instance, resource_identifiers|
|
113
|
-
resource_identifiers << build_resource_identifier(instance)
|
34
|
+
resource_identifiers << build_resource_identifier(instance: instance)
|
114
35
|
end
|
115
36
|
end
|
116
37
|
|
117
|
-
def
|
118
|
-
|
119
|
-
relationship = self.relationship(name)
|
120
|
-
JSONAPIonify::Structure::Objects::Relationship.new.tap do |rel|
|
121
|
-
rel[:links] = relationship.build_links(resource_url) if links
|
122
|
-
if data || context.includes.present?
|
123
|
-
rel[:data] =
|
124
|
-
if relationship.rel.is_a? Relationship::Many
|
125
|
-
instance.send(name).map do |child|
|
126
|
-
relationship.build_resource_identifier(child)
|
127
|
-
end
|
128
|
-
elsif relationship.rel.is_a? Relationship::One
|
129
|
-
value = instance.send(name)
|
130
|
-
relationship.build_resource_identifier value if value
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
38
|
+
def build_cursor(**options)
|
39
|
+
CursorBuilder.build(self, **options)
|
134
40
|
end
|
135
41
|
|
136
|
-
def build_url(context, instance = nil)
|
137
|
-
URI.parse(context.request.root_url).tap do |uri|
|
138
|
-
uri.path =
|
139
|
-
if instance
|
140
|
-
File.join(uri.path, type, build_id(instance))
|
141
|
-
else
|
142
|
-
File.join(context.request.root_url, type)
|
143
|
-
end
|
144
|
-
sticky_params = self.sticky_params(context.params)
|
145
|
-
uri.query = sticky_params.to_param if sticky_params.present?
|
146
|
-
end.to_s
|
147
|
-
end
|
148
|
-
|
149
|
-
def build_id(instance)
|
150
|
-
instance.send(id_attribute).to_s
|
151
|
-
end
|
152
42
|
end
|
153
43
|
|
44
|
+
delegated_methods = ClassMethods.instance_methods - instance_methods
|
154
45
|
included do
|
155
|
-
delegated_methods = ClassMethods.instance_methods -
|
156
|
-
JSONAPIonify::Api::Resource::Builders.instance_methods
|
157
46
|
delegate(*delegated_methods, to: :class)
|
158
47
|
end
|
159
48
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module JSONAPIonify::Api
|
2
|
+
module Resource::Builders
|
3
|
+
class AttributesBuilder < FieldsBuilder
|
4
|
+
|
5
|
+
delegate :attributes, to: :resource, prefix: true
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def build_default
|
10
|
+
resource_attributes.each_with_object(Objects::Attributes.new) do |attribute, attrs|
|
11
|
+
if attribute.supports_read_for_action?(action_name, context) && !attribute.hidden_for_action?(action_name)
|
12
|
+
attrs[attribute.name] = attribute.resolve(instance, context, example_id: example_id)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def build_sparce
|
18
|
+
resource_fields.each_with_object(Objects::Attributes.new) do |field, attrs|
|
19
|
+
field = field.to_sym
|
20
|
+
attribute = resource_attributes.find { |attr| attr.name == field }
|
21
|
+
if attribute&.supports_read_for_action?(action_name, context)
|
22
|
+
attrs[field] = attribute.resolve(instance, context, example_id: example_id)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module JSONAPIonify::Api
|
2
|
+
module Resource::Builders
|
3
|
+
class BaseBuilder
|
4
|
+
include JSONAPIonify::Structure
|
5
|
+
|
6
|
+
def self.build(*args, &block)
|
7
|
+
new(*args, &block).build
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :resource
|
11
|
+
|
12
|
+
def initialize(resource)
|
13
|
+
@resource = resource
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module JSONAPIonify::Api
|
2
|
+
module Resource::Builders
|
3
|
+
class CursorBuilder < BaseBuilder
|
4
|
+
|
5
|
+
attr_reader :context, :instance
|
6
|
+
delegate :type, to: :resource, prefix: true
|
7
|
+
delegate :sort_fields_from_sort_string, to: :resource
|
8
|
+
delegate :request, :params, to: :context
|
9
|
+
|
10
|
+
def initialize(resource, instance:, context:)
|
11
|
+
super(resource)
|
12
|
+
@instance = instance
|
13
|
+
@context = context
|
14
|
+
end
|
15
|
+
|
16
|
+
def build
|
17
|
+
sort_string = params['sort']
|
18
|
+
sort_fields = sort_fields_from_sort_string(sort_string)
|
19
|
+
attrs_with_values = sort_fields.each_with_object({}) do |field, hash|
|
20
|
+
hash[field.name] = instance.send(field.name)
|
21
|
+
end
|
22
|
+
Base64.urlsafe_encode64(JSON.dump(
|
23
|
+
{
|
24
|
+
t: resource_type,
|
25
|
+
s: sort_string,
|
26
|
+
a: attrs_with_values
|
27
|
+
}
|
28
|
+
))
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module JSONAPIonify::Api
|
2
|
+
module Resource::Builders
|
3
|
+
class FieldsBuilder < BaseBuilder
|
4
|
+
|
5
|
+
attr_reader :context, :instance, :example_id
|
6
|
+
delegate :action_name, :fields, to: :context
|
7
|
+
delegate :type, to: :resource, prefix: true
|
8
|
+
|
9
|
+
def initialize(resource, instance:, context:, example_id:)
|
10
|
+
super(resource)
|
11
|
+
@instance = instance
|
12
|
+
@context = context
|
13
|
+
@example_id = example_id
|
14
|
+
end
|
15
|
+
|
16
|
+
def resource_fields
|
17
|
+
fields[resource_type] || {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def build
|
21
|
+
fields.nil? ? build_default : build_sparce
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module JSONAPIonify::Api
|
2
|
+
module Resource::Builders
|
3
|
+
module IdentityHelper
|
4
|
+
def build_url
|
5
|
+
URI.parse(request.root_url).tap do |uri|
|
6
|
+
uri.path = File.join uri.path, resource_type, build_id
|
7
|
+
sticky_params = resource.sticky_params(context.params)
|
8
|
+
uri.query = sticky_params.to_param if sticky_params.present?
|
9
|
+
end.to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
def build_id
|
13
|
+
instance.send(resource.id_attribute).to_s
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module JSONAPIonify::Api
|
2
|
+
module Resource::Builders
|
3
|
+
class RelationshipBuilder < BaseBuilder
|
4
|
+
include IdentityHelper
|
5
|
+
delegate :type, to: :resource, prefix: true
|
6
|
+
delegate :request, :includes, to: :context
|
7
|
+
|
8
|
+
attr_reader :relationship, :related_resource, :context, :instance
|
9
|
+
|
10
|
+
def initialize(resource, relationship:, context:, instance:)
|
11
|
+
super(resource)
|
12
|
+
@relationship = relationship
|
13
|
+
@context = context
|
14
|
+
@instance = instance
|
15
|
+
@related_resource = resource.relationship(relationship.name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def build
|
19
|
+
Objects::Relationship.new.tap do |rel|
|
20
|
+
rel[:links] = related_resource.build_links(build_url)
|
21
|
+
rel[:data] = build_data if includes.present?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def build_data
|
28
|
+
case relationship
|
29
|
+
when Relationship::Many
|
30
|
+
resolution.map do |child|
|
31
|
+
relationship.build_resource_identifier(child)
|
32
|
+
end
|
33
|
+
when Relationship::One
|
34
|
+
relationship.build_resource_identifier resolution
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def resolution
|
39
|
+
instance.instance_exec(
|
40
|
+
relationship.name,
|
41
|
+
instance,
|
42
|
+
context,
|
43
|
+
&relationship.resolve
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module JSONAPIonify::Api
|
2
|
+
module Resource::Builders
|
3
|
+
class RelationshipsBuilder < FieldsBuilder
|
4
|
+
|
5
|
+
delegate :relationships, to: :resource, prefix: true
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def build_default
|
10
|
+
resource_relationships.each_with_object(Objects::Relationships.new) do |relationship, attrs|
|
11
|
+
unless relationship.hidden_for_action?(action_name)
|
12
|
+
attrs[relationship.name] = build_relationship(relationship)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def build_sparce
|
18
|
+
resource_fields.each_with_object(Objects::Relationships.new) do |field, attrs|
|
19
|
+
field = field.to_sym
|
20
|
+
relationship = resource_relationships.find { |rel| rel.name == field }
|
21
|
+
attrs[field] = build_relationship(relationship) if relationship
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def build_relationship(relationship)
|
26
|
+
RelationshipBuilder.build(
|
27
|
+
resource,
|
28
|
+
relationship: relationship,
|
29
|
+
context: context,
|
30
|
+
instance: instance
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module JSONAPIonify::Api
|
2
|
+
module Resource::Builders
|
3
|
+
class ResourceBuilder < ResourceIdentifierBuilder
|
4
|
+
delegate :attributes, :relationships, :type, :relationship, to: :resource, prefix: true
|
5
|
+
delegate :params, :includes, :fields, :action_name, :request, to: :context
|
6
|
+
|
7
|
+
attr_reader :context, :links, :include_cursor, :block
|
8
|
+
|
9
|
+
def initialize(resource, context:, links: true, include_cursor: false, **opts, &block)
|
10
|
+
super(resource, **opts)
|
11
|
+
@context = context
|
12
|
+
@links = links
|
13
|
+
@include_cursor = include_cursor
|
14
|
+
@block = block
|
15
|
+
end
|
16
|
+
|
17
|
+
def build
|
18
|
+
return nil unless instance
|
19
|
+
Objects::Resource.new.tap do |resource|
|
20
|
+
resource[:type] = resource_type
|
21
|
+
(id = build_id) && resource[:id] = id
|
22
|
+
(attributes = build_attributes).present? && resource[:attributes] = attributes
|
23
|
+
(relationships = build_relationships).present? && resource[:relationships] = relationships
|
24
|
+
(links = build_links).present? && resource[:links] = links
|
25
|
+
(meta = build_meta).present? && resource[:meta] = meta
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def build_attributes
|
32
|
+
AttributesBuilder.build(
|
33
|
+
resource,
|
34
|
+
instance: instance,
|
35
|
+
context: context,
|
36
|
+
example_id: example_id
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def build_relationships
|
41
|
+
RelationshipsBuilder.build(
|
42
|
+
resource,
|
43
|
+
instance: instance,
|
44
|
+
context: context,
|
45
|
+
example_id: example_id
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
def build_links
|
50
|
+
return unless links
|
51
|
+
Objects::Links.new(self: build_url)
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_meta
|
55
|
+
Objects::Meta.new.tap do |meta|
|
56
|
+
(cursor = build_cursor).present? && meta[:cursor] = cursor
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def build_cursor
|
61
|
+
return unless include_cursor
|
62
|
+
CursorBuilder.build(resource, instance: instance, context: context)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module JSONAPIonify::Api
|
2
|
+
module Resource::Builders
|
3
|
+
class ResourceIdentifierBuilder < BaseBuilder
|
4
|
+
include IdentityHelper
|
5
|
+
|
6
|
+
attr_reader :example_id, :instance
|
7
|
+
delegate :type, to: :resource, prefix: true
|
8
|
+
|
9
|
+
def initialize(resource, instance:)
|
10
|
+
super(resource)
|
11
|
+
@instance = instance
|
12
|
+
@example_id = resource.generate_id
|
13
|
+
end
|
14
|
+
|
15
|
+
def build
|
16
|
+
return nil unless instance
|
17
|
+
Objects::ResourceIdentifier.new.tap do |resource|
|
18
|
+
resource[:type] = resource_type
|
19
|
+
(id = build_id) && resource[:id] = id
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -5,108 +5,82 @@ module JSONAPIonify::Api
|
|
5
5
|
module Resource::Definitions::Actions
|
6
6
|
ActionNotFound = Class.new StandardError
|
7
7
|
|
8
|
+
INSTANCE_RESPONSE = proc do |context|
|
9
|
+
builder =
|
10
|
+
context.respond_to?(:builder) ? context.builder : nil
|
11
|
+
context.response_object[:data] =
|
12
|
+
build_resource(
|
13
|
+
context: context,
|
14
|
+
instance: context.instance,
|
15
|
+
&builder
|
16
|
+
)
|
17
|
+
context.response_object.to_json
|
18
|
+
end
|
19
|
+
|
20
|
+
COLLECTION_RESPONSE = proc do |context|
|
21
|
+
builder = context.respond_to?(:builder) ? context.builder : nil
|
22
|
+
context.response_object[:data] = build_resource_collection(
|
23
|
+
context: context,
|
24
|
+
collection: context.response_collection,
|
25
|
+
include_cursors: (context.links.keys & [:first, :last, :next, :prev]).present?,
|
26
|
+
&builder
|
27
|
+
)
|
28
|
+
context.response_object.to_json
|
29
|
+
end
|
30
|
+
|
8
31
|
def self.extended(klass)
|
9
32
|
klass.class_eval do
|
10
33
|
extend JSONAPIonify::InheritedAttributes
|
11
34
|
include JSONAPIonify::Callbacks
|
12
35
|
define_callbacks(
|
13
|
-
:request,
|
14
|
-
:
|
15
|
-
:
|
16
|
-
:
|
17
|
-
:
|
18
|
-
:
|
19
|
-
:
|
20
|
-
:
|
21
|
-
:
|
36
|
+
:request,
|
37
|
+
:exception,
|
38
|
+
:response,
|
39
|
+
:list, :commit_list,
|
40
|
+
:create, :commit_create,
|
41
|
+
:read, :commit_read,
|
42
|
+
:update, :commit_update,
|
43
|
+
:delete, :commit_delete,
|
44
|
+
:show, :commit_show,
|
45
|
+
:add, :commit_add,
|
46
|
+
:remove, :commit_remove,
|
22
47
|
:replace, :commit_replace
|
23
48
|
)
|
24
49
|
inherited_array_attribute :action_definitions
|
25
50
|
end
|
26
51
|
end
|
27
52
|
|
28
|
-
def list(
|
29
|
-
options
|
30
|
-
|
31
|
-
only_associated: only_associated,
|
32
|
-
callbacks: callbacks,
|
33
|
-
cacheable: true
|
34
|
-
}
|
35
|
-
define_action(:list, 'GET', **options, &block).tap do |action|
|
36
|
-
action.response status: 200 do |context|
|
37
|
-
builder = context.respond_to?(:builder) ? context.builder : nil
|
38
|
-
context.response_object[:data] = build_collection(
|
39
|
-
context,
|
40
|
-
context.response_collection,
|
41
|
-
fields: context.fields,
|
42
|
-
include_cursors: (context.links.keys & [:first, :last, :next, :prev]).present?,
|
43
|
-
&builder
|
44
|
-
)
|
45
|
-
context.response_object.to_json
|
46
|
-
end
|
53
|
+
def list(**options, &block)
|
54
|
+
define_action(:list, 'GET', **options, cacheable: true, &block).tap do |action|
|
55
|
+
action.response(status: 200, &COLLECTION_RESPONSE)
|
47
56
|
end
|
48
57
|
end
|
49
58
|
|
50
|
-
def create(
|
51
|
-
options
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
example_input: :resource
|
57
|
-
}
|
58
|
-
define_action(:create, 'POST', **options, &block).tap do |action|
|
59
|
-
action.response status: 201 do |context|
|
60
|
-
builder = context.respond_to?(:builder) ? context.builder : nil
|
61
|
-
context.response_object[:data] = build_resource(context, context.instance, fields: context.fields, &builder)
|
62
|
-
response_headers['Location'] = build_url(context, context.instance)
|
63
|
-
context.response_object.to_json
|
59
|
+
def create(**options, &block)
|
60
|
+
define_action(:create, 'POST', '', **options, cacheable: false, example_input: :resource, &block).tap do |action|
|
61
|
+
action.response(status: 201) do |context|
|
62
|
+
instance_exec(context, &INSTANCE_RESPONSE).tap do
|
63
|
+
response_headers['Location'] = context.response_object[:data][:links][:self]
|
64
|
+
end
|
64
65
|
end
|
65
66
|
end
|
66
67
|
end
|
67
68
|
|
68
|
-
def read(
|
69
|
-
options
|
70
|
-
|
71
|
-
only_associated: only_associated,
|
72
|
-
callbacks: callbacks,
|
73
|
-
cacheable: true
|
74
|
-
}
|
75
|
-
define_action(:read, 'GET', '/:id', **options, &block).tap do |action|
|
76
|
-
action.response status: 200 do |context|
|
77
|
-
builder = context.respond_to?(:builder) ? context.builder : nil
|
78
|
-
context.response_object[:data] = build_resource(context, context.instance, fields: context.fields, &builder)
|
79
|
-
context.response_object.to_json
|
80
|
-
end
|
69
|
+
def read(**options, &block)
|
70
|
+
define_action(:read, 'GET', '/:id', **options, cacheable: true, &block).tap do |action|
|
71
|
+
action.response(status: 200, &INSTANCE_RESPONSE)
|
81
72
|
end
|
82
73
|
end
|
83
74
|
|
84
|
-
def update(
|
85
|
-
options
|
86
|
-
|
87
|
-
only_associated: only_associated,
|
88
|
-
callbacks: callbacks,
|
89
|
-
cacheable: false,
|
90
|
-
example_input: :resource
|
91
|
-
}
|
92
|
-
define_action(:update, 'PATCH', '/:id', **options, &block).tap do |action|
|
93
|
-
action.response status: 200 do |context|
|
94
|
-
builder = context.respond_to?(:builder) ? context.builder : nil
|
95
|
-
context.response_object[:data] = build_resource(context, context.instance, fields: context.fields, &builder)
|
96
|
-
context.response_object.to_json
|
97
|
-
end
|
75
|
+
def update(**options, &block)
|
76
|
+
define_action(:update, 'PATCH', '/:id', **options, cacheable: false, example_input: :resource, &block).tap do |action|
|
77
|
+
action.response(status: 200, &INSTANCE_RESPONSE)
|
98
78
|
end
|
99
79
|
end
|
100
80
|
|
101
|
-
def delete(
|
102
|
-
options
|
103
|
-
|
104
|
-
only_associated: only_associated,
|
105
|
-
callbacks: callbacks,
|
106
|
-
cacheable: false
|
107
|
-
}
|
108
|
-
define_action(:delete, 'DELETE', '/:id', **options, &block).tap do |action|
|
109
|
-
action.response status: 204
|
81
|
+
def delete(**options, &block)
|
82
|
+
define_action(:delete, 'DELETE', '/:id', **options, cacheable: false, &block).tap do |action|
|
83
|
+
action.response(status: 204)
|
110
84
|
end
|
111
85
|
end
|
112
86
|
|
@@ -120,17 +94,12 @@ module JSONAPIonify::Api
|
|
120
94
|
end
|
121
95
|
end
|
122
96
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
def before(*action_names, &block)
|
131
|
-
return before_request &block if action_names.blank?
|
132
|
-
action_names.each do |action_name|
|
133
|
-
send("before_#{action_name}", &block)
|
97
|
+
%i{before after}.each do |cb|
|
98
|
+
define_method(cb) do |*action_names, &block|
|
99
|
+
return send(:"#{cb}_request", &block) if action_names.blank?
|
100
|
+
action_names.each do |action_name|
|
101
|
+
send("#{cb}_#{action_name}", &block)
|
102
|
+
end
|
134
103
|
end
|
135
104
|
end
|
136
105
|
|
@@ -9,31 +9,9 @@ module JSONAPIonify::Api
|
|
9
9
|
delegate :id_attribute, :attributes, to: :class
|
10
10
|
|
11
11
|
context(:fields, readonly: true) do |context|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
input_fields.each_with_object(
|
16
|
-
actionable_fields
|
17
|
-
) do |(type, fields), field_map|
|
18
|
-
type_sym = type.to_sym
|
19
|
-
field_symbols = fields.to_s.split(',').map(&:to_sym)
|
20
|
-
field_map[type_sym] =
|
21
|
-
field_symbols.each_with_object([]) do |field, field_list|
|
22
|
-
type_attributes = self.class.api.resource(type_sym).attributes
|
23
|
-
attribute = type_attributes.find do |attribute|
|
24
|
-
attribute.name == field &&
|
25
|
-
attribute.supports_read_for_action?(context.action_name, context)
|
26
|
-
end
|
27
|
-
if attribute
|
28
|
-
field_list << attribute.name
|
29
|
-
else
|
30
|
-
error(:field_not_permitted, type, field)
|
31
|
-
should_error = true
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end.tap do
|
35
|
-
halt if should_error
|
36
|
-
end
|
12
|
+
context.params['fields']&.map do |type, fields|
|
13
|
+
[type, fields.split(',')]
|
14
|
+
end&.to_h
|
37
15
|
end
|
38
16
|
end
|
39
17
|
end
|
@@ -57,10 +35,6 @@ module JSONAPIonify::Api
|
|
57
35
|
attributes.delete_if { |attr| attr.name == name.to_sym }
|
58
36
|
end
|
59
37
|
|
60
|
-
def fields
|
61
|
-
attributes.select(&:read?).map(&:name)
|
62
|
-
end
|
63
|
-
|
64
38
|
def builder(&block)
|
65
39
|
context :builder, readonly: true, persisted: true do |context|
|
66
40
|
proc do |resource, instance|
|
@@ -69,19 +43,5 @@ module JSONAPIonify::Api
|
|
69
43
|
end
|
70
44
|
end
|
71
45
|
|
72
|
-
def field_valid?(name)
|
73
|
-
fields.include? name.to_sym
|
74
|
-
end
|
75
|
-
|
76
|
-
def fields_for_action(action, context)
|
77
|
-
api.fields.each_with_object({}) do |(type, attrs), fields|
|
78
|
-
fields[type] = attrs.select do |attr|
|
79
|
-
api.resource(type).attributes.find do |type_attr|
|
80
|
-
type_attr.name == attr
|
81
|
-
end.supports_read_for_action? action, context
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
46
|
end
|
87
47
|
end
|
@@ -51,8 +51,8 @@ module JSONAPIonify::Api
|
|
51
51
|
|
52
52
|
links.first first: size
|
53
53
|
links.last last: size
|
54
|
-
links.prev before:
|
55
|
-
links.next after:
|
54
|
+
links.prev before: build_cursor(context: context, instance: slice.first), last: size unless !slice.first || slice.first == collection.first
|
55
|
+
links.next after: build_cursor(context: context, instance: slice.last), first: size unless !slice.last || slice.last == collection.last
|
56
56
|
|
57
57
|
slice
|
58
58
|
end
|
@@ -82,8 +82,8 @@ module JSONAPIonify::Api
|
|
82
82
|
|
83
83
|
links.first first: size
|
84
84
|
links.last last: size
|
85
|
-
links.prev before:
|
86
|
-
links.next after:
|
85
|
+
links.prev before: build_cursor(context: context, instance: slice.first), last: size unless !slice.first || slice.first == collection.first
|
86
|
+
links.next after: build_cursor(context: context, instance: slice.last), first: size unless !slice.last || slice.last == collection.last
|
87
87
|
|
88
88
|
slice
|
89
89
|
end
|
@@ -25,14 +25,9 @@ module JSONAPIonify::Api
|
|
25
25
|
def define_relationship_counter(rel_name, name, include: true)
|
26
26
|
attribute name.to_sym, types.Integer, "The number of #{rel_name}.", write: false do |_, instance, context|
|
27
27
|
rel = context.resource.class.relationship(rel_name)
|
28
|
-
blank_fields = context.fields.map { |k, _| [k, {}] }.to_h
|
29
28
|
rel_context = rel.new(
|
30
29
|
request: context.request,
|
31
|
-
context_overrides: {
|
32
|
-
owner: instance,
|
33
|
-
fields: blank_fields,
|
34
|
-
params: {}
|
35
|
-
}
|
30
|
+
context_overrides: { owner: instance, fields: {}, params: {} }
|
36
31
|
).exec { |c| c }
|
37
32
|
count = rel_context.collection.uniq.count
|
38
33
|
case count
|
@@ -23,20 +23,7 @@ module JSONAPIonify::Api
|
|
23
23
|
end
|
24
24
|
|
25
25
|
context(:sort_params, readonly: true, persisted: true) do |context|
|
26
|
-
sort_fields_from_sort_string(context.params['sort'])
|
27
|
-
should_error = false
|
28
|
-
fields.each do |field|
|
29
|
-
unless self.class.field_valid?(field.name) || field.name == id_attribute
|
30
|
-
should_error = true
|
31
|
-
type = self.class.type
|
32
|
-
error :sort_parameter_invalid do
|
33
|
-
detail "resource `#{type}` does not have field: #{field.name}"
|
34
|
-
end
|
35
|
-
next
|
36
|
-
end
|
37
|
-
end
|
38
|
-
halt if should_error
|
39
|
-
end
|
26
|
+
sort_fields_from_sort_string(context.params['sort'])
|
40
27
|
end
|
41
28
|
|
42
29
|
define_sorting_strategy('Object') do |collection|
|
data/lib/jsonapionify/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsonapionify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Waldrip
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-06-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -206,6 +206,20 @@ dependencies:
|
|
206
206
|
- - ! '>='
|
207
207
|
- !ruby/object:Gem::Version
|
208
208
|
version: '0'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: pry-byebug
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ! '>='
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ! '>='
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
209
223
|
- !ruby/object:Gem::Dependency
|
210
224
|
name: rocco
|
211
225
|
requirement: !ruby/object:Gem::Requirement
|
@@ -527,6 +541,15 @@ files:
|
|
527
541
|
- lib/jsonapionify/api/relationship/one.rb
|
528
542
|
- lib/jsonapionify/api/resource.rb
|
529
543
|
- lib/jsonapionify/api/resource/builders.rb
|
544
|
+
- lib/jsonapionify/api/resource/builders/attributes_builder.rb
|
545
|
+
- lib/jsonapionify/api/resource/builders/base_builder.rb
|
546
|
+
- lib/jsonapionify/api/resource/builders/cursor_builder.rb
|
547
|
+
- lib/jsonapionify/api/resource/builders/fields_builder.rb
|
548
|
+
- lib/jsonapionify/api/resource/builders/identity_helper.rb
|
549
|
+
- lib/jsonapionify/api/resource/builders/relationship_builder.rb
|
550
|
+
- lib/jsonapionify/api/resource/builders/relationships_builder.rb
|
551
|
+
- lib/jsonapionify/api/resource/builders/resource_builder.rb
|
552
|
+
- lib/jsonapionify/api/resource/builders/resource_identifer_builder.rb
|
530
553
|
- lib/jsonapionify/api/resource/caching.rb
|
531
554
|
- lib/jsonapionify/api/resource/caller.rb
|
532
555
|
- lib/jsonapionify/api/resource/class_methods.rb
|