jsonapionify 0.9.0 → 0.9.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.
Files changed (84) hide show
  1. checksums.yaml +13 -5
  2. data/.rubocop.yml +1 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +8 -0
  5. data/README.md +85 -3
  6. data/Rakefile +14 -0
  7. data/jsonapionify.gemspec +3 -0
  8. data/lib/jsonapionify/api/action.rb +84 -121
  9. data/lib/jsonapionify/api/attribute.rb +97 -20
  10. data/lib/jsonapionify/api/base/class_methods.rb +5 -4
  11. data/lib/jsonapionify/api/base/delegation.rb +20 -4
  12. data/lib/jsonapionify/api/base/doc_helper.rb +3 -3
  13. data/lib/jsonapionify/api/base/reloader.rb +1 -1
  14. data/lib/jsonapionify/api/base/resource_definitions.rb +28 -15
  15. data/lib/jsonapionify/api/base.rb +6 -0
  16. data/lib/jsonapionify/api/context.rb +18 -5
  17. data/lib/jsonapionify/api/context_delegate.rb +24 -7
  18. data/lib/jsonapionify/api/errors.rb +2 -0
  19. data/lib/jsonapionify/api/errors_object.rb +6 -5
  20. data/lib/jsonapionify/api/relationship/blocks.rb +1 -1
  21. data/lib/jsonapionify/api/relationship/many.rb +35 -11
  22. data/lib/jsonapionify/api/relationship/one.rb +17 -7
  23. data/lib/jsonapionify/api/relationship.rb +20 -6
  24. data/lib/jsonapionify/api/resource/builders.rb +81 -30
  25. data/lib/jsonapionify/api/resource/caching.rb +28 -0
  26. data/lib/jsonapionify/api/resource/caller.rb +61 -0
  27. data/lib/jsonapionify/api/resource/class_methods.rb +6 -2
  28. data/lib/jsonapionify/api/resource/defaults/actions.rb +47 -0
  29. data/lib/jsonapionify/api/resource/defaults/errors.rb +61 -15
  30. data/lib/jsonapionify/api/resource/defaults/hooks.rb +68 -0
  31. data/lib/jsonapionify/api/resource/defaults/options.rb +16 -28
  32. data/lib/jsonapionify/api/resource/defaults/params.rb +3 -0
  33. data/lib/jsonapionify/api/resource/defaults/request_contexts.rb +80 -32
  34. data/lib/jsonapionify/api/resource/defaults/response_contexts.rb +13 -6
  35. data/lib/jsonapionify/api/resource/defaults.rb +1 -1
  36. data/lib/jsonapionify/api/resource/definitions/actions.rb +81 -55
  37. data/lib/jsonapionify/api/resource/definitions/attributes.rb +46 -10
  38. data/lib/jsonapionify/api/resource/definitions/contexts.rb +6 -2
  39. data/lib/jsonapionify/api/resource/definitions/helpers.rb +1 -1
  40. data/lib/jsonapionify/api/resource/definitions/pagination.rb +47 -56
  41. data/lib/jsonapionify/api/resource/definitions/params.rb +11 -15
  42. data/lib/jsonapionify/api/resource/definitions/relationships.rb +43 -7
  43. data/lib/jsonapionify/api/resource/definitions/request_headers.rb +6 -3
  44. data/lib/jsonapionify/api/resource/definitions/response_headers.rb +1 -1
  45. data/lib/jsonapionify/api/resource/definitions/scopes.rb +5 -5
  46. data/lib/jsonapionify/api/resource/definitions/sorting.rb +12 -11
  47. data/lib/jsonapionify/api/resource/definitions.rb +1 -1
  48. data/lib/jsonapionify/api/resource/error_handling.rb +92 -20
  49. data/lib/jsonapionify/api/resource/exec.rb +11 -0
  50. data/lib/jsonapionify/api/resource/includer.rb +89 -1
  51. data/lib/jsonapionify/api/resource.rb +55 -8
  52. data/lib/jsonapionify/api/response.rb +43 -14
  53. data/lib/jsonapionify/api/server/media_type.rb +36 -0
  54. data/lib/jsonapionify/api/server/request.rb +25 -11
  55. data/lib/jsonapionify/api/server.rb +8 -4
  56. data/lib/jsonapionify/api/sort_field.rb +18 -0
  57. data/lib/jsonapionify/api/sort_field_set.rb +1 -1
  58. data/lib/jsonapionify/api/test_helper.rb +46 -0
  59. data/lib/jsonapionify/documentation/template.erb +2 -2
  60. data/lib/jsonapionify/documentation.rb +10 -0
  61. data/lib/jsonapionify/structure/collections/base.rb +10 -3
  62. data/lib/jsonapionify/structure/helpers/object_defaults.rb +5 -10
  63. data/lib/jsonapionify/structure/maps/relationships.rb +4 -0
  64. data/lib/jsonapionify/structure/objects/attributes.rb +4 -0
  65. data/lib/jsonapionify/structure/objects/base.rb +22 -9
  66. data/lib/jsonapionify/structure/objects/error.rb +2 -0
  67. data/lib/jsonapionify/structure/objects/jsonapi.rb +1 -0
  68. data/lib/jsonapionify/structure/objects/link.rb +1 -0
  69. data/lib/jsonapionify/structure/objects/relationship.rb +2 -0
  70. data/lib/jsonapionify/structure/objects/resource.rb +2 -0
  71. data/lib/jsonapionify/structure/objects/resource_identifier.rb +12 -4
  72. data/lib/jsonapionify/structure/objects/top_level.rb +4 -2
  73. data/lib/jsonapionify/types/array_type.rb +16 -11
  74. data/lib/jsonapionify/types/boolean_type.rb +9 -4
  75. data/lib/jsonapionify/types/date_string_type.rb +7 -10
  76. data/lib/jsonapionify/types/float_type.rb +13 -0
  77. data/lib/jsonapionify/types/integer_type.rb +12 -0
  78. data/lib/jsonapionify/types/object_type.rb +7 -2
  79. data/lib/jsonapionify/types/string_type.rb +12 -0
  80. data/lib/jsonapionify/types/time_string_type.rb +8 -10
  81. data/lib/jsonapionify/types.rb +43 -5
  82. data/lib/jsonapionify/version.rb +1 -1
  83. data/lib/jsonapionify.rb +36 -1
  84. metadata +121 -74
