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,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: []