jsonapi-rails 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 29c73976a2fb4451e84ad275247518ed54bfe660
4
- data.tar.gz: 58417d63bcfc17109fce8a6e8527ef1324c234e8
3
+ metadata.gz: c4ddfbba5b18351a7d69d74537a47c821e717397
4
+ data.tar.gz: 79ec8e0be87a8aa3110e0e927b449fbf46e85ce6
5
5
  SHA512:
6
- metadata.gz: ef952c74fbf3c00e06ce25bfc7775ff92faf25c59fd080b64a9ceec0755ba5235e8f883986f411e24bf664de0ad22fcd947ddd49492f2b6fb0c435f379c042c8
7
- data.tar.gz: 03b7cd4763b50cab7a90e357216f21fe7df4f2c892271896e76710d4e3fc084c4a7ceabdfd4e11d4cea988422a64ccb752146de9b6cb41d206eed0d9ab1c6e2f
6
+ metadata.gz: 8d2958f2136d6a613ade5d7a713dbfa7d12f4d6d233a1e23f119b2d8a2fd57d8052c9d821a2993b1377b6fa843b250cc4bf59b5205fcb345cdd98de7851083fd
7
+ data.tar.gz: e42703a777344cc33da29a6a2ccae04f68f58c4fdcf399d5e20fedc30a7033c9646941aa2bd1b5712756e008999f11c2a782ffb1438f5c82fa6ac1300f9007c6
@@ -0,0 +1,5 @@
1
+ Description:
2
+ Generates an initializer for jsonapi-rails.
3
+
4
+ Example:
5
+ `rails generate jsonapi:initializer`
@@ -0,0 +1,7 @@
1
+ class InitializerGenerator < Rails::Generators::Base
2
+ source_root File.expand_path('../templates', __FILE__)
3
+
4
+ def copy_initializer_file
5
+ copy_file 'initializer.rb', 'config/initializers/jsonapi.rb'
6
+ end
7
+ end
@@ -0,0 +1,31 @@
1
+ JSONAPI::Rails.configure do |config|
2
+ # # Set a default serializable class mapping.
3
+ # config.jsonapi_class = Hash.new { |h, k|
4
+ # names = k.to_s.split('::')
5
+ # klass = names.pop
6
+ # h[k] = [*names, "Serializable#{klass}"].join('::').safe_constantize
7
+ # }
8
+ #
9
+ # # Set a default serializable class mapping for errors.
10
+ # config.jsonapi_errors_class = Hash.new { |h, k|
11
+ # names = k.to_s.split('::')
12
+ # klass = names.pop
13
+ # h[k] = [*names, "Serializable#{klass}"].join('::').safe_constantize
14
+ # }.tap { |h|
15
+ # h[:'ActiveModel::Errors'] = JSONAPI::Rails::SerializableActiveModelErrors
16
+ # h[:Hash] = JSONAPI::Rails::SerializableErrorHash
17
+ # }
18
+ #
19
+ # # Set a default JSON API object.
20
+ # config.jsonapi_object = {
21
+ # version: '1.0'
22
+ # }
23
+ #
24
+ # # Set default exposures.
25
+ # config.jsonapi_expose = {
26
+ # url_helpers: ::Rails.application.routes.url_helpers
27
+ # }
28
+ #
29
+ # # Set a default pagination scheme.
30
+ # config.jsonapi_pagination = ->(_) { nil }
31
+ end
@@ -0,0 +1,45 @@
1
+ require 'jsonapi/rails/serializable_active_model_errors'
2
+ require 'jsonapi/rails/serializable_error_hash'
3
+
4
+ module JSONAPI
5
+ module Rails
6
+ class Configuration < ActiveSupport::InheritableOptions; end
7
+ DEFAULT_JSONAPI_CLASS = Hash.new do |h, k|
8
+ names = k.to_s.split('::')
9
+ klass = names.pop
10
+ h[k] = [*names, "Serializable#{klass}"].join('::').safe_constantize
11
+ end.freeze
12
+
13
+ DEFAULT_JSONAPI_ERRORS_CLASS = DEFAULT_JSONAPI_CLASS.dup.merge!(
14
+ 'ActiveModel::Errors'.to_sym =>
15
+ JSONAPI::Rails::SerializableActiveModelErrors,
16
+ 'Hash'.to_sym => JSONAPI::Rails::SerializableErrorHash
17
+ ).freeze
18
+
19
+ DEFAULT_JSONAPI_OBJECT = {
20
+ version: '1.0'
21
+ }.freeze
22
+
23
+ DEFAULT_JSONAPI_EXPOSE = {
24
+ url_helpers: ::Rails.application.routes.url_helpers
25
+ }.freeze
26
+
27
+ DEFAULT_JSONAPI_PAGINATION = ->(_) { nil }
28
+
29
+ DEFAULT_CONFIG = {
30
+ jsonapi_class: DEFAULT_JSONAPI_CLASS,
31
+ jsonapi_errors_class: DEFAULT_JSONAPI_ERRORS_CLASS,
32
+ jsonapi_object: DEFAULT_JSONAPI_OBJECT,
33
+ jsonapi_expose: DEFAULT_JSONAPI_EXPOSE,
34
+ jsonapi_pagination: DEFAULT_JSONAPI_PAGINATION
35
+ }.freeze
36
+
37
+ def self.configure
38
+ yield config
39
+ end
40
+
41
+ def self.config
42
+ @config ||= JSONAPI::Rails::Configuration.new(DEFAULT_CONFIG)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,126 @@
1
+ require 'jsonapi/deserializable'
2
+ require 'jsonapi/parser'
3
+ require 'jsonapi/rails/configuration'
4
+
5
+ module JSONAPI
6
+ module Rails
7
+ module Deserializable
8
+ # @private
9
+ class Resource < JSONAPI::Deserializable::Resource
10
+ id
11
+ type
12
+ attributes
13
+ has_one do |_rel, id, type, key|
14
+ type = type.to_s.singularize.camelize
15
+ { "#{key}_id".to_sym => id, "#{key}_type".to_sym => type }
16
+ end
17
+ has_many do |_rel, ids, types, key|
18
+ key = key.to_s.singularize
19
+ types = types.map { |t| t.to_s.singularize.camelize }
20
+ { "#{key}_ids".to_sym => ids, "#{key}_types".to_sym => types }
21
+ end
22
+ end
23
+ end
24
+
25
+ # ActionController methods and hooks for JSON API deserialization and
26
+ # rendering.
27
+ module Controller
28
+ extend ActiveSupport::Concern
29
+
30
+ JSONAPI_POINTERS_KEY = 'jsonapi-rails.jsonapi_pointers'.freeze
31
+
32
+ class_methods do
33
+ # Declare a deserializable resource.
34
+ #
35
+ # @param key [Symbol] The key under which the deserialized hash will be
36
+ # available within the `params` hash.
37
+ # @param options [Hash]
38
+ # @option class [Class] A custom deserializer class. Optional.
39
+ # @option only List of actions for which deserialization should happen.
40
+ # Optional.
41
+ # @option except List of actions for which deserialization should not
42
+ # happen. Optional.
43
+ # @yieldreturn Optional block for in-line definition of custom
44
+ # deserializers.
45
+ #
46
+ # @example
47
+ # class ArticlesController < ActionController::Base
48
+ # deserializable_resource :article, only: [:create, :update]
49
+ #
50
+ # def create
51
+ # article = Article.new(params[:article])
52
+ #
53
+ # if article.save
54
+ # render jsonapi: article
55
+ # else
56
+ # render jsonapi_errors: article.errors
57
+ # end
58
+ # end
59
+ #
60
+ # # ...
61
+ # end
62
+ #
63
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
64
+ def deserializable_resource(key, options = {}, &block)
65
+ options = options.dup
66
+ klass = options.delete(:class) ||
67
+ Class.new(JSONAPI::Rails::Deserializable::Resource, &block)
68
+
69
+ before_action(options) do |controller|
70
+ # TODO(lucas): Fail with helpful error message if _jsonapi not
71
+ # present.
72
+ hash = controller.params[:_jsonapi].to_unsafe_hash
73
+ ActiveSupport::Notifications
74
+ .instrument('parse.jsonapi', payload: hash, class: klass) do
75
+ JSONAPI::Parser::Resource.parse!(hash)
76
+ resource = klass.new(hash[:data])
77
+ controller.request.env[JSONAPI_POINTERS_KEY] =
78
+ resource.reverse_mapping
79
+ controller.params[key.to_sym] = resource.to_hash
80
+ end
81
+ end
82
+ end
83
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
84
+ end
85
+
86
+ # Hook for serializable class mapping (for resources).
87
+ # Overridden by the `class` renderer option.
88
+ # @return [Hash{Symbol=>Class}]
89
+ def jsonapi_class
90
+ JSONAPI::Rails.config[:jsonapi_class]
91
+ end
92
+
93
+ # Hook for serializable class mapping (for errors).
94
+ # Overridden by the `class` renderer option.
95
+ # @return [Hash{Symbol=>Class}]
96
+ def jsonapi_errors_class
97
+ JSONAPI::Rails.config[:jsonapi_errors_class]
98
+ end
99
+
100
+ # Hook for the jsonapi object.
101
+ # Overridden by the `jsonapi_object` renderer option.
102
+ # @return [Hash]
103
+ def jsonapi_object
104
+ JSONAPI::Rails.config[:jsonapi_object]
105
+ end
106
+
107
+ # Hook for default exposures.
108
+ # @return [Hash]
109
+ def jsonapi_expose
110
+ JSONAPI::Rails.config[:jsonapi_expose]
111
+ end
112
+
113
+ # Hook for pagination scheme.
114
+ # @return [Hash]
115
+ def jsonapi_pagination(resources)
116
+ instance_exec(resources, &JSONAPI::Rails.config[:jsonapi_pagination])
117
+ end
118
+
119
+ # JSON pointers for deserialized fields.
120
+ # @return [Hash{Symbol=>String}]
121
+ def jsonapi_pointers
122
+ request.env[JSONAPI_POINTERS_KEY] || {}
123
+ end
124
+ end
125
+ end
126
+ end
@@ -2,61 +2,66 @@ require 'rails/railtie'
2
2
  require 'action_controller'