@@ -52,7 +52,11 @@ module JSONAPIonify::Api
52
52
 
53
53
  def documented_actions_in_order
54
54
  indexes = %i{list create read update delete add replace remove}
55
- documented_actions.sort_by { |action, _| indexes.index(action.name) || indexes.length }
55
+ documented_actions.reject do |a, *|
56
+ ['HEAD', 'OPTIONS'].include? a.request_method
57
+ end.sort_by do |action, *|
58
+ indexes.index(action.name) || indexes.length
59
+ end
56
60
  end
57
61
 
58
62
  def documentation_object(base_url)
@@ -60,7 +64,7 @@ module JSONAPIonify::Api
60
64
  name: type,
61
65
  description: JSONAPIonify::Documentation.render_markdown(@description || ''),
62
66
  relationships: relationships.map { |r| r.documentation_object },
63
- attributes: attributes.map(&:documentation_object),
67
+ attributes: attributes.sort_by(&:name).map(&:documentation_object),
64
68
  actions: documented_actions_in_order.map do |action, base, args|
65
69
  action.documentation_object File.join(base_url, base), *args
66
70
  end
@@ -3,6 +3,53 @@ module JSONAPIonify::Api
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
+ context :path_actions, readonly: true, persisted: true do |context|
7
+ self.class.path_actions(context.request)
8
+ end
9
+
10
+ context :http_allow, readonly: true, persisted: true do |context|
11
+ context.path_actions.map(&:request_method)
12
+ end
13
+
14
+ context(:action_name, persisted: true) {}
15
+ define_action(:options, 'OPTIONS', '*', cacheable: true, callbacks: false) do
16
+ cache 'options-request'
17
+ end.response(status: 200) do |context|
18
+ response_headers['Allow'] = context.http_allow.join(', ')
19
+ requests = context.path_actions.each_with_object({}) do |action, h|
20
+ request_attributes_json = attributes.select do |attr|
21
+ attr.supports_write_for_action? action.name, context
22
+ end.map do |attr|
23
+ attr.options_json_for_action(action.name, context)
24
+ end
25
+
26
+ response_attributes_json = attributes.select do |attr|
27
+ attr.supports_read_for_action? action.name, context
28
+ end.map do |attr|
29
+ attr.options_json_for_action(action.name, context)
30
+ end
31
+
32
+ h['path'] = context.request.path
33
+ h['url'] = context.request.url
34
+ action_options = h[action.request_method] = {}
35
+
36
+ if ['GET', 'POST', 'PUT', 'PATCH'].include? action.request_method
37
+ action_options[:response_attributes] = response_attributes_json
38
+ action_options[:relationships] = self.class.relationships.map(&:options_json)
39
+ end
40
+
41
+ if ['POST', 'PUT', 'PATCH'].include? action.request_method
42
+ action_options[:request_attributes] = request_attributes_json
43
+ end
44
+ end
45
+ JSONAPIonify.new_object(
46
+ meta: {
47
+ type: self.class.type,
48
+ requests: requests
49
+ }
50
+ ).to_json
51
+ end
52
+
6
53
  before(:create) { |context| context.instance = context.new_instance }
