jsonapionify 0.11.11 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|