3
3
  require 'active_support'
4
4
 
5
- require 'jsonapi/rails/parser'
6
5
  require 'jsonapi/rails/renderer'
7
6
 
8
7
  module JSONAPI
9
8
  module Rails
9
+ # @private
10
10
  class Railtie < ::Rails::Railtie
11
11
  MEDIA_TYPE = 'application/vnd.api+json'.freeze
12
+ PARSER = lambda do |body|
13
+ data = JSON.parse(body)
14
+ hash = { _jsonapi: data }
15
+
16
+ hash.with_indifferent_access
17
+ end
12
18
  RENDERERS = {
13
- jsonapi: SuccessRenderer.new,
14
- jsonapi_error: ErrorsRenderer.new
19
+ jsonapi: SuccessRenderer.new,
20
+ jsonapi_errors: ErrorsRenderer.new
15
21
  }.freeze
16
22
 
17
- initializer 'jsonapi-rails.action_controller' do
23
+ initializer 'jsonapi-rails.init' do
24
+ register_mime_type
25
+ register_parameter_parser
26
+ register_renderers
18
27
  ActiveSupport.on_load(:action_controller) do
19
- require 'jsonapi/rails/action_controller'
20
- include ::JSONAPI::Rails::ActionController
21
-
22
- Mime::Type.register MEDIA_TYPE, :jsonapi
23
-
24
- if ::Rails::VERSION::MAJOR >= 5
25
- ::ActionDispatch::Request.parameter_parsers[:jsonapi] = PARSER
26
- else
27
- ::ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime[:jsonapi]] = PARSER
28
- end
28
+ require 'jsonapi/rails/controller'
29
+ include ::JSONAPI::Rails::Controller
30
+ end
31
+ end
29
32
 