7
54
  read
8
55
  end
@@ -5,8 +5,11 @@ module JSONAPIonify::Api
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- rescue_from JSONAPIonify::Structure::ValidationError, error: :jsonapi_validation_error
9
- rescue_from Oj::ParseError, error: :json_parse_error
8
+ register_exception 'ActiveRecord::RecordInvalid', error: :invalid_record
9
+ register_exception 'ActiveRecord::RecordNotUnique', error: :duplicate_record
10
+ register_exception 'ActiveRecord::RecordNotFound', error: :not_found
11
+ register_exception JSONAPIonify::Structure::ValidationError, error: :jsonapi_validation_error
12
+ register_exception Oj::ParseError, error: :json_parse_error
10
13
 
11
14
  Rack::Utils::SYMBOL_TO_STATUS_CODE.reject { |_, v| v < 400 }.each do |symbol, code|
12
15
  message = Rack::Utils::HTTP_STATUS_CODES[code]
@@ -18,74 +21,100 @@ module JSONAPIonify::Api
18
21
 
19
22
  error :data_missing do
20
23
  pointer ''
21
- title 'Missing Member'
24
+ title 'missing Member'
22
25
  detail 'missing data member'
23
26
  status '422'
24
27
  end
25
28
 
26
29
  error :json_parse_error do
27
- title 'Parse Error'
28
- detail 'Could not parse JSON object'
30
+ title 'parse Error'
31
+ detail 'could not parse json object'
29
32
  status '422'
30
33
  end
31
34
 
32
35
  error :field_not_permitted do |type, field|
33
36
  parameter "fields[#{type}]"
34
- title 'Invalid Field'
37
+ title 'invalid field'
35
38
  detail "type: `#{type}`, does not have field: `#{field}`"
36
39
  status '400'
37
40
  end
38
41
 
42
+ error :attribute_type_error do |attribute|
43
+ pointer "data/attributes/#{attribute}"
44
+ status '500'
45
+ title "attribute type error"
46
+ end
47
+
48
+ error :attribute_required do |attribute|
49
+ pointer "data/attributes/#{attribute}"
50
+ title 'attribute required'
51
+ detail "attribute required: #{attribute}"
52
+ status '422'
53
+ end
54
+
55
+ error :attribute_cannot_be_null do |attribute|
56
+ pointer "data/attributes/#{attribute}"
57
+ title "attribute cannot be null: #{attribute}"
58
+ status '500'
59
+ end
60
+
39
61
  error :attribute_not_permitted do |attribute|
40
62
  pointer "data/attributes/#{attribute}"
41
- title 'Attribute not permitted'
42
- detail "Attribute not permitted: #{attribute}"
63
+ title 'attribute not permitted'
64
+ status '422'
65
+ detail "attribute not permitted: #{attribute}"
43
66
  end
44
67
 
45
68
  error :attributes_missing do
46
69
  pointer 'data'
47
- title 'Missing Member'
70
+ title 'missing Member'
48
71
  detail 'missing attributes member'
49
72
  status '422'
50
73
  end
51
74
 
52
75
  error :include_parameter_invalid do
53
76
  parameter 'sort'
54
- title 'Include parameter is invalid'
77
+ title 'include parameter is invalid'
55
78
  status '400'
56
79
  end
57
80
 
58
81
  error :parameters_missing do |parameters|
