jsonapionify 0.11.11 → 0.12.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/README.md +3 -0
- data/examples/example_api.rb +80 -0
- data/jsonapionify.gemspec +1 -0
- data/lib/jsonapionify/api/action/documentation.rb +2 -2
- data/lib/jsonapionify/api/attribute.rb +3 -2
- data/lib/jsonapionify/api/context.rb +8 -3
- data/lib/jsonapionify/api/context_delegate.rb +6 -0
- data/lib/jsonapionify/api/relationship.rb +5 -10
- data/lib/jsonapionify/api/relationship/many.rb +46 -14
- data/lib/jsonapionify/api/relationship/one.rb +31 -8
- data/lib/jsonapionify/api/resource.rb +3 -6
- data/lib/jsonapionify/api/resource/builders/fields_builder.rb +2 -2
- data/lib/jsonapionify/api/resource/builders/relationship_builder.rb +3 -2
- data/lib/jsonapionify/api/resource/builders/relationships_builder.rb +3 -2
- data/lib/jsonapionify/api/resource/caching.rb +1 -4
- data/lib/jsonapionify/api/resource/callbacks.rb +60 -0
- data/lib/jsonapionify/api/resource/caller.rb +8 -7
- data/lib/jsonapionify/api/resource/defaults/actions.rb +15 -12
- data/lib/jsonapionify/api/resource/defaults/hooks.rb +10 -57
- data/lib/jsonapionify/api/resource/defaults/options.rb +3 -8
- data/lib/jsonapionify/api/resource/defaults/params.rb +0 -2
- data/lib/jsonapionify/api/resource/defaults/request_contexts.rb +24 -29
- data/lib/jsonapionify/api/resource/defaults/response_contexts.rb +11 -18
- data/lib/jsonapionify/api/resource/definitions/actions.rb +38 -22
- data/lib/jsonapionify/api/resource/definitions/attributes.rb +2 -2
- data/lib/jsonapionify/api/resource/definitions/helpers.rb +1 -1
- data/lib/jsonapionify/api/resource/definitions/includes.rb +16 -0
- data/lib/jsonapionify/api/resource/definitions/pagination.rb +7 -11
- data/lib/jsonapionify/api/resource/definitions/params.rb +19 -26
- data/lib/jsonapionify/api/resource/definitions/relationships.rb +3 -3
- data/lib/jsonapionify/api/resource/definitions/request_headers.rb +14 -16
- data/lib/jsonapionify/api/resource/definitions/response_headers.rb +2 -1
- data/lib/jsonapionify/api/resource/definitions/scopes.rb +17 -6
- data/lib/jsonapionify/api/resource/definitions/sorting.rb +8 -7
- data/lib/jsonapionify/api/resource/error_handling.rb +3 -2
- data/lib/jsonapionify/api/resource/exec.rb +3 -1
- data/lib/jsonapionify/api/resource/includer.rb +20 -51
- data/lib/jsonapionify/api/response.rb +3 -1
- data/lib/jsonapionify/callbacks.rb +10 -7
- data/lib/jsonapionify/destructured_proc.rb +27 -0
- data/lib/jsonapionify/structure/collections/base.rb +20 -8
- data/lib/jsonapionify/structure/objects/base.rb +11 -2
- data/lib/jsonapionify/structure/objects/resource_identifier.rb +9 -14
- data/lib/jsonapionify/version.rb +1 -1
- metadata +20 -2
@@ -14,11 +14,11 @@ module JSONAPIonify::Api
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def resource_fields
|
17
|
-
fields[resource_type]
|
17
|
+
fields && fields[resource_type]
|
18
18
|
end
|
19
19
|
|
20
20
|
def build
|
21
|
-
|
21
|
+
resource_fields.nil? ? build_default : build_sparce
|
22
22
|
end
|
23
23
|
|
24
24
|
end
|
@@ -25,13 +25,14 @@ module JSONAPIonify::Api
|
|
25
25
|
private
|
26
26
|
|
27
27
|
def build_data
|
28
|
+
rel_resource = resource.relationship(relationship.name)
|
28
29
|
case relationship
|
29
30
|
when Relationship::Many
|
30
31
|
resolution.map do |child|
|
31
|
-
|
32
|
+
rel_resource.build_resource_identifier(instance: child)
|
32
33
|
end
|
33
34
|
when Relationship::One
|
34
|
-
|
35
|
+
rel_resource.build_resource_identifier instance: resolution
|
35
36
|
end
|
36
37
|
end
|
37
38
|
|
@@ -3,19 +3,20 @@ module JSONAPIonify::Api
|
|
3
3
|
class RelationshipsBuilder < FieldsBuilder
|
4
4
|
|
5
5
|
delegate :relationships, to: :resource, prefix: true
|
6
|
+
delegate :includes, to: :context
|
6
7
|
|
7
8
|
private
|
8
9
|
|
9
10
|
def build_default
|
10
11
|
resource_relationships.each_with_object(Objects::Relationships.new) do |relationship, attrs|
|
11
|
-
|
12
|
+
if !relationship.hidden_for_action?(action_name) || includes.keys.include?(relationship.name.to_s)
|
12
13
|
attrs[relationship.name] = build_relationship(relationship)
|
13
14
|
end
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
17
18
|
def build_sparce
|
18
|
-
resource_fields.each_with_object(Objects::Relationships.new) do |field, attrs|
|
19
|
+
(resource_fields + includes.keys).each_with_object(Objects::Relationships.new) do |field, attrs|
|
19
20
|
field = field.to_sym
|
20
21
|
relationship = resource_relationships.find { |rel| rel.name == field }
|
21
22
|
attrs[field] = build_relationship(relationship) if relationship
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module JSONAPIonify::Api
|
4
|
+
module Resource::Callbacks
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
using JSONAPIonify::DestructuredProc
|
7
|
+
included do
|
8
|
+
|
9
|
+
def self.define_callbacks(*names)
|
10
|
+
names.each do |name|
|
11
|
+
chains = {
|
12
|
+
main: "__#{name}_callback_chain",
|
13
|
+
before: "__#{name}_before_callback_chain",
|
14
|
+
after: "__#{name}_after_callback_chain"
|
15
|
+
}
|
16
|
+
define_method chains[:main] do |*args, &block|
|
17
|
+
block ||= proc {}
|
18
|
+
if send(chains[:before], *args) != false
|
19
|
+
value = begin
|
20
|
+
instance_exec(*args, @__context, &block&.destructure)
|
21
|
+
rescue => e
|
22
|
+
e.backtrace.unshift block.source_location.join(':') + ":in `(callback)`"
|
23
|
+
raise e
|
24
|
+
end
|
25
|
+
value if send(chains[:after], *args) != false
|
26
|
+
end
|
27
|
+
end unless method_defined? chains[:main]
|
28
|
+
|
29
|
+
# Define before and after chains
|
30
|
+
%i{after before}.each do |timing|
|
31
|
+
define_method chains[timing] { |*| } unless method_defined? chains[timing]
|
32
|
+
callback_name = "#{timing}_#{name}"
|
33
|
+
define_singleton_method callback_name do |sym = nil, &outer_block|
|
34
|
+
outer_block = (outer_block || sym).to_proc
|
35
|
+
prev_chain = instance_method(chains[timing])
|
36
|
+
define_method chains[timing] do |*args, &block|
|
37
|
+
begin
|
38
|
+
if prev_chain.bind(self).call(*args, @__context, &block&.destructure) != false
|
39
|
+
instance_exec(*args, @__context, &outer_block&.destructure)
|
40
|
+
end
|
41
|
+
rescue => e
|
42
|
+
e.backtrace.unshift outer_block.source_location.join(':') + ":in `(callback) #{callback_name}`"
|
43
|
+
raise e
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private(*chains.values)
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def run_callbacks(name, *args, &block)
|
56
|
+
send("__#{name}_callback_chain", *args, &block)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -1,14 +1,16 @@
|
|
1
1
|
module JSONAPIonify::Api
|
2
2
|
module Resource::Caller
|
3
|
+
using JSONAPIonify::DestructuredProc
|
4
|
+
|
3
5
|
def call
|
4
6
|
do_respond = proc { __respond }
|
5
7
|
do_request = proc { __request }
|
6
|
-
response = @callbacks ? run_callbacks(:request,
|
8
|
+
response = @callbacks ? run_callbacks(:request, &do_request) : do_request.call
|
7
9
|
rescue Errors::RequestError => e
|
8
10
|
raise e unless errors.present?
|
9
11
|
response = error_response
|
10
12
|
rescue Errors::CacheHit
|
11
|
-
JSONAPIonify.logger.
|
13
|
+
JSONAPIonify.logger.debug "Cache Hit: #{@cache_options[:key]}"
|
12
14
|
response = self.class.cache_store.read @cache_options[:key]
|
13
15
|
rescue Exception => exception
|
14
16
|
response = rescued_response exception, @__context, do_respond
|
@@ -25,21 +27,20 @@ module JSONAPIonify::Api
|
|
25
27
|
private
|
26
28
|
|
27
29
|
def __commit
|
28
|
-
instance_exec(@__context, &action.block)
|
29
|
-
halt if errors.present?
|
30
|
+
instance_exec(@__context, &action.block.destructure)
|
30
31
|
end
|
31
32
|
|
32
33
|
def __commit_and_respond
|
33
34
|
do_respond = proc { __respond }
|
34
35
|
do_commit = proc { __commit }
|
35
36
|
halt if errors.present?
|
36
|
-
action.name && @callbacks ? run_callbacks("commit_#{action.name}",
|
37
|
-
@callbacks ? run_callbacks(:response,
|
37
|
+
action.name && @callbacks ? run_callbacks("commit_#{action.name}", &do_commit) : do_commit.call
|
38
|
+
@callbacks ? run_callbacks(:response, &do_respond) : do_respond.call
|
38
39
|
end
|
39
40
|
|
40
41
|
def __request
|
41
42
|
do_commit_and_respond = proc { __commit_and_respond }
|
42
|
-
action.name && @callbacks ? run_callbacks(action.name,
|
43
|
+
action.name && @callbacks ? run_callbacks(action.name, &do_commit_and_respond) : do_commit_and_respond.call
|
43
44
|
end
|
44
45
|
|
45
46
|
def __respond(**options)
|
@@ -3,20 +3,21 @@ module JSONAPIonify::Api
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
6
|
-
context :path_actions, readonly: true, persisted: true do |
|
7
|
-
self.class.path_actions(
|
6
|
+
context :path_actions, readonly: true, persisted: true do |request:|
|
7
|
+
self.class.path_actions(request)
|
8
8
|
end
|
9
9
|
|
10
|
-
context :http_allow, readonly: true, persisted: true do |
|
11
|
-
|
10
|
+
context :http_allow, readonly: true, persisted: true do |path_actions:|
|
11
|
+
path_actions.map(&:request_method)
|
12
12
|
end
|
13
13
|
|
14
|
-
context(:action_name, persisted: true)
|
14
|
+
context(:action_name, persisted: true)
|
15
|
+
|
15
16
|
define_action(:options, 'OPTIONS', '*', cacheable: true, callbacks: false) do
|
16
17
|
cache 'options-request'
|
17
|
-
end.response(status: 200) do |context
|
18
|
-
response_headers['Allow'] =
|
19
|
-
requests =
|
18
|
+
end.response(status: 200) do |context, request:, http_allow:, path_actions:|
|
19
|
+
response_headers['Allow'] = http_allow.join(', ')
|
20
|
+
requests = path_actions.each_with_object({}) do |action, h|
|
20
21
|
request_attributes_json = attributes.select do |attr|
|
21
22
|
attr.supports_write_for_action? action.name, context
|
22
23
|
end.map do |attr|
|
@@ -26,11 +27,11 @@ module JSONAPIonify::Api
|
|
26
27
|
response_attributes_json = attributes.select do |attr|
|
27
28
|
attr.supports_read_for_action? action.name, context
|
28
29
|
end.map do |attr|
|
29
|
-
attr.options_json_for_action
|
30
|
+
attr.options_json_for_action action.name, context
|
30
31
|
end
|
31
32
|
|
32
|
-
h['path'] =
|
33
|
-
h['url'] =
|
33
|
+
h['path'] = request.path
|
34
|
+
h['url'] = request.url
|
34
35
|
action_options = h[action.request_method] = {}
|
35
36
|
|
36
37
|
if ['GET', 'POST', 'PUT', 'PATCH'].include? action.request_method
|
@@ -50,7 +51,9 @@ module JSONAPIonify::Api
|
|
50
51
|
).to_json
|
51
52
|
end
|
52
53
|
|
53
|
-
before(:create)
|
54
|
+
before(:create) do |context|
|
55
|
+
context.instance = context.new_instance
|
56
|
+
end
|
54
57
|
read
|
55
58
|
end
|
56
59
|
end
|
@@ -3,66 +3,19 @@ module JSONAPIonify::Api
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
after :commit_create, :commit_update do |context|
|
20
|
-
try_commit(context.instance)
|
21
|
-
end
|
22
|
-
|
23
|
-
after :commit_delete do |context|
|
24
|
-
if defined?(ActiveRecord) && context.instance.is_a?(ActiveRecord::Base)
|
25
|
-
context.instance.destroy
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
before :commit_add do |context|
|
30
|
-
context.scope.concat context.request_instances
|
31
|
-
end
|
32
|
-
|
33
|
-
before :commit_remove do |context|
|
34
|
-
context.request_instances.each { |instance| context.scope.delete(instance) }
|
35
|
-
end
|
36
|
-
|
37
|
-
before :commit_replace do |context|
|
38
|
-
case self.class.rel
|
39
|
-
when Relationship::One
|
40
|
-
context.owner.send "#{self.class.rel.name}=", context.request_instance
|
41
|
-
try_commit(context.owner)
|
42
|
-
when Relationship::Many
|
43
|
-
instances_to_add = context.request_instances - context.scope
|
44
|
-
instances_to_delete = context.scope - context.request_instances
|
45
|
-
instances_to_delete.each { |instance| context.scope.delete(instance) }
|
46
|
-
context.scope.append instances_to_add
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def try_commit(instance)
|
52
|
-
if defined?(ActiveRecord) && instance.is_a?(ActiveRecord::Base)
|
53
|
-
commit_active_record(instance)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def commit_active_record(instance)
|
58
|
-
instance.save
|
59
|
-
if instance.errors.present?
|
60
|
-
instance.errors.messages.each do |attr, messages|
|
61
|
-
messages.each do |message|
|
62
|
-
error :invalid_attribute, attr, message
|
6
|
+
after :commit_update, :commit_create do |instance:|
|
7
|
+
if defined?(ActiveRecord) && instance.is_a?(ActiveRecord::Base)
|
8
|
+
# Collect Errors
|
9
|
+
if instance.errors.present?
|
10
|
+
instance.errors.messages.each do |attr, messages|
|
11
|
+
messages.each do |message|
|
12
|
+
error :invalid_attribute, attr, message
|
13
|
+
end
|
14
|
+
end
|
63
15
|
end
|
64
16
|
end
|
65
17
|
end
|
66
18
|
end
|
19
|
+
|
67
20
|
end
|
68
21
|
end
|
@@ -11,7 +11,7 @@ module JSONAPIonify::Api
|
|
11
11
|
included do
|
12
12
|
id :id
|
13
13
|
scope { self.type.classify.constantize }
|
14
|
-
collection do |scope
|
14
|
+
collection do |scope|
|
15
15
|
Resource::Defaults::Options.scope_is_active_record?(scope) ? scope.all : scope
|
16
16
|
end
|
17
17
|
|
@@ -25,13 +25,8 @@ module JSONAPIonify::Api
|
|
25
25
|
scope.new
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
before do |context|
|
33
|
-
context.params # pull params so they verify
|
34
|
-
end
|
28
|
+
# Invoke validating contexts
|
29
|
+
before { |request_headers:| }
|
35
30
|
|
36
31
|
end
|
37
32
|
end
|
@@ -3,37 +3,35 @@ module JSONAPIonify::Api
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
6
|
-
|
7
|
-
|
8
|
-
context.request.body.read
|
6
|
+
context(:request_body, readonly: true, persisted: true) do |request:|
|
7
|
+
request.body.read
|
9
8
|
end
|
10
9
|
|
11
|
-
context(:request_object, readonly: true, persisted: true) do |context
|
12
|
-
JSONAPIonify.parse(
|
10
|
+
context(:request_object, readonly: true, persisted: true) do |context, request_body:|
|
11
|
+
JSONAPIonify.parse(request_body).as(:client).tap do |input|
|
13
12
|
error_now(:request_object_invalid, context, input) unless input.validate
|
14
13
|
end
|
15
14
|
end
|
16
15
|
|
17
|
-
context(:id, readonly: true, persisted: true) do |
|
18
|
-
|
16
|
+
context(:id, readonly: true, persisted: true) do |request:|
|
17
|
+
request.env['jsonapionify.id']
|
19
18
|
end
|
20
19
|
|
21
|
-
context(:request_id, readonly: true, persisted: true) do |
|
22
|
-
|
20
|
+
context(:request_id, readonly: true, persisted: true) do |request_data:|
|
21
|
+
request_data[:id]
|
23
22
|
end
|
24
23
|
|
25
|
-
context(:request_attributes, readonly: true, persisted: true) do |context
|
24
|
+
context(:request_attributes, readonly: true, persisted: true) do |context, request:, action_name:, id:, request_data:|
|
26
25
|
should_error = false
|
27
26
|
|
28
|
-
request_attributes =
|
29
|
-
|
30
|
-
|
31
|
-
end
|
27
|
+
request_attributes = request_data.fetch(:attributes) do
|
28
|
+
error_now :attributes_missing
|
29
|
+
end
|
32
30
|
|
33
31
|
# Check for required attributes
|
34
32
|
self.attributes.each do |attr|
|
35
33
|
next unless attr.required_for_action?(action_name, context)
|
36
|
-
if attr.read? ||
|
34
|
+
if attr.read? || id
|
37
35
|
example_id = self.build_id(instance: context.instance)
|
38
36
|
next unless attr.resolve(
|
39
37
|
context.instance, context, example_id: example_id
|
@@ -64,10 +62,9 @@ module JSONAPIonify::Api
|
|
64
62
|
end
|
65
63
|
end
|
66
64
|
|
67
|
-
context(:request_relationships, readonly: true, persisted: true) do |
|
68
|
-
|
69
|
-
|
70
|
-
data[:relationships].each_with_object({}) do |(name, rel), obj|
|
65
|
+
context(:request_relationships, readonly: true, persisted: true) do |request_data:|
|
66
|
+
if request_data[:relationships]
|
67
|
+
request_data[:relationships].each_with_object({}) do |(name, rel), obj|
|
71
68
|
pointer = "data/relationships/#{name}/data"
|
72
69
|
case rel[:data]
|
73
70
|
when JSONAPIonify::Structure::Collections::Base
|
@@ -81,22 +78,20 @@ module JSONAPIonify::Api
|
|
81
78
|
end
|
82
79
|
end
|
83
80
|
|
84
|
-
context(:request_instances, readonly: true, persisted: true) do |
|
85
|
-
data
|
86
|
-
(data ? find_instances(data, pointer: '/data') : [])
|
81
|
+
context(:request_instances, readonly: true, persisted: true) do |request_data:|
|
82
|
+
(request_data ? find_instances(request_data, pointer: '/data') : [])
|
87
83
|
end
|
88
84
|
|
89
|
-
context(:request_instance, readonly: true, persisted: true) do |
|
90
|
-
find_instance(
|
85
|
+
context(:request_instance, readonly: true, persisted: true) do |request_data:|
|
86
|
+
find_instance(request_data, pointer: 'data')
|
91
87
|
end
|
92
88
|
|
93
|
-
context(:request_resource, readonly: true, persisted: true) do |
|
94
|
-
|
95
|
-
find_resource item, pointer: 'data'
|
89
|
+
context(:request_resource, readonly: true, persisted: true) do |request_data:|
|
90
|
+
find_resource request_data, pointer: 'data'
|
96
91
|
end
|
97
92
|
|
98
|
-
context(:request_data, readonly: true, persisted: true) do |
|
99
|
-
|
93
|
+
context(:request_data, readonly: true, persisted: true) do |request_object:|
|
94
|
+
request_object.fetch(:data) {
|
100
95
|
error_now(:data_missing)
|
101
96
|
}
|
102
97
|
end
|
@@ -3,32 +3,25 @@ module JSONAPIonify::Api
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
6
|
-
context(:invalidate_cache?, readonly: true, persisted: true)
|
6
|
+
context(:invalidate_cache?, readonly: true, persisted: true) do |includes:|
|
7
|
+
includes.present?
|
8
|
+
end
|
7
9
|
|
8
10
|
# Response Objects
|
9
|
-
context(:links, readonly: true, persisted: true) do |
|
10
|
-
|
11
|
+
context(:links, readonly: true, persisted: true) do |response_object:|
|
12
|
+
response_object[:links]
|
11
13
|
end
|
12
14
|
|
13
|
-
context(:meta, readonly: true, persisted: true) do |
|
14
|
-
JSONAPIonify::Structure::Helpers::MetaDelegate.new
|
15
|
+
context(:meta, readonly: true, persisted: true) do |response_object:|
|
16
|
+
JSONAPIonify::Structure::Helpers::MetaDelegate.new response_object
|
15
17
|
end
|
16
18
|
|
17
|
-
context(:response_object, readonly: true, persisted: true) do |
|
18
|
-
JSONAPIonify.parse(links: { self:
|
19
|
+
context(:response_object, readonly: true, persisted: true) do |request:|
|
20
|
+
JSONAPIonify.parse(links: { self: request.url })
|
19
21
|
end
|
20
22
|
|
21
|
-
context(:response_collection, readonly: true) do |
|
22
|
-
|
23
|
-
collections = %i{
|
24
|
-
paginated_collection
|
25
|
-
sorted_collection
|
26
|
-
collection
|
27
|
-
}
|
28
|
-
context.public_send collections.find { |c| context.respond_to? c }
|
29
|
-
else
|
30
|
-
context.collection
|
31
|
-
end
|
23
|
+
context(:response_collection, readonly: true) do |collection:, nested_request: false, paginated_collection: nil, sorted_collection: nil|
|
24
|
+
nested_request ? collection : paginated_collection || sorted_collection || collection
|
32
25
|
end
|
33
26
|
|
34
27
|
end
|