sanger-jsonapi-resources 0.1.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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.md +53 -0
  4. data/lib/generators/jsonapi/USAGE +13 -0
  5. data/lib/generators/jsonapi/controller_generator.rb +14 -0
  6. data/lib/generators/jsonapi/resource_generator.rb +14 -0
  7. data/lib/generators/jsonapi/templates/jsonapi_controller.rb +4 -0
  8. data/lib/generators/jsonapi/templates/jsonapi_resource.rb +4 -0
  9. data/lib/jsonapi/acts_as_resource_controller.rb +320 -0
  10. data/lib/jsonapi/cached_resource_fragment.rb +127 -0
  11. data/lib/jsonapi/callbacks.rb +51 -0
  12. data/lib/jsonapi/compiled_json.rb +36 -0
  13. data/lib/jsonapi/configuration.rb +258 -0
  14. data/lib/jsonapi/error.rb +47 -0
  15. data/lib/jsonapi/error_codes.rb +60 -0
  16. data/lib/jsonapi/exceptions.rb +563 -0
  17. data/lib/jsonapi/formatter.rb +169 -0
  18. data/lib/jsonapi/include_directives.rb +100 -0
  19. data/lib/jsonapi/link_builder.rb +152 -0
  20. data/lib/jsonapi/mime_types.rb +41 -0
  21. data/lib/jsonapi/naive_cache.rb +30 -0
  22. data/lib/jsonapi/operation.rb +24 -0
  23. data/lib/jsonapi/operation_dispatcher.rb +88 -0
  24. data/lib/jsonapi/operation_result.rb +65 -0
  25. data/lib/jsonapi/operation_results.rb +35 -0
  26. data/lib/jsonapi/paginator.rb +209 -0
  27. data/lib/jsonapi/processor.rb +328 -0
  28. data/lib/jsonapi/relationship.rb +94 -0
  29. data/lib/jsonapi/relationship_builder.rb +167 -0
  30. data/lib/jsonapi/request_parser.rb +678 -0
  31. data/lib/jsonapi/resource.rb +1255 -0
  32. data/lib/jsonapi/resource_controller.rb +5 -0
  33. data/lib/jsonapi/resource_controller_metal.rb +16 -0
  34. data/lib/jsonapi/resource_serializer.rb +531 -0
  35. data/lib/jsonapi/resources/version.rb +5 -0
  36. data/lib/jsonapi/response_document.rb +135 -0
  37. data/lib/jsonapi/routing_ext.rb +262 -0
  38. data/lib/jsonapi-resources.rb +27 -0
  39. metadata +223 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bf908bb5423dd9c31362ed50c78a5089fd02fa120278d543d90d83d99690acb1