59
- title 'Missing required parameters'
82
+ title 'missing required parameters'
60
83
  detail "missing: #{parameters.to_sentence}"
61
84
  status '400'
62
85
  end
63
86
 
64
87
  error :parameter_invalid do |param|
65
88
  parameter param
66
- title 'Parameter Invalid'
89
+ title 'parameter Invalid'
67
90
  detail "parameter invalid: #{param}"
68
91
  status '400'
69
92
  end
70
93
 
71
94
  error :headers_missing do |headers|
72
- title 'Missing required headers'
95
+ title 'missing required headers'
73
96
  detail "missing: #{headers.to_sentence}"
74
97
  status '400'
75
98
  end
76
99
 
77
100
  error :sort_parameter_invalid do
78
101
  parameter 'sort'
79
- title 'Sort parameter is invalid'
102
+ title 'sort parameter is invalid'
80
103
  status '400'
81
104
  end
82
105
 
83
106
  error :page_parameter_invalid do |*paths|
84
107
  parameter ParamOptions.keypath_to_string(*paths)
85
- title 'Page parameter invalid'
108
+ title 'page parameter invalid'
86
109
  status '400'
87
110
  end
88
111
 
112
+ error :relationship_not_includable do |name|
113
+ parameter 'include'
114
+ title "relationship not includable: #{name}"
115
+ status '406'
116
+ end
117
+
89
118
  error :request_object_invalid do |context, request_object|
90
119
  context.errors.set request_object.errors.as_collection
91
120
  end
@@ -94,6 +123,23 @@ module JSONAPIonify::Api
94
123
  title 'Invalid Resource'
95
124
  status '404'
96
125
  end
126
+
127
+ error :duplicate_record do
128
+ title 'Duplicate Record'
129
+ status "409"
130
+ end
131
+
132
+ error :invalid_attribute do |attr, message|
133
+ title attr.present? ? 'Invalid Attribute' : 'Invalid Record'
134
+ detail [attr, message].reject(&:empty?).join(' ')
135
+ pointer "data/attributes/#{attr}" if attr.present?
136
+ status "422"
137
+ end
138
+
139
+ error :invalid_record do
140
+ title 'Invalid Record'
141
+ status "422"
142
+ end
97
143
  end
98
144
  end
99
145
  end
@@ -0,0 +1,68 @@
1
+ module JSONAPIonify::Api
2
+ module Resource::Defaults::Hooks
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ before :commit_create, :commit_update do |context|
7
+ # Assign the attributes
8
+ context.request_attributes.each do |key, value|
9
+ context.instance.send "#{key}=", value
10
+ end
11
+
12
+ # Assign the relationships
13
+ context.request_relationships.each do |key, value|
14
+ context.instance.send "#{key}=", value
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
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -3,40 +3,28 @@ module JSONAPIonify::Api
3
3
  extend ActiveSupport::Concern
4
4
  included do
5
5
  id :id
6
- scope { raise NotImplementedError, 'scope not implemented' }
7
- collection { raise NotImplementedError, 'collection not implemented' }
8
- instance { raise NotImplementedError, 'instance not implemented' }
9
- new_instance { raise NotImplementedError, 'new instance not implemented' }
10
-
11
- context :scope_defined? do |context|
12
- begin
13
- !!context.scope
14
- rescue NotImplementedError
15
- false
16
- end
17
- end
18
-
19
- context :collection_defined? do |context|
20
- begin
21
- !!context.collection
22
- rescue NotImplementedError
23
- false
6
+ scope { self.type.classify.constantize }
7
+ collection do |scope|
8
+ if defined?(ActiveRecord) && scope < ActiveRecord::Base && scope.is_a?(Class)
9
+ scope.all
10
+ else
11
+ scope
24
12
  end
25
13
  end
26
14
 
27
- context :instance_defined? do |context|
28
- begin
29
- !!context.instance
30
- rescue NotImplementedError
31
- false
15
+ instance do |scope, key|
16
+ if defined?(ActiveRecord) && scope < ActiveRecord::Base
17
+ scope.find_by! id_attribute => key
18
+ else
19
+ raise NotImplementedError, 'instance not implemented'
32
20
  end
33
21
  end
34
22
 