30
- ::ActionController::Renderers.add(:jsonapi) do |resources, options|
31
- self.content_type ||= Mime[:jsonapi]
33
+ private
32
34
 
33
- RENDERERS[:jsonapi].render(resources, options).to_json
34
- end
35
-
36
- ::ActionController::Renderers.add(:jsonapi_error) do |errors, options|
37
- # Renderer proc is evaluated in the controller context, so it
38
- # has access to the jsonapi_pointers method.
39
- options = options.merge(_jsonapi_pointers: jsonapi_pointers)
40
- self.content_type ||= Mime[:jsonapi]
35
+ def register_mime_type
36
+ Mime::Type.register(MEDIA_TYPE, :jsonapi)
37
+ end
41
38
 
42
- RENDERERS[:jsonapi_error].render(errors, options).to_json
43
- end
39
+ def register_parameter_parser
40
+ if ::Rails::VERSION::MAJOR >= 5
41
+ ActionDispatch::Request.parameter_parsers[:jsonapi] = PARSER
42
+ else
43
+ ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime[:jsonapi]] = PARSER
44
+ end
45
+ end
44
46
 
45
- JSONAPI::Deserializable::Resource.configure do |config|
46
- config.default_has_one do |key, _rel, id, type|
47
- key = key.to_s.singularize
48
- type = type.to_s.singularize.camelize
49
- { "#{key}_id".to_sym => id, "#{key}_type".to_sym => type }
50
- end
47
+ # rubocop:disable Metrics/MethodLength
48
+ def register_renderers
49
+ ActiveSupport.on_load(:action_controller) do
50
+ RENDERERS.each do |name, renderer|
51
+ ::ActionController::Renderers.add(name) do |resources, options|
52
+ # Renderer proc is evaluated in the controller context.
53
+ self.content_type ||= Mime[:jsonapi]
51
54
 
