jsonapi_compliable 0.3.4

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