35
- context :new_instance_defined? do |context|
36
- begin
37
- !!context.new_instance
38
- rescue NotImplementedError
39
- false
23
+ new_instance do |scope|
24
+ if defined?(ActiveRecord) && scope < ActiveRecord::Base
25
+ scope.new
26
+ else
27
+ raise NotImplementedError, 'scope not implemented'
40
28
  end
41
29
  end
42
30
 
@@ -4,6 +4,9 @@ module JSONAPIonify::Api
4
4
 
5
5
  included do
6
6
  param :'include-relationships'
7
+
8
+ # Configure the default sort
9
+ default_sort 'id'
7
10
  end
8
11
  end
9
12
  end
@@ -3,74 +3,122 @@ module JSONAPIonify::Api
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- context(:request_body, readonly: true) do |context|
6
+
7
+ context(:request_body, readonly: true, persisted: true) do |context|
7
8
  context.request.body.read
8
9
  end
9
10
 
10
- context(:request_object, readonly: true) do |context|
11
+ context(:request_object, readonly: true, persisted: true) do |context|
11
12
  JSONAPIonify.parse(context.request_body).as(:client).tap do |input|
12
13
  error_now(:request_object_invalid, context, input) unless input.validate
13
14
  end
14
15
  end
15
16
 
16
- context(:id, readonly: true) do |context|
17
+ context(:id, readonly: true, persisted: true) do |context|
17
18
  context.request.env['jsonapionify.id']
18
19
  end
19
20
 
20
- context(:request_attributes, readonly: true) do |context|
21
- request_object = context.request_object
21
+ context(:request_id, readonly: true, persisted: true) do |context|
22
+ context.request_data[:id]
23
+ end
24
+
25
+ context(:request_attributes, readonly: true, persisted: true) do |context|
26
+ should_error = false
22
27
 
23
- # Validate Request Object
24
- request_object.validate
25
- error_now(:request_object_invalid, context, request_object) if request_object.errors.present?
28
+ request_attributes =
29
+ context.request_data.fetch(:attributes) do
30
+ error_now :attributes_missing
31
+ end
26
32
 
27
- request_attributes = context.request_data.fetch(:attributes) do
28
- error_now :attributes_missing
33
+ # Check for required attributes
34
+ self.attributes.each do |attr|
35
+ next unless attr.required_for_action?(action_name, context)
36
+ if attr.read? || context.id
37
+ example_id = self.build_id(context.instance)
38
+ next unless attr.resolve(
39
+ context.instance, context, example_id: example_id
40
+ ).nil?
41
+ end
42
+ unless request_attributes.has_key?(attr.name)
43
+ error :attribute_required, attr.name
44
+ should_error = true
45
+ end
29
46
  end
30
- request_attributes.tap do |attributes|
31
- # Check Permitted Attributes
32
- writable_attributes = context.request_resource.attributes.select(&:write?)
33
- if (extra_attributes = attributes.keys - writable_attributes.map(&:name)).present?
34
- extra_attributes.each { |attr| error :attribute_not_permitted, attr }
35
- raise Errors::RequestError
47
+
48
+ request_attributes.each_with_object({}) do |(attr, v), attributes|
49
+ resource_attribute = self.attributes.find { |a| a.name == attr }
50
+ is_actionable = !!resource_attribute&.supports_write_for_action?(action_name, context)
51
+ unless is_actionable
52
+ error :attribute_not_permitted, attr
53
+ should_error = true
36
54
  end
37
- end.to_hash
38
- end
39
55
 
40
- context(:request_instances, readonly: true) do |context|
41
- should_error = false
42
- data = context.request_data
43
- instances = data.map.each_with_index do |item, i|
44
56
  begin
45
- find_instance item, pointer: "data/#{i}"
46
- rescue Errors::RequestError
57
+ attributes[attr] = resource_attribute.type.load(v)
58
+ rescue JSONAPIonify::Types::LoadError
59
+ error :attribute_type_error, attr
47
60
  should_error = true
48
61
  end
62
+ end.tap do
63
+ halt if should_error
49
64
  end
50
- raise Errors::RequestError if should_error
51
- instances
52
65
  end
53
66
 
