jsonapi_compliable 0.3.4

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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +5 -0
  6. data/Appraisals +7 -0
  7. data/CODE_OF_CONDUCT.md +49 -0
  8. data/Gemfile +13 -0
  9. data/Guardfile +32 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +20 -0
  12. data/Rakefile +6 -0
  13. data/bin/appraisal +17 -0
  14. data/bin/console +14 -0
  15. data/bin/rspec +17 -0
  16. data/bin/setup +8 -0
  17. data/gemfiles/rails_3.gemfile +9 -0
  18. data/gemfiles/rails_4.gemfile +16 -0
  19. data/gemfiles/rails_4.gemfile.lock +217 -0
  20. data/gemfiles/rails_5.gemfile +16 -0
  21. data/gemfiles/rails_5.gemfile.lock +222 -0
  22. data/jsonapi_compliable.gemspec +33 -0
  23. data/lib/jsonapi_compliable.rb +30 -0
  24. data/lib/jsonapi_compliable/base.rb +105 -0
  25. data/lib/jsonapi_compliable/deserializable.rb +122 -0
  26. data/lib/jsonapi_compliable/dsl.rb +60 -0
  27. data/lib/jsonapi_compliable/errors.rb +15 -0
  28. data/lib/jsonapi_compliable/scope/base.rb +40 -0
  29. data/lib/jsonapi_compliable/scope/default_filter.rb +25 -0
  30. data/lib/jsonapi_compliable/scope/extra_fields.rb +35 -0
  31. data/lib/jsonapi_compliable/scope/filter.rb +32 -0
  32. data/lib/jsonapi_compliable/scope/filterable.rb +23 -0
  33. data/lib/jsonapi_compliable/scope/paginate.rb +40 -0
  34. data/lib/jsonapi_compliable/scope/sideload.rb +25 -0
  35. data/lib/jsonapi_compliable/scope/sort.rb +29 -0
  36. data/lib/jsonapi_compliable/util/field_params.rb +17 -0
  37. data/lib/jsonapi_compliable/util/include_params.rb +28 -0
  38. data/lib/jsonapi_compliable/util/scoping.rb +20 -0
  39. data/lib/jsonapi_compliable/version.rb +3 -0
  40. metadata +258 -0
