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,60 @@
1
+ module JsonapiCompliable
2
+ class DSL
3
+ attr_accessor :sideloads,
4
+ :default_filters,
5
+ :extra_fields,
6
+ :filters,
7
+ :sorting,
8
+ :pagination
9
+
10
+ def initialize
11
+ @sideloads = {}
12
+ @filters = {}
13
+ @default_filters = {}
14
+ @extra_fields = {}
15
+ @sorting = nil
16
+ @pagination = nil
17
+ end
18
+
19
+ def includes(whitelist: nil, &blk)
20
+ whitelist = JSONAPI::IncludeDirective.new(whitelist) if whitelist
21
+
22
+ @sideloads = {
23
+ whitelist: whitelist,
24
+ custom_scope: blk
25
+ }
26
+ end
27
+
28
+ def allow_filter(name, *args, &blk)
29
+ opts = args.extract_options!
30
+ aliases = [name, opts[:aliases]].flatten.compact
31
+ @filters[name.to_sym] = {
32
+ aliases: aliases,
33
+ if: opts[:if],
34
+ filter: blk
35
+ }
36
+ end
37
+
38
+ def default_filter(name, &blk)
39
+ @default_filters[name.to_sym] = {
40
+ filter: blk
41
+ }
42
+ end
43
+
44
+ def sort(&blk)
45
+ @sorting = blk
46
+ end
47
+
48
+ def paginate(&blk)
49
+ @pagination = blk
50
+ end
51
+
52
+ def extra_field(field, &blk)
53
+ @extra_fields[field.keys.first] ||= []
54
+ @extra_fields[field.keys.first] << {
55
+ name: field.values.first,
56
+ proc: blk
57
+ }
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,15 @@
1
+ module JsonapiCompliable
2
+ module Errors
3
+ class BadFilter < StandardError; end
4
+
5
+ class UnsupportedPageSize < StandardError
6
+ def initialize(size, max)
7
+ @size, @max = size, max
8
+ end
9
+
10
+ def message
11
+ "Requested page size #{@size} is greater than max supported size #{@max}"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,40 @@
1
+ module JsonapiCompliable
2
+ module Scope
3
+ class Base
4
+ attr_reader :controller, :dsl, :params, :scope
5
+
6
+ def initialize(controller, scope)
7
+ @controller = controller
8
+ @dsl = controller._jsonapi_compliable
9
+ @params = controller.params
10
+ @scope = scope
11
+ end
12
+
13
+ def apply
14
+ apply_standard_or_override
15
+ end
16
+
17
+ def apply_standard_or_override
18
+ if apply_standard_scope?
19
+ @scope = apply_standard_scope
20
+ else
21
+ @scope = apply_custom_scope
22
+ end
23
+
24
+ @scope
25
+ end
26
+
27
+ def apply_standard_scope?
28
+ custom_scope.nil?
29
+ end
30
+
31
+ def apply_standard_scope
32
+ raise 'override in subclass'
33
+ end
34
+
35
+ def apply_custom_scope
36
+ raise 'override in subclass'
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,25 @@
1
+ module JsonapiCompliable
2
+ class Scope::DefaultFilter < Scope::Base
3
+ include Scope::Filterable
4
+
5
+ def apply
6
+ dsl.default_filters.each_pair do |name, opts|
7
+ next if overridden?(name)
8
+ @scope = opts[:filter].call(@scope)
9
+ end
10
+
11
+ @scope
12
+ end
13
+
14
+ private
15
+
16
+ def overridden?(name)
17
+ if found = find_filter(name)
18
+ found_aliases = found[name][:aliases]
19
+ filter_param.keys.any? { |k| found_aliases.include?(k.to_sym) }
20
+ else
21
+ false
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ module JsonapiCompliable
2
+ class Scope::ExtraFields < Scope::Base
3
+ def apply
4
+ each_extra_field do |extra_field|
5
+ @scope = extra_field[:proc].call(@scope)
6
+ end
7
+
8
+ @scope
9
+ end
10
+
11
+ private
12
+
13
+ def each_extra_field
14
+ dsl.extra_fields.each_pair do |namespace, extra_fields|
15
+ extra_fields.each do |extra_field|
16
+ if requested_extra_field?(namespace, extra_field[:name])
17
+ yield extra_field
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ def extra_fields
24
+ params[:extra_fields] || {}
25
+ end
26
+
27
+ def requested_extra_field?(namespace, field)
28
+ if namespaced = extra_fields[namespace]
29
+ namespaced.include?(field)
30
+ else
31
+ false
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,32 @@
1
+ module JsonapiCompliable
2
+ class Scope::Filter < Scope::Base
3
+ include Scope::Filterable
4
+
5
+ def apply
6
+ each_filter do |filter, value|
7
+ @scope = filter_scope(filter, value)
8
+ end
9
+
10
+ @scope
11
+ end
12
+
13
+ def filter_scope(filter, value)
14
+ if custom_scope = filter.values.first[:filter]
15
+ custom_scope.call(@scope, value)
16
+ else
17
+ @scope.where(filter.keys.first => value)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def each_filter
24
+ filter_param.each_pair do |param_name, param_value|
25
+ filter = find_filter!(param_name.to_sym)
26
+ value = param_value
27
+ value = value.split(',') if value.include?(',')
28
+ yield filter, value
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ module JsonapiCompliable
2
+ module Scope::Filterable
3
+ def find_filter(name)
4
+ find_filter!(name)
5
+ rescue JsonapiCompliable::Errors::BadFilter
6
+ nil
7
+ end
8
+
9
+ def find_filter!(name)
10
+ filter_name, filter_value = \
11
+ dsl.filters.find { |_name, opts| opts[:aliases].include?(name.to_sym) }
12
+ raise JsonapiCompliable::Errors::BadFilter unless filter_name
13
+ if guard = filter_value[:if]
14
+ raise JsonapiCompliable::Errors::BadFilter if controller.send(guard) == false
15
+ end
16
+ { filter_name => filter_value }
17
+ end
18
+
19
+ def filter_param
20
+ params[:filter] || {}
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ module JsonapiCompliable
2
+ class Scope::Paginate < Scope::Base
3
+ MAX_PAGE_SIZE = 1_000
4
+
5
+ def apply
6
+ if size > MAX_PAGE_SIZE
7
+ raise JsonapiCompliable::Errors::UnsupportedPageSize
8
+ .new(size, MAX_PAGE_SIZE)
9
+ else
10
+ super
11
+ end
12
+ end
13
+
14
+ def custom_scope
15
+ dsl.pagination
16
+ end
17
+
18
+ def apply_standard_scope
19
+ @scope.page(number).per(size)
20
+ end
21
+
22
+ def apply_custom_scope
23
+ custom_scope.call(@scope, number, size)
24
+ end
25
+
26
+ private
27
+
28
+ def page_param
29
+ @page_param ||= (params[:page] || {})
30
+ end
31
+
32
+ def number
33
+ (page_param[:number] || controller.default_page_number).to_i
34
+ end
35
+
36
+ def size
37
+ (page_param[:size] || controller.default_page_size).to_i
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,25 @@
1
+ module JsonapiCompliable
2
+ class Scope::Sideload < Scope::Base
3
+ def apply
4
+ params[:include] ? super : @scope
5
+ end
6
+
7
+ def custom_scope
8
+ dsl.sideloads[:custom_scope]
9
+ end
10
+
11
+ def apply_standard_scope
12
+ @scope.includes(scrubbed)
13
+ end
14
+
15
+ def apply_custom_scope
16
+ custom_scope.call(@scope, scrubbed)
17
+ end
18
+
19
+ private
20
+
21
+ def scrubbed
22
+ Util::IncludeParams.scrub(controller)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ module JsonapiCompliable
2
+ class Scope::Sort < Scope::Base
3
+ def custom_scope
4
+ dsl.sorting
5
+ end
6
+
7
+ def apply_standard_scope
8
+ @scope.order(attribute => direction)
9
+ end
10
+
11
+ def apply_custom_scope
12
+ custom_scope.call(@scope, attribute, direction)
13
+ end
14
+
15
+ private
16
+
17
+ def sort_param
18
+ @sort_param ||= (params[:sort] || 'id')
19
+ end
20
+
21
+ def direction
22
+ sort_param.starts_with?('-') ? :desc : :asc
23
+ end
24
+
25
+ def attribute
26
+ sort_param.dup.sub('-', '').to_sym
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ module JsonapiCompliable
2
+ module Util
3
+ class FieldParams
4
+ def self.parse!(params, name)
5
+ return unless params[name]
6
+
7
+ params[name].each_pair do |key, value|
8
+ params[name][key] = value.split(',').map(&:to_sym)
9
+ end
10
+ end
11
+
12
+ def self.fieldset(params, name)
13
+ params[name].to_unsafe_hash.deep_symbolize_keys
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ module JsonapiCompliable
2
+ module Util
3
+ class IncludeParams
4
+ def self.compare(includes, whitelist)
5
+ {}.tap do |valid|
6
+ includes.to_hash.each_pair do |key, sub_hash|
7
+ if whitelist[key]
8
+ valid[key] = compare(sub_hash, whitelist[key])
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ def self.scrub(controller)
15
+ dsl = controller._jsonapi_compliable
16
+ whitelist = dsl.sideloads[:whitelist] || {}
17
+ whitelist = whitelist[controller.action_name]
18
+ includes = JSONAPI::IncludeDirective.new(controller.params[:include])
19
+
20
+ if whitelist
21
+ Util::IncludeParams.compare(includes, whitelist)
22
+ else
23
+ {}
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ module JsonapiCompliable
2
+ module Util
3
+ class Scoping
4
+ def self.apply?(controller, object, force)
5
+ return false if force == false
6
+ return true if !controller._jsonapi_scoped && object.is_a?(ActiveRecord::Relation)
7
+
8
+ already_scoped = !!controller._jsonapi_scoped
9
+ is_activerecord = object.is_a?(ActiveRecord::Base)
10
+ is_activerecord_array = object.is_a?(Array) && object[0].is_a?(ActiveRecord::Base)
11
+
12
+ if [already_scoped, is_activerecord, is_activerecord_array].any?
13
+ false
14
+ else
15
+ true
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module JsonapiCompliable
2
+ VERSION = "0.3.4"
3
+ end
metadata ADDED
@@ -0,0 +1,258 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jsonapi_compliable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.4
5
+ platform: ruby
6
+ authors:
7
+ - Lee Richmond
8
+ - Venkata Pasupuleti
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2016-09-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '4.1'
21
+ - - "<"
22
+ - !ruby/object:Gem::Version
23
+ version: '6'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ version: '4.1'
31
+ - - "<"
32
+ - !ruby/object:Gem::Version
33
+ version: '6'
34
+ - !ruby/object:Gem::Dependency
35
+ name: jsonapi
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.1.1.beta2
41
+ type: :runtime
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.1.1.beta2
48
+ - !ruby/object:Gem::Dependency
49
+ name: active_model_serializers
50
+ requirement: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.10'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.10'
62
+ - !ruby/object:Gem::Dependency
63
+ name: jsonapi_ams_extensions
64
+ requirement: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.1'
69
+ type: :runtime
70
+ prerelease: false
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.1'
76
+ - !ruby/object:Gem::Dependency
77
+ name: kaminari
78
+ requirement: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ - !ruby/object:Gem::Dependency
91
+ name: active_model_serializers
92
+ requirement: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ type: :development
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ - !ruby/object:Gem::Dependency
105
+ name: jsonapi_spec_helpers
106
+ requirement: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ type: :development
112
+ prerelease: false
113
+ version_requirements: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ - !ruby/object:Gem::Dependency
119
+ name: bundler
120
+ requirement: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.12'
125
+ type: :development
126
+ prerelease: false
127
+ version_requirements: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.12'
132
+ - !ruby/object:Gem::Dependency
133
+ name: rake
134
+ requirement: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '10.0'
139
+ type: :development
140
+ prerelease: false
141
+ version_requirements: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '10.0'
146
+ - !ruby/object:Gem::Dependency
147
+ name: rspec-rails
148
+ requirement: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ type: :development
154
+ prerelease: false
155
+ version_requirements: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ - !ruby/object:Gem::Dependency
161
+ name: sqlite3
162
+ requirement: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ type: :development
168
+ prerelease: false
169
+ version_requirements: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ - !ruby/object:Gem::Dependency
175
+ name: database_cleaner
176
+ requirement: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ type: :development
182
+ prerelease: false
183
+ version_requirements: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ description:
189
+ email:
190
+ - richmolj@gmail.com
191
+ - spasupuleti4@bloomberg.net
192
+ executables: []
193
+ extensions: []
194
+ extra_rdoc_files: []
195
+ files:
196
+ - ".gitignore"
197
+ - ".rspec"
198
+ - ".ruby-version"
199
+ - ".travis.yml"
200
+ - Appraisals
201
+ - CODE_OF_CONDUCT.md
202
+ - Gemfile
203
+ - Guardfile
204
+ - LICENSE.txt
205
+ - README.md
206
+ - Rakefile
207
+ - bin/appraisal
208
+ - bin/console
209
+ - bin/rspec
210
+ - bin/setup
211
+ - gemfiles/rails_3.gemfile
212
+ - gemfiles/rails_4.gemfile
213
+ - gemfiles/rails_4.gemfile.lock
214
+ - gemfiles/rails_5.gemfile
215
+ - gemfiles/rails_5.gemfile.lock
216
+ - jsonapi_compliable.gemspec
217
+ - lib/jsonapi_compliable.rb
218
+ - lib/jsonapi_compliable/base.rb
219
+ - lib/jsonapi_compliable/deserializable.rb
220
+ - lib/jsonapi_compliable/dsl.rb
221
+ - lib/jsonapi_compliable/errors.rb
222
+ - lib/jsonapi_compliable/scope/base.rb
223
+ - lib/jsonapi_compliable/scope/default_filter.rb
224
+ - lib/jsonapi_compliable/scope/extra_fields.rb
225
+ - lib/jsonapi_compliable/scope/filter.rb
226
+ - lib/jsonapi_compliable/scope/filterable.rb
227
+ - lib/jsonapi_compliable/scope/paginate.rb
228
+ - lib/jsonapi_compliable/scope/sideload.rb
229
+ - lib/jsonapi_compliable/scope/sort.rb
230
+ - lib/jsonapi_compliable/util/field_params.rb
231
+ - lib/jsonapi_compliable/util/include_params.rb
232
+ - lib/jsonapi_compliable/util/scoping.rb
233
+ - lib/jsonapi_compliable/version.rb
234
+ homepage:
235
+ licenses:
236
+ - MIT
237
+ metadata: {}
238
+ post_install_message:
239
+ rdoc_options: []
240
+ require_paths:
241
+ - lib
242
+ required_ruby_version: !ruby/object:Gem::Requirement
243
+ requirements:
244
+ - - ">="
245
+ - !ruby/object:Gem::Version
246
+ version: '0'
247
+ required_rubygems_version: !ruby/object:Gem::Requirement
248
+ requirements:
249
+ - - ">="
250
+ - !ruby/object:Gem::Version
251
+ version: '0'
252
+ requirements: []
253
+ rubyforge_project:
254
+ rubygems_version: 2.5.1
255
+ signing_key:
256
+ specification_version: 4
257
+ summary: JSON Compliable serializer for action controller
258
+ test_files: []