52
- config.default_has_many do |key, _rel, ids, types|
53
- key = key.to_s.singularize
54
- types = types.map { |t| t.to_s.singularize.camelize }
55
- { "#{key}_ids".to_sym => ids, "#{key}_types".to_sym => types }
55
+ ActiveSupport::Notifications.instrument('render.jsonapi',
56
+ resources: resources,
57
+ options: options) do
58
+ renderer.render(resources, options, self).to_json
59
+ end
56
60
  end
57
61
  end
58
62
  end
59
63
  end
64
+ # rubocop:enable Metrics/MethodLength
60
65
  end
61
66
  end
62
67
  end
@@ -2,36 +2,63 @@ require 'jsonapi/serializable/renderer'
2
2
 
3
3
  module JSONAPI
4
4
  module Rails
5
+ # @private
5
6
  class SuccessRenderer
6
- def initialize(renderer = JSONAPI::Serializable::SuccessRenderer.new)
7
+ def initialize(renderer = JSONAPI::Serializable::Renderer.new)
7
8
  @renderer = renderer
8
9
 
9
10
  freeze
10
11
  end
11
12
 
12
- def render(resources, options)
13
- opts = options.dup
14
- # TODO(beauby): Move this to a global configuration.
15
- default_exposures = {
16
- url_helpers: ::Rails.application.routes.url_helpers
17
- }
18
- opts[:expose] = default_exposures.merge!(opts[:expose] || {})
19
- opts[:jsonapi] = opts.delete(:jsonapi_object)
13
+ def render(resources, options, controller)
14
+ options = default_options(options, controller, resources)
20
15
 
21
- @renderer.render(resources, opts)
16
+ @renderer.render(resources, options)
17
+ end
18
+
19
+ private
20
+
21
+ def default_options(options, controller, resources)
22
+ options.dup.tap do |opts|
23
+ opts[:class] ||= controller.jsonapi_class
24
+ if (pagination_links = controller.jsonapi_pagination(resources))
25
+ opts[:links] = (opts[:links] || {}).merge(pagination_links)
26
+ end
27
+ opts[:expose] = controller.jsonapi_expose.merge(opts[:expose] || {})
28
+ opts[:jsonapi] = opts.delete(:jsonapi_object) ||
29
+ controller.jsonapi_object
30
+ end
22
31
  end
23
32
  end
24
33
 
34
+ # @private
25
35
  class ErrorsRenderer
26
- def initialize(renderer = JSONAPI::Serializable::ErrorsRenderer.new)
36
+ def initialize(renderer = JSONAPI::Serializable::Renderer.new)
27
37
  @renderer = renderer
28
38
 
29
39
  freeze
30
40
  end
31
41
 
32
- def render(errors, options)
33
- # TODO(beauby): SerializableError inference on AR validation errors.
34
- @renderer.render(errors, options)
42
+ def render(errors, options, controller)
43
+ options = default_options(options, controller)
44
+
45
+ errors = [errors] unless errors.is_a?(Array)
46
+
47
+ @renderer.render_errors(errors, options)
48
+ end
49
+
50
+ private
51
+
52
+ def default_options(options, controller)
53
+ options.dup.tap do |opts|
54
+ opts[:class] ||= controller.jsonapi_errors_class
55
+ opts[:expose] =
56
+ controller.jsonapi_expose
57
+ .merge(opts[:expose] || {})
58
+ .merge!(_jsonapi_pointers: controller.jsonapi_pointers)
59
+ opts[:jsonapi] = opts.delete(:jsonapi_object) ||
60
+ controller.jsonapi_object
61
+ end
35
62
  end
36
63
  end
37
64
  end