@@ -0,0 +1,16 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "http://artprod.dev.bloomberg.com/artifactory/api/gems/rubygems/"
4
+
5
+ gem "active_model_serializers", :git => "https://github.com/richmolj/active_model_serializers.git"
6
+ gem "rails", "~> 5.0"
7
+
8
+ group :test do
9
+ gem "appraisal"
10
+ gem "guard"
11
+ gem "guard-rspec"
12
+ gem "pry"
13
+ gem "pry-byebug"
14
+ end
15
+
16
+ gemspec :path => "../"
@@ -0,0 +1,222 @@
1
+ GIT
2
+ remote: https://github.com/richmolj/active_model_serializers.git
3
+ revision: 1dc2b74059731339f4df3e7882fd949e20433013
4
+ specs:
5
+ active_model_serializers (0.10.2)
6
+ actionpack (>= 4.1, < 6)
7
+ activemodel (>= 4.1, < 6)
8
+ jsonapi (~> 0.1.1.beta2)
9
+ railties (>= 4.1, < 6)
10
+
11
+ PATH
12
+ remote: ../
13
+ specs:
14
+ jsonapi_compliable (0.3.2)
15
+ active_model_serializers (~> 0.10)
16
+ jsonapi (~> 0.1.1.beta2)
17
+ jsonapi_ams_extensions (~> 0.1)
18
+ rails (>= 4.1, < 6)
19
+
20
+ GEM
21
+ remote: http://artprod.dev.bloomberg.com/artifactory/api/gems/rubygems/
22
+ specs:
23
+ actioncable (5.0.0.1)
24
+ actionpack (= 5.0.0.1)
25
+ nio4r (~> 1.2)
26
+ websocket-driver (~> 0.6.1)
27
+ actionmailer (5.0.0.1)
28
+ actionpack (= 5.0.0.1)
29
+ actionview (= 5.0.0.1)
30
+ activejob (= 5.0.0.1)
31
+ mail (~> 2.5, >= 2.5.4)
32
+ rails-dom-testing (~> 2.0)
33
+ actionpack (5.0.0.1)
34
+ actionview (= 5.0.0.1)
35
+ activesupport (= 5.0.0.1)
36
+ rack (~> 2.0)
37
+ rack-test (~> 0.6.3)
38
+ rails-dom-testing (~> 2.0)
39
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
40
+ actionview (5.0.0.1)
41
+ activesupport (= 5.0.0.1)
42
+ builder (~> 3.1)
43
+ erubis (~> 2.7.0)
44
+ rails-dom-testing (~> 2.0)
45
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
46
+ activejob (5.0.0.1)
47
+ activesupport (= 5.0.0.1)
48
+ globalid (>= 0.3.6)
49
+ activemodel (5.0.0.1)
50
+ activesupport (= 5.0.0.1)
51
+ activerecord (5.0.0.1)
52
+ activemodel (= 5.0.0.1)
53
+ activesupport (= 5.0.0.1)
54
+ arel (~> 7.0)
55
+ activesupport (5.0.0.1)
56
+ concurrent-ruby (~> 1.0, >= 1.0.2)
57
+ i18n (~> 0.7)
58
+ minitest (~> 5.1)
59
+ tzinfo (~> 1.1)
60
+ appraisal (2.1.0)
61
+ bundler
62
+ rake
63
+ thor (>= 0.14.0)
64
+ arel (7.1.1)
65
+ builder (3.2.2)
66
+ byebug (9.0.5)
67
+ coderay (1.1.1)
68
+ concurrent-ruby (1.0.2)
69
+ database_cleaner (1.5.3)
70
+ diff-lcs (1.2.5)
71
+ erubis (2.7.0)
72
+ ffi (1.9.14)
73
+ formatador (0.2.5)
74
+ globalid (0.3.7)
75
+ activesupport (>= 4.1.0)
76
+ guard (2.14.0)
77
+ formatador (>= 0.2.4)
78
+ listen (>= 2.7, < 4.0)
79
+ lumberjack (~> 1.0)
80
+ nenv (~> 0.1)
81
+ notiffany (~> 0.0)
82
+ pry (>= 0.9.12)
83
+ shellany (~> 0.0)
84
+ thor (>= 0.18.1)
85
+ guard-compat (1.2.1)
86
+ guard-rspec (4.7.3)
87
+ guard (~> 2.1)
88
+ guard-compat (~> 1.1)
89
+ rspec (>= 2.99.0, < 4.0)
90
+ i18n (0.7.0)
91
+ json (1.8.3)
92
+ jsonapi (0.1.1.beta2)
93
+ json (~> 1.8)
94
+ jsonapi_ams_extensions (0.1.0)
95
+ active_model_serializers (~> 0.10.x)
96
+ jsonapi_spec_helpers (0.2.0)
97
+ kaminari (0.17.0)
98
+ actionpack (>= 3.0.0)
99
+ activesupport (>= 3.0.0)
100
+ listen (3.1.5)
101
+ rb-fsevent (~> 0.9, >= 0.9.4)
102
+ rb-inotify (~> 0.9, >= 0.9.7)
103
+ ruby_dep (~> 1.2)
104
+ loofah (2.0.3)
105
+ nokogiri (>= 1.5.9)
106
+ lumberjack (1.0.10)
107
+ mail (2.6.4)
108
+ mime-types (>= 1.16, < 4)
109
+ method_source (0.8.2)
110
+ mime-types (3.1)
111
+ mime-types-data (~> 3.2015)
112
+ mime-types-data (3.2016.0521)
113
+ mini_portile2 (2.1.0)
114
+ minitest (5.9.0)
115
+ nenv (0.3.0)
116
+ nio4r (1.2.1)
117
+ nokogiri (1.6.8)
118
+ mini_portile2 (~> 2.1.0)
119
+ pkg-config (~> 1.1.7)
120
+ notiffany (0.1.1)
121
+ nenv (~> 0.1)
122
+ shellany (~> 0.0)
123
+ pkg-config (1.1.7)
124
+ pry (0.10.4)
125
+ coderay (~> 1.1.0)
126
+ method_source (~> 0.8.1)
127
+ slop (~> 3.4)
128
+ pry-byebug (3.4.0)
129
+ byebug (~> 9.0)
130
+ pry (~> 0.10)
131
+ rack (2.0.1)
132
+ rack-test (0.6.3)
133
+ rack (>= 1.0)
134
+ rails (5.0.0.1)
135
+ actioncable (= 5.0.0.1)
136
+ actionmailer (= 5.0.0.1)
137
+ actionpack (= 5.0.0.1)
138
+ actionview (= 5.0.0.1)
139
+ activejob (= 5.0.0.1)
140
+ activemodel (= 5.0.0.1)
141
+ activerecord (= 5.0.0.1)
142
+ activesupport (= 5.0.0.1)
143
+ bundler (>= 1.3.0, < 2.0)
144
+ railties (= 5.0.0.1)
145
+ sprockets-rails (>= 2.0.0)
146
+ rails-dom-testing (2.0.1)
147
+ activesupport (>= 4.2.0, < 6.0)
148
+ nokogiri (~> 1.6.0)
149
+ rails-html-sanitizer (1.0.3)
150
+ loofah (~> 2.0)
151
+ railties (5.0.0.1)
152
+ actionpack (= 5.0.0.1)
153
+ activesupport (= 5.0.0.1)
154
+ method_source
155
+ rake (>= 0.8.7)
156
+ thor (>= 0.18.1, < 2.0)
157
+ rake (10.5.0)
158
+ rb-fsevent (0.9.7)
159
+ rb-inotify (0.9.7)
160
+ ffi (>= 0.5.0)
161
+ rspec (3.5.0)
162
+ rspec-core (~> 3.5.0)
163
+ rspec-expectations (~> 3.5.0)
164
+ rspec-mocks (~> 3.5.0)
165
+ rspec-core (3.5.3)
166
+ rspec-support (~> 3.5.0)
167
+ rspec-expectations (3.5.0)
168
+ diff-lcs (>= 1.2.0, < 2.0)
169
+ rspec-support (~> 3.5.0)
170
+ rspec-mocks (3.5.0)
171
+ diff-lcs (>= 1.2.0, < 2.0)
172
+ rspec-support (~> 3.5.0)
173
+ rspec-rails (3.5.2)
174
+ actionpack (>= 3.0)
175
+ activesupport (>= 3.0)
176
+ railties (>= 3.0)
177
+ rspec-core (~> 3.5.0)
178
+ rspec-expectations (~> 3.5.0)
179
+ rspec-mocks (~> 3.5.0)
180
+ rspec-support (~> 3.5.0)
181
+ rspec-support (3.5.0)
182
+ ruby_dep (1.4.0)
183
+ shellany (0.0.1)
184
+ slop (3.6.0)
185
+ sprockets (3.7.0)
186
+ concurrent-ruby (~> 1.0)
187
+ rack (> 1, < 3)
188
+ sprockets-rails (3.2.0)
189
+ actionpack (>= 4.0)
190
+ activesupport (>= 4.0)
191
+ sprockets (>= 3.0.0)
192
+ sqlite3 (1.3.11)
193
+ thor (0.19.1)
194
+ thread_safe (0.3.5)
195
+ tzinfo (1.2.2)
196
+ thread_safe (~> 0.1)
197
+ websocket-driver (0.6.4)
198
+ websocket-extensions (>= 0.1.0)
199
+ websocket-extensions (0.1.2)
200
+
201
+ PLATFORMS
202
+ ruby
203
+
204
+ DEPENDENCIES
205
+ active_model_serializers!
206
+ appraisal
207
+ bundler (~> 1.12)
208
+ database_cleaner
209
+ guard
210
+ guard-rspec
211
+ jsonapi_compliable!
212
+ jsonapi_spec_helpers
213
+ kaminari
214
+ pry
215
+ pry-byebug
216
+ rails (~> 5.0)
217
+ rake (~> 10.0)
218
+ rspec-rails
219
+ sqlite3
220
+
221
+ BUNDLED WITH
222
+ 1.12.5
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'jsonapi_compliable/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "jsonapi_compliable"
8
+ spec.version = JsonapiCompliable::VERSION
9
+ spec.authors = ["Lee Richmond", "Venkata Pasupuleti"]
10
+ spec.email = ["richmolj@gmail.com", "spasupuleti4@bloomberg.net"]
11
+
12
+ spec.summary = %q{JSON Compliable serializer for action controller}
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "rails", ['>= 4.1', '< 6']
21
+ spec.add_dependency "jsonapi", '~> 0.1.1.beta2'
22
+ spec.add_dependency "active_model_serializers", "~> 0.10"
23
+ spec.add_dependency "jsonapi_ams_extensions", "~> 0.1"
24
+
25
+ spec.add_development_dependency "kaminari"
26
+ spec.add_development_dependency "active_model_serializers"
27
+ spec.add_development_dependency "jsonapi_spec_helpers"
28
+ spec.add_development_dependency "bundler", "~> 1.12"
29
+ spec.add_development_dependency "rake", "~> 10.0"
30
+ spec.add_development_dependency "rspec-rails"
31
+ spec.add_development_dependency "sqlite3"
32
+ spec.add_development_dependency "database_cleaner"
33
+ end
@@ -0,0 +1,30 @@
1
+ require 'active_model_serializers'
2
+ require 'jsonapi'
3
+ require 'jsonapi_ams_extensions'
4
+
5
+ require "jsonapi_compliable/version"
6
+ require "jsonapi_compliable/errors"
7
+ require "jsonapi_compliable/dsl"
8
+ require "jsonapi_compliable/scope/base"
9
+ require "jsonapi_compliable/scope/sort"
10
+ require "jsonapi_compliable/scope/paginate"
11
+ require "jsonapi_compliable/scope/sideload"
12
+ require "jsonapi_compliable/scope/extra_fields"
13
+ require "jsonapi_compliable/scope/filterable"
14
+ require "jsonapi_compliable/scope/default_filter"
15
+ require "jsonapi_compliable/scope/filter"
16
+ require "jsonapi_compliable/util/include_params"
17
+ require "jsonapi_compliable/util/field_params"
18
+ require "jsonapi_compliable/util/scoping"
19
+
20
+ module JsonapiCompliable
21
+ autoload :Base, 'jsonapi_compliable/base'
22
+ autoload :Deserializable, 'jsonapi_compliable/deserializable'
23
+
24
+ def self.included(klass)
25
+ klass.instance_eval do
26
+ include Base
27
+ include Deserializable
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,105 @@
1
+ module JsonapiCompliable
2
+ module Base
3
+ extend ActiveSupport::Concern
4
+ include Deserializable
5
+
6
+ MAX_PAGE_SIZE = 1_000
7
+
8
+ included do
9
+ class_attribute :_jsonapi_compliable
10
+ attr_reader :_jsonapi_scoped
11
+
12
+ before_action :parse_fieldsets!
13
+ after_action :reset_scope_flag
14
+ end
15
+
16
+ def default_page_number
17
+ 1
18
+ end
19
+
20
+ def default_page_size
21
+ 20
22
+ end
23
+
24
+ def jsonapi_scope(scope,
25
+ filter: true,
26
+ includes: true,
27
+ paginate: true,
28
+ extra_fields: true,
29
+ sort: true)
30
+ scope = JsonapiCompliable::Scope::DefaultFilter.new(self, scope).apply
31
+ scope = JsonapiCompliable::Scope::Filter.new(self, scope).apply if filter
32
+ scope = JsonapiCompliable::Scope::ExtraFields.new(self, scope).apply if extra_fields
33
+ scope = JsonapiCompliable::Scope::Sideload.new(self, scope).apply if includes
34
+ scope = JsonapiCompliable::Scope::Sort.new(self, scope).apply if sort
35
+ scope = JsonapiCompliable::Scope::Paginate.new(self, scope).apply if paginate
36
+ @_jsonapi_scoped = true
37
+ scope
38
+ end
39
+
40
+ def reset_scope_flag
41
+ @_jsonapi_scoped = false
42
+ end
43
+
44
+ def parse_fieldsets!
45
+ Util::FieldParams.parse!(params, :fields)
46
+ Util::FieldParams.parse!(params, :extra_fields)
47
+ end
48
+
49
+ def render_ams(scope, opts = {})
50
+ scope = jsonapi_scope(scope) if Util::Scoping.apply?(self, scope, opts.delete(:scope))
51
+ options = default_ams_options
52
+ options[:include] = forced_includes || Util::IncludeParams.scrub(self)
53
+ options[:json] = scope
54
+ options[:fields] = Util::FieldParams.fieldset(params, :fields) if params[:fields]
55
+ options[:extra_fields] = Util::FieldParams.fieldset(params, :extra_fields) if params[:extra_fields]
56
+
57
+ options.merge!(opts)
58
+ render(options)
59
+ end
60
+
61
+ # render_ams(foo) equivalent to
62
+ # render json: foo, ams_default_options
63
+ def default_ams_options
64
+ {}.tap do |options|
65
+ options[:adapter] = :json_api
66
+ end
67
+ end
68
+
69
+ # TODO: This nastiness likely goes away once jsonapi standardizes
70
+ # a spec for nested relationships.
71
+ # See: https://github.com/json-api/json-api/issues/1089
72
+ def forced_includes(data = nil)
73
+ return unless force_includes?
74
+ data = raw_params[:data] unless data
75
+
76
+ {}.tap do |forced|
77
+ (data[:relationships] || {}).each_pair do |relation_name, relation|
78
+ if relation[:data].is_a?(Array)
79
+ forced[relation_name] = {}
80
+ relation[:data].each do |datum|
81
+ forced[relation_name].deep_merge!(forced_includes(datum))
82
+ end
83
+ else
84
+ forced[relation_name] = forced_includes(relation[:data])
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ def force_includes?
91
+ %w(PUT PATCH POST).include?(request.method) and
92
+ raw_params[:data][:relationships].present?
93
+ end
94
+
95
+ module ClassMethods
96
+ def jsonapi(&blk)
97
+ if !self._jsonapi_compliable
98
+ dsl = JsonapiCompliable::DSL.new
99
+ self._jsonapi_compliable = dsl
100
+ end
101
+ self._jsonapi_compliable.instance_eval(&blk)
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,122 @@
1
+ # This will convert JSONAPI-compatible POST/PUT payloads
2
+ # into something Rails better understands. Example:
3
+ #
4
+ # {
5
+ # "data": {
6
+ # "type": "articles",
7
+ # "attributes": { "title": "the first article" },
8
+ # "relationships": {
9
+ # "tags": {
10
+ # "data": [{
11
+ # "type": "tags",
12
+ # "attributes": { "name": "One" }
13
+ # }, {
14
+ # "type": "tags",
15
+ # "attributes": { "name": "Two" }
16
+ # }]
17
+ # }
18
+ # }
19
+ # }
20
+ # }
21
+ #
22
+ # Into:
23
+ #
24
+ # {
25
+ # article: {
26
+ # title: 'the first article',
27
+ # tags_attributes: [
28
+ # { name: 'One' },
29
+ # { name: 'Two' },
30
+ # ]
31
+ # }
32
+ # }
33
+ #
34
+ # Why we don't use AMS deserialization - AMS will:
35
+ # * not support relationship data
36
+ # * override foreign key incorrectly, ie
37
+ # post_id incorrectly becomes nil if post relation is nil,
38
+ # even if it is in the attributes payload
39
+ #
40
+ # Usage:
41
+ #
42
+ # In controller:
43
+ #
44
+ # before_action :deserialize_jsonapi!, only: [:my_action]
45
+
46
+ module JsonapiCompliable
47
+ module Deserializable
48
+ extend ActiveSupport::Concern
49
+
50
+ included do
51
+ attr_accessor :raw_params
52
+ end
53
+
54
+ class Deserialization
55
+ def initialize(params, namespace: true)
56
+ @params = params
57
+ @namespace = namespace
58
+ end
59
+
60
+ def deserialize
61
+ hash = attributes
62
+ hash = hash.merge(relationships)
63
+ hash = @namespace ? { parsed_type => hash } : hash
64
+ hash.merge(@params.except(:data)).deep_symbolize_keys
65
+ end
66
+
67
+ private
68
+
69
+ def parsed_type
70
+ @params[:data][:type].underscore.singularize.to_sym
71
+ end
72
+
73
+ def attributes
74
+ attrs = {}
75
+ attrs[:id] = @params[:data].try(:[], :id) if @params[:data].try(:[], :id)
76
+ attrs.merge!(@params[:data].try(:[], :attributes) || {})
77
+ attrs
78
+ end
79
+
80
+ def relationships
81
+ return {} if @params[:data].try(:[], :relationships).blank?
82
+
83
+ {}.tap do |hash|
84
+ @params[:data][:relationships].each_pair do |relationship_name, payload|
85
+ parsed_relation = parse_relation(payload)
86
+
87
+ if parsed_relation.present?
88
+ hash["#{relationship_name}_attributes".to_sym] = parsed_relation
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ def parse_relation(payload)
95
+ if payload[:data].is_a?(Array)
96
+ parse_has_many(payload[:data])
97
+ else
98
+ parse_belongs_to(payload)
99
+ end
100
+ end
101
+
102
+ def parse_belongs_to(payload)
103
+ self.class.new(payload, namespace: false).deserialize
104
+ end
105
+
106
+ def parse_has_many(payloads)
107
+ payloads.map do |payload|
108
+ payload = { data: payload }
109
+ self.class.new(payload, namespace: false).deserialize
110
+ end.compact
111
+ end
112
+ end
113
+
114
+ def deserialize_jsonapi!
115
+ self.raw_params = self.params.deep_dup
116
+ hash = params.to_unsafe_h
117
+ hash = hash.with_indifferent_access if Rails::VERSION::MAJOR == 4
118
+ deserialized = Deserialization.new(hash).deserialize
119
+ self.params = ActionController::Parameters.new(deserialized)
120
+ end
121
+ end
122
+ end