54
- context(:request_instance, readonly: true) do |context|
67
+ context(:request_relationships, readonly: true, persisted: true) do |context|
68
+ data = context.request_data
69
+ if data[:relationships]
70
+ data[:relationships].each_with_object({}) do |(name, rel), obj|
71
+ pointer = "data/relationships/#{name}/data"
72
+ case rel[:data]
73
+ when JSONAPIonify::Structure::Collections::Base
74
+ obj[name] = find_instances(rel[:data], pointer: pointer)
75
+ when JSONAPIonify::Structure::Objects::Base
76
+ obj[name] = find_instance(rel[:data], pointer: pointer)
77
+ end
78
+ end
79
+ else
80
+ {}
81
+ end
82
+ end
83
+
84
+ context(:request_instances, readonly: true, persisted: true) do |context|
85
+ data = context.request_data
86
+ (data ? find_instances(data, pointer: '/data') : [])
87
+ end
88
+
89
+ context(:request_instance, readonly: true, persisted: true) do |context|
55
90
  find_instance(context.request_data, pointer: 'data')
56
91
  end
57
92
 
58
- context(:request_resource, readonly: true) do |context|
93
+ context(:request_resource, readonly: true, persisted: true) do |context|
59
94
  item = context.request_data
60
95
  find_resource item, pointer: 'data'
61
96
  end
62
97
 
63
- context(:request_data) do |context|
98
+ context(:request_data, readonly: true, persisted: true) do |context|
64
99
  context.request_object.fetch(:data) {
65
100
  error_now(:data_missing)
66
101
  }
67
102
  end
68
103
 
69
- context(:authentication, readonly: true) do
104
+ context(:authentication, readonly: true, persisted: true) do
70
105
  OpenStruct.new
71
106
  end
72
107
  end
73
108
 
109
+ def find_instances(items, pointer:)
110
+ should_error = false
111
+ instances = items.map.each_with_index do |item, i|
112
+ begin
113
+ find_instance item, pointer: "#{pointer}/#{i}"
114
+ rescue Errors::RequestError
115
+ should_error = true
116
+ end
117
+ end
118
+ halt if should_error
119
+ instances
120
+ end
121
+
74
122
  def find_instance(item, pointer:)
75
123
  should_error = false
76
124
  resource = find_resource(item, pointer: pointer)
@@ -81,7 +129,7 @@ module JSONAPIonify::Api
81
129
  self.detail "could not find resource: `#{item[:type]}` with id: #{item[:id]}"
82
130
  end
83
131
  end
84
- raise Errors::RequestError if should_error
132
+ halt if should_error
85
133
  instance
86
134
  end
87
135
 
@@ -94,7 +142,7 @@ module JSONAPIonify::Api
94
142
  self.detail "could not find resource: `#{item[:type]}`"
95
143
  end
96
144
  end
97
- raise Errors::RequestError if should_error
145
+ halt if should_error
98
146
  resource
99
147
  end
100
148
 
@@ -3,27 +3,34 @@ module JSONAPIonify::Api
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
+ before(:response) { |context| context.clear }
7
+
8
+ context(:invalidate_cache?, readonly: true, persisted: true) { |c| c.includes.present? }
6
9
 
7
10
  # Response Objects
8
- context(:links, readonly: true) do |context|
11
+ context(:links, readonly: true, persisted: true) do |context|
9
12
  context.response_object[:links]
10
13
  end
11
14
 
12
- context(:meta, readonly: true) do |context|
15
+ context(:meta, readonly: true, persisted: true) do |context|
13
16
  JSONAPIonify::Structure::Helpers::MetaDelegate.new context.response_object
14
17
  end
15
18
 
16
- context(:response_object) do |context|
19
+ context(:response_object, readonly: true, persisted: true) do |context|
17
20
  JSONAPIonify.parse(links: { self: context.request.url })
18
21
  end
19
22
 
20
- context(:response_collection) do |context|
21
- collections = %i{
23
+ context(:response_collection, readonly: true) do |context|
24
+ if context.root_request?
25
+ collections = %i{
22
26
  paginated_collection
23
27
  sorted_collection
24
28
  collection
25
29
  }
26
- context.public_send collections.find { |c| context.respond_to? c }
30
+ context.public_send collections.find { |c| context.respond_to? c }
31
+ else
32
+ context.collection
33
+ end
27
34
  end
28
35
 
29
36
  end
@@ -7,4 +7,4 @@ module JSONAPIonify::Api
7
7
  constants(false).each { |const| klass.include const_get const, false }
8
8
  end
9
9
  end
10
- end
10
+ end