@@ -0,0 +1,38 @@
1
+ module JSONAPI
2
+ module Rails
3
+ # @private
4
+ class SerializableActiveModelError < Serializable::Error
5
+ title do
6
+ "Invalid #{@field}" unless @field.nil?
7
+ end
8
+
9
+ detail do
10
+ @message
11
+ end
12
+
13
+ source do
14
+ pointer @pointer unless @pointer.nil?
15
+ end
16
+ end
17
+
18
+ # @private
19
+ class SerializableActiveModelErrors
20
+ def initialize(exposures)
21
+ @errors = exposures[:object]
22
+ @reverse_mapping = exposures[:_jsonapi_pointers] || {}
23
+
24
+ freeze
25
+ end
26
+
27
+ def as_jsonapi
28
+ @errors.keys.flat_map do |key|
29
+ @errors.full_messages_for(key).map do |message|
30
+ SerializableActiveModelError.new(field: key, message: message,
31
+ pointer: @reverse_mapping[key])
32
+ .as_jsonapi
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,13 @@
1
+ module JSONAPI
2
+ module Rails
3
+ # @private
4
+ class SerializableErrorHash < JSONAPI::Serializable::Error
5
+ def initialize(exposures)
6
+ super
7
+ exposures[:object].each do |k, v|
8
+ instance_variable_set("@_#{k}", v)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonapi-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucas Hosseini
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-07-22 00:00:00.000000000 Z
11
+ date: 2017-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jsonapi-rb
@@ -16,14 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.2.1
19
+ version: 0.5.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.2.1
26
+ version: 0.5.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: jsonapi-parser
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.1.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.1.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rails
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +94,20 @@ dependencies:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
96
  version: '3.5'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  description: Efficient, convenient, non-intrusive JSONAPI framework for Rails.
84
112
  email:
85
113
  - lucas.hosseini@gmail.com
@@ -88,14 +116,19 @@ extensions: []
88
116
  extra_rdoc_files: []
89
117
  files:
90
118
  - README.md
119
+ - lib/generators/jsonapi/initializer/USAGE
120
+ - lib/generators/jsonapi/initializer/initializer_generator.rb
121
+ - lib/generators/jsonapi/initializer/templates/initializer.rb
91
122
  - lib/generators/jsonapi/serializable/USAGE
92
123
  - lib/generators/jsonapi/serializable/serializable_generator.rb
93
124
  - lib/generators/jsonapi/serializable/templates/serializable.rb.erb
94
125
  - lib/jsonapi/rails.rb
95
- - lib/jsonapi/rails/action_controller.rb
96
- - lib/jsonapi/rails/parser.rb
126
+ - lib/jsonapi/rails/configuration.rb
127
+ - lib/jsonapi/rails/controller.rb
97
128
  - lib/jsonapi/rails/railtie.rb
98
129
  - lib/jsonapi/rails/renderer.rb
130
+ - lib/jsonapi/rails/serializable_active_model_errors.rb
131
+ - lib/jsonapi/rails/serializable_error_hash.rb
99
132
  homepage: https://github.com/jsonapi-rb/jsonapi-rails
100
133
  licenses:
101
134
  - MIT
@@ -116,7 +149,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
149
  version: '0'
117
150
  requirements: []
118
151
  rubyforge_project:
119
- rubygems_version: 2.6.12
152
+ rubygems_version: 2.6.13
120
153
  signing_key:
121
154
  specification_version: 4
122
155
  summary: jsonapi-rb integrations for Rails.
@@ -1,41 +0,0 @@
1
- require 'jsonapi/deserializable'
2
- require 'jsonapi/parser'
3
-
4
- module JSONAPI
5
- module Rails
6
- module ActionController
7
- extend ActiveSupport::Concern
8
-
9
- JSONAPI_POINTERS_KEY = 'jsonapi_deserializable.jsonapi_pointers'.freeze
10
-
11
- class_methods do
12
- def deserializable_resource(key, options = {}, &block)
13
- _deserializable(key, options,
14
- JSONAPI::Deserializable::Resource, &block)
15
- end
16
-
17
- def deserializable_relationship(key, options = {}, &block)
18
- _deserializable(key, options,
19
- JSONAPI::Deserializable::Relationship, &block)
20
- end
21
-
22
- # @api private
23
- def _deserializable(key, options, fallback, &block)
24
- options = options.dup
25
- klass = options.delete(:class) || Class.new(fallback, &block)
26
-
27
- before_action(options) do |controller|
28
- resource = klass.new(controller.params[:_jsonapi].to_unsafe_hash)
29
- controller.request.env[JSONAPI_POINTERS_KEY] =
30
- resource.reverse_mapping
31
- controller.params[key.to_sym] = resource.to_hash
32
- end
33
- end
34
- end
35
-
36
- def jsonapi_pointers
37
- request.env[JSONAPI_POINTERS_KEY]
38
- end
39
- end
40
- end
41
- end
@@ -1,12 +0,0 @@
1
- require 'jsonapi/deserializable'
2
-
3
- module JSONAPI
4
- module Rails
5
- PARSER = lambda do |body|
6
- data = JSON.parse(body)
7
- hash = { _jsonapi: data }
8
-
9
- hash.with_indifferent_access
10
- end
11
- end
12
- end