4
+ data.tar.gz: 1118019c84568daf2ee120327d821dc1e930d210947bf7c9ab0ff6006adec87e
5
+ SHA512:
6
+ metadata.gz: bfff352e247bb4b77eab4df52557e91768498155f60084cb20b0e1e66b38a50f4199c07a27af97df97d9672394ff2cf7ef320bde343de7ebd206bb6fa899b4c0
7
+ data.tar.gz: cf361701487602881c1f2271165be8295f187cf520efcf3324a47b2265efc2926b5642dc33d6cf89da6a63daf196a863aa472ee59436db04ecd5647a64abb5d0
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Larry Gebhardt
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # JSONAPI::Resources [![Gem Version](https://badge.fury.io/rb/jsonapi-resources.svg)](https://badge.fury.io/rb/jsonapi-resources) [![Build Status](https://secure.travis-ci.org/cerebris/jsonapi-resources.svg?branch=beta)](http://travis-ci.org/cerebris/jsonapi-resources) [![Code Climate](https://codeclimate.com/github/cerebris/jsonapi-resources/badges/gpa.svg)](https://codeclimate.com/github/cerebris/jsonapi-resources)
2
+
3
+ [![Join the chat at https://gitter.im/cerebris/jsonapi-resources](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/cerebris/jsonapi-resources?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4
+
5
+ `JSONAPI::Resources`, or "JR", provides a framework for developing an API server that complies with the
6
+ [JSON:API](http://jsonapi.org/) specification.
7
+
8
+ Like JSON:API itself, JR's design is focused on the resources served by an API. JR needs little more than a definition
9
+ of your resources, including their attributes and relationships, to make your server compliant with JSON API.
10
+
11
+ JR is designed to work with Rails 4.2+, and provides custom routes, controllers, and serializers. JR's resources may be
12
+ backed by ActiveRecord models or by custom objects.
13
+
14
+ ## Documentation
15
+
16
+ Full documentation can be found at [http://jsonapi-resources.com](http://jsonapi-resources.com), including the [v0.9 beta Guide](http://jsonapi-resources.com/v0.9/guide/) specific to this version.
17
+
18
+ ## Demo App
19
+
20
+ We have a simple demo app, called [Peeps](https://github.com/cerebris/peeps), available to show how JR is used.
21
+
22
+ ## Client Libraries
23
+
24
+ JSON:API maintains a (non-verified) listing of [client libraries](http://jsonapi.org/implementations/#client-libraries)
25
+ which *should* be compatible with JSON:API compliant server implementations such as JR.
26
+
27
+ ## Installation
28
+
29
+ Add JR to your application's `Gemfile`:
30
+
31
+ gem 'jsonapi-resources'
32
+
33
+ And then execute:
34
+
35
+ $ bundle
36
+
37
+ Or install it yourself as:
38
+
39
+ $ gem install jsonapi-resources
40
+
41
+ **For further usage see the [v0.9 beta Guide](http://jsonapi-resources.com/v0.9/guide/)**
42
+
43
+ ## Contributing
44
+
45
+ 1. Fork it ( http://github.com/cerebris/jsonapi-resources/fork )
46
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
47
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
48
+ 4. Push to the branch (`git push origin my-new-feature`)
49
+ 5. Create a new Pull Request
50
+
51
+ ## License
52
+
53
+ Copyright 2014-2016 Cerebris Corporation. MIT License (see LICENSE for details).
@@ -0,0 +1,13 @@
1
+ Description:
2
+ Generator for JSONAPI Resources
3
+
4
+ Examples:
5
+ rails generate jsonapi:resource Post
6
+
7
+ This will create:
8
+ app/resources/post_resource.rb
9
+
10
+ rails generate jsonapi:controller Post
11
+
12
+ This will create:
13
+ app/controllers/posts_controller.rb
@@ -0,0 +1,14 @@
1
+ module Jsonapi
2
+ class ControllerGenerator < Rails::Generators::NamedBase
3
+ source_root File.expand_path('../templates', __FILE__)
4
+
5
+ def create_resource
6
+ template_file = File.join(
7
+ 'app/controllers',
8
+ class_path,
9
+ "#{file_name.pluralize}_controller.rb"
10
+ )
11
+ template 'jsonapi_controller.rb', template_file
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Jsonapi
2
+ class ResourceGenerator < Rails::Generators::NamedBase
3
+ source_root File.expand_path('../templates', __FILE__)
4
+
5
+ def create_resource
6
+ template_file = File.join(
7
+ 'app/resources',
8
+ class_path,
9
+ "#{file_name.singularize}_resource.rb"
10
+ )
11
+ template 'jsonapi_resource.rb', template_file
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,4 @@
1
+ <% module_namespacing do -%>
2
+ class <%= class_name.pluralize %>Controller < JSONAPI::ResourceController
3
+ end
4
+ <% end -%>
@@ -0,0 +1,4 @@
1
+ <% module_namespacing do -%>
2
+ class <%= class_name.singularize %>Resource < JSONAPI::Resource
3
+ end
4
+ <% end -%>
@@ -0,0 +1,320 @@
1
+ require 'csv'
2
+
3
+ module JSONAPI
4
+ module ActsAsResourceController
5
+ MEDIA_TYPE_MATCHER = /.+".+"[^,]*|[^,]+/
6
+ ALL_MEDIA_TYPES = '*/*'
7
+
8
+ def self.included(base)
9
+ base.extend ClassMethods
10
+ base.include Callbacks
11
+ base.cattr_reader :server_error_callbacks
12
+ base.define_jsonapi_resources_callbacks :process_operations
13
+ end
14
+
15
+ def index
16
+ process_request
17
+ end
18
+
19
+ def show
20
+ process_request
21
+ end
22
+
23
+ def show_relationship
24
+ process_request
25
+ end
26
+
27
+ def create
28
+ return unless verify_content_type_header
29
+ process_request
30
+ end
31
+
32
+ def create_relationship
33
+ return unless verify_content_type_header
34
+ process_request
35
+ end
36
+
37
+ def update_relationship
38
+ return unless verify_content_type_header
39
+ process_request
40
+ end
41
+
42
+ def update
43
+ return unless verify_content_type_header
44
+ process_request
45
+ end
46
+
47
+ def destroy
48
+ process_request
49
+ end
50
+
51
+ def destroy_relationship
52
+ process_request
53
+ end
54
+
55
+ def get_related_resource
56
+ process_request
57
+ end
58
+
59
+ def get_related_resources
60
+ process_request
61
+ end
62
+
63
+ def process_request
64
+ return unless verify_accept_header
65
+
66
+ @request = JSONAPI::RequestParser.new(params, context: context,
67
+ key_formatter: key_formatter,
68
+ server_error_callbacks: (self.class.server_error_callbacks || []))
69
+
70
+ unless @request.errors.empty?
71
+ render_errors(@request.errors)
72
+ else
73
+ operations = @request.operations
74
+ unless JSONAPI.configuration.resource_cache.nil?
75
+ operations.each {|op| op.options[:cache_serializer] = resource_serializer }
76
+ end
77
+ results = process_operations(operations)
78
+ render_results(results)
79
+ end
80
+ rescue => e
81
+ handle_exceptions(e)
82
+ end
83
+
84
+ def process_operations(operations)
85
+ run_callbacks :process_operations do
86
+ operation_dispatcher.process(operations)
87
+ end
88
+ end
89
+
90
+ def transaction
91
+ lambda { |&block|
92
+ ActiveRecord::Base.transaction do
93
+ block.yield
94
+ end
95
+ }
96
+ end
97
+
98
+ def rollback
99
+ lambda {
100
+ fail ActiveRecord::Rollback
101
+ }
102
+ end
103
+
104
+ def operation_dispatcher
105
+ @operation_dispatcher ||= JSONAPI::OperationDispatcher.new(transaction: transaction,
106
+ rollback: rollback,
107
+ server_error_callbacks: @request.server_error_callbacks)
108
+ end
109
+
110
+ private
111
+
112
+ def resource_klass
113
+ @resource_klass ||= resource_klass_name.safe_constantize
114
+ end
115
+
116
+ def resource_serializer_klass
117
+ @resource_serializer_klass ||= JSONAPI::ResourceSerializer
118
+ end
119
+
120
+ def resource_serializer
121
+ @resource_serializer ||= resource_serializer_klass.new(
122
+ resource_klass,
123
+ include_directives: @request ? @request.include_directives : nil,
124
+ fields: @request ? @request.fields : {},
125
+ base_url: base_url,
126
+ key_formatter: key_formatter,
127
+ route_formatter: route_formatter,
128
+ serialization_options: serialization_options
129
+ )
130
+ @resource_serializer
131
+ end
132
+
133
+ def base_url
134
+ @base_url ||= request.protocol + request.host_with_port
135
+ end
136
+
137
+ def resource_klass_name
138
+ @resource_klass_name ||= "#{self.class.name.underscore.sub(/_controller$/, '').singularize}_resource".camelize
139
+ end
140
+
141
+ def verify_content_type_header
142
+ unless request.content_type == JSONAPI::MEDIA_TYPE
143
+ fail JSONAPI::Exceptions::UnsupportedMediaTypeError.new(request.content_type)
144
+ end
145
+ true
146
+ rescue => e
147
+ handle_exceptions(e)
148
+ false
149
+ end
150
+
151
+ def verify_accept_header
152
+ unless valid_accept_media_type?
153
+ fail JSONAPI::Exceptions::NotAcceptableError.new(request.accept)
154
+ end
155
+ true
156
+ rescue => e
157
+ handle_exceptions(e)
158
+ false
159
+ end
160
+
161
+ def valid_accept_media_type?
162
+ media_types = media_types_for('Accept')
163
+
164
+ media_types.blank? ||
165
+ media_types.any? do |media_type|
166
+ (media_type == JSONAPI::MEDIA_TYPE || media_type.start_with?(ALL_MEDIA_TYPES))
167
+ end
168
+ end
169
+
170
+ def media_types_for(header)
171
+ (request.headers[header] || '')
172
+ .scan(MEDIA_TYPE_MATCHER)
173
+ .to_a
174
+ .map(&:strip)
175
+ end
176
+
177
+ # override to set context
178
+ def context
179
+ {}
180
+ end
181
+
182
+ def serialization_options
183
+ {}
184
+ end
185
+
186
+ # Control by setting in an initializer:
187
+ # JSONAPI.configuration.json_key_format = :camelized_key
188
+ # JSONAPI.configuration.route = :camelized_route
189
+ #
190
+ # Override if you want to set a per controller key format.
191
+ # Must return an instance of a class derived from KeyFormatter.
192
+ def key_formatter
193
+ JSONAPI.configuration.key_formatter
194
+ end
195
+
196
+ def route_formatter
197
+ JSONAPI.configuration.route_formatter
198
+ end
199
+
200
+ def base_response_meta
201
+ {}
202
+ end
203
+
204
+ def base_meta
205
+ if @request.nil? || @request.warnings.empty?
206
+ base_response_meta
207
+ else
208
+ base_response_meta.merge(warnings: @request.warnings)
209
+ end
210
+ end
211
+
212
+ def base_response_links
213
+ {}
214
+ end
215
+
216
+ def render_errors(errors)
217
+ operation_results = JSONAPI::OperationResults.new
218
+ result = JSONAPI::ErrorsOperationResult.new(errors[0].status, errors)
219
+ operation_results.add_result(result)
220
+
221
+ render_results(operation_results)
222
+ end
223
+
224
+ def render_results(operation_results)
225
+ response_doc = create_response_document(operation_results)
226
+ content = response_doc.contents
227
+
228
+ render_options = {}
229
+ if operation_results.has_errors?
230
+ render_options[:json] = content
231
+ else
232
+ # Bypasing ActiveSupport allows us to use CompiledJson objects for cached response fragments
233
+ render_options[:body] = JSON.generate(content)
234
+ end
235
+
236
+ render_options[:location] = content[:data]["links"][:self] if (
237
+ response_doc.status == :created && content[:data].class != Array
238
+ )
239
+
240
+ # For whatever reason, `render` ignores :status and :content_type when :body is set.
241
+ # But, we can just set those values directly in the Response object instead.
242
+ response.status = response_doc.status
243
+ response.headers['Content-Type'] = JSONAPI::MEDIA_TYPE
244
+
245
+ render(render_options)
246
+ end
247
+
248
+ def create_response_document(operation_results)
249
+ JSONAPI::ResponseDocument.new(
250
+ operation_results,
251
+ operation_results.has_errors? ? nil : resource_serializer,
252
+ key_formatter: key_formatter,
253
+ base_meta: base_meta,
254
+ base_links: base_response_links,
255
+ request: @request
256
+ )
257
+ end
258
+
259
+ # override this to process other exceptions
260
+ # Note: Be sure to either call super(e) or handle JSONAPI::Exceptions::Error and raise unhandled exceptions
261
+ def handle_exceptions(e)
262
+ case e
263
+ when JSONAPI::Exceptions::Error
264
+ render_errors(e.errors)
265
+ else
266
+ if JSONAPI.configuration.exception_class_whitelisted?(e)
267
+ fail e
268
+ else
269
+ (self.class.server_error_callbacks || []).each { |callback|
270
+ safe_run_callback(callback, e)
271
+ }
272
+
273
+ internal_server_error = JSONAPI::Exceptions::InternalServerError.new(e)
274
+ Rails.logger.error { "Internal Server Error: #{e.message} #{e.backtrace.join("\n")}" }
275
+ render_errors(internal_server_error.errors)
276
+ end
277
+ end
278
+ end
279
+
280
+ def safe_run_callback(callback, error)
281
+ begin
282
+ callback.call(error)
283
+ rescue => e
284
+ Rails.logger.error { "Error in error handling callback: #{e.message} #{e.backtrace.join("\n")}" }
285
+ internal_server_error = JSONAPI::Exceptions::InternalServerError.new(e)
286
+ render_errors(internal_server_error.errors)
287
+ end
288
+ end
289
+
290
+ # Pass in a methods or a block to be run when an exception is
291
+ # caught that is not a JSONAPI::Exceptions::Error
292
+ # Useful for additional logging or notification configuration that
293
+ # would normally depend on rails catching and rendering an exception.
294
+ # Ignores whitelist exceptions from config
295
+
296
+ module ClassMethods
297
+
298
+ def on_server_error(*args, &callback_block)
299
+ callbacks ||= []
300
+
301
+ if callback_block
302
+ callbacks << callback_block
303
+ end
304
+
305
+ method_callbacks = args.map do |method|
306
+ ->(error) do
307
+ if self.respond_to? method
308
+ send(method, error)
309
+ else
310
+ Rails.logger.warn("#{method} not defined on #{self}, skipping error callback")
311
+ end
312
+ end
313
+ end.compact
314
+ callbacks += method_callbacks
315
+ self.class_variable_set :@@server_error_callbacks, callbacks
316
+ end
317
+
318
+ end
319
+ end
320
+ end
@@ -0,0 +1,127 @@
1
+ module JSONAPI
2
+ class CachedResourceFragment
3
+ def self.fetch_fragments(resource_klass, serializer, context, cache_ids)
4
+ serializer_config_key = serializer.config_key(resource_klass).gsub("/", "_")
5
+ context_json = resource_klass.attribute_caching_context(context).to_json
6
+ context_b64 = JSONAPI.configuration.resource_cache_digest_function.call(context_json)
7
+ context_key = "ATTR-CTX-#{context_b64.gsub("/", "_")}"
8
+
9
+ results = self.lookup(resource_klass, serializer_config_key, context, context_key, cache_ids)
10
+
11
+ miss_ids = results.select{|k,v| v.nil? }.keys
12
+ unless miss_ids.empty?
13
+ find_filters = {resource_klass._primary_key => miss_ids.uniq}
14
+ find_options = {context: context}
15
+ resource_klass.find(find_filters, find_options).each do |resource|
16
+ (id, cr) = write(resource_klass, resource, serializer, serializer_config_key, context, context_key)
17
+ results[id] = cr
18
+ end
19
+ end
20
+
21
+ if JSONAPI.configuration.resource_cache_usage_report_function
22
+ JSONAPI.configuration.resource_cache_usage_report_function.call(
23
+ resource_klass.name,
24
+ cache_ids.size - miss_ids.size,
25
+ miss_ids.size
26
+ )
27
+ end
28
+
29
+ return results
30
+ end
31
+
32
+ attr_reader :resource_klass, :id, :type, :context, :fetchable_fields, :relationships,
33
+ :links_json, :attributes_json, :meta_json,
34
+ :preloaded_fragments
35
+
36
+ def initialize(resource_klass, id, type, context, fetchable_fields, relationships,
37
+ links_json, attributes_json, meta_json)
38
+ @resource_klass = resource_klass
39
+ @id = id
40
+ @type = type
41
+ @context = context
42
+ @fetchable_fields = Set.new(fetchable_fields)
43
+
44
+ # Relationships left uncompiled because we'll often want to insert included ids on retrieval
45
+ @relationships = relationships
46
+
47
+ @links_json = CompiledJson.of(links_json)
48
+ @attributes_json = CompiledJson.of(attributes_json)
49
+ @meta_json = CompiledJson.of(meta_json)
50
+
51
+ # A hash of hashes
52
+ @preloaded_fragments ||= Hash.new
53
+ end
54
+
55
+ def to_cache_value
56
+ {
57
+ id: id,
58
+ type: type,
59
+ fetchable: fetchable_fields,
60
+ rels: relationships,
61
+ links: links_json.try(:to_s),
62
+ attrs: attributes_json.try(:to_s),
63
+ meta: meta_json.try(:to_s)
64
+ }
65
+ end
66
+
67
+ def to_real_resource
68
+ rs = Resource.resource_for(self.type).find_by_keys([self.id], {context: self.context})
69
+ return rs.try(:first)
70
+ end
71
+
72
+ private
73
+
74
+ def self.lookup(resource_klass, serializer_config_key, context, context_key, cache_ids)
75
+ type = resource_klass._type
76
+
77
+ keys = cache_ids.map do |(id, cache_key)|
78
+ [type, id, cache_key, serializer_config_key, context_key]
79
+ end
80
+
81
+ hits = JSONAPI.configuration.resource_cache.read_multi(*keys).reject{|_,v| v.nil? }
82
+ return keys.each_with_object({}) do |key, hash|
83
+ (_, id, _, _) = key
84
+ if hits.has_key?(key)
85
+ hash[id] = self.from_cache_value(resource_klass, context, hits[key])
86
+ else
87
+ hash[id] = nil
88
+ end
89
+ end
90
+ end
91
+
92
+ def self.from_cache_value(resource_klass, context, h)
93
+ new(
94
+ resource_klass,
95
+ h.fetch(:id),
96
+ h.fetch(:type),
97
+ context,
98
+ h.fetch(:fetchable),
99
+ h.fetch(:rels, nil),
100
+ h.fetch(:links, nil),
101
+ h.fetch(:attrs, nil),
102
+ h.fetch(:meta, nil)
103
+ )
104
+ end
105
+
106
+ def self.write(resource_klass, resource, serializer, serializer_config_key, context, context_key)
107
+ (id, cache_key) = resource.cache_id
108
+ json = serializer.object_hash(resource) # No inclusions passed to object_hash
109
+ cr = self.new(
110
+ resource_klass,
111
+ json['id'],
112
+ json['type'],
113
+ context,
114
+ resource.fetchable_fields,
115
+ json['relationships'],
116
+ json['links'],
117
+ json['attributes'],
118
+ json['meta']
119
+ )
120
+
121
+ key = [resource_klass._type, id, cache_key, serializer_config_key, context_key]
122
+ JSONAPI.configuration.resource_cache.write(key, cr.to_cache_value)
123
+ return [id, cr]
124
+ end
125
+
126
+ end
127
+ end
@@ -0,0 +1,51 @@
1
+ require 'active_support/callbacks'
2
+
3
+ module JSONAPI
4
+ module Callbacks
5
+ def self.included(base)
6
+ base.class_eval do
7
+ include ActiveSupport::Callbacks
8
+ base.extend ClassMethods
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ def define_jsonapi_resources_callbacks(*callbacks)
14
+ options = callbacks.extract_options!
15
+ options = {
16
+ only: [:before, :around, :after]
17
+ }.merge!(options)
18
+
19
+ types = Array(options.delete(:only))
20
+
21
+ callbacks.each do |callback|
22
+ define_callbacks(callback, options)
23
+
24
+ types.each do |type|
25
+ send("_define_#{type}_callback", self, callback)
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def _define_before_callback(klass, callback) #:nodoc:
33
+ klass.define_singleton_method("before_#{callback}") do |*args, &block|
34
+ set_callback(:"#{callback}", :before, *args, &block)
35
+ end
36
+ end
37
+
38
+ def _define_around_callback(klass, callback) #:nodoc:
39
+ klass.define_singleton_method("around_#{callback}") do |*args, &block|
40
+ set_callback(:"#{callback}", :around, *args, &block)
41
+ end
42
+ end
43
+
44
+ def _define_after_callback(klass, callback) #:nodoc:
45
+ klass.define_singleton_method("after_#{callback}") do |*args, &block|
46
+ set_callback(:"#{callback}", :after, *args, &block)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,36 @@
1
+ module JSONAPI
2
+ class CompiledJson
3
+ def self.compile(h)
4
+ new(JSON.generate(h), h)
5
+ end
6
+
7
+ def self.of(obj)
8
+ case obj
9
+ when NilClass then nil
10
+ when CompiledJson then obj
11
+ when String then CompiledJson.new(obj)
12
+ when Hash then CompiledJson.compile(obj)
13
+ else raise "Can't figure out how to turn #{obj.inspect} into CompiledJson"
14
+ end
15
+ end
16
+
17
+ def initialize(json, h = nil)
18
+ @json = json
19
+ @h = h
20
+ end
21
+
22
+ def to_json(*args)
23
+ @json
24
+ end
25
+
26
+ def to_s
27
+ @json
28
+ end
29
+
30
+ def to_h
31
+ @h ||= JSON.parse(@json)
32
+ end
33
+
34
+ undef_method :as_json
35
+ end
36
+ end