jsonapi_compliable 0.4.0 → 0.5.1

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +4 -0
  3. data/Gemfile +2 -2
  4. data/gemfiles/rails_4.gemfile +4 -3
  5. data/gemfiles/rails_4.gemfile.lock +27 -36
  6. data/gemfiles/rails_5.gemfile +4 -3
  7. data/gemfiles/rails_5.gemfile.lock +27 -37
  8. data/jsonapi_compliable.gemspec +3 -7
  9. data/lib/jsonapi_compliable/adapters/abstract.rb +25 -0
  10. data/lib/jsonapi_compliable/adapters/active_record.rb +47 -0
  11. data/lib/jsonapi_compliable/adapters/active_record_sideloading.rb +119 -0
  12. data/lib/jsonapi_compliable/adapters/null.rb +21 -0
  13. data/lib/jsonapi_compliable/base.rb +43 -50
  14. data/lib/jsonapi_compliable/deserializable.rb +9 -4
  15. data/lib/jsonapi_compliable/extensions/extra_attribute.rb +1 -5
  16. data/lib/jsonapi_compliable/query.rb +153 -0
  17. data/lib/jsonapi_compliable/rails.rb +14 -0
  18. data/lib/jsonapi_compliable/resource.rb +191 -0
  19. data/lib/jsonapi_compliable/scope.rb +57 -0
  20. data/lib/jsonapi_compliable/{scope → scoping}/base.rb +5 -6
  21. data/lib/jsonapi_compliable/{scope → scoping}/default_filter.rb +3 -3
  22. data/lib/jsonapi_compliable/scoping/extra_fields.rb +25 -0
  23. data/lib/jsonapi_compliable/{scope → scoping}/filter.rb +4 -4
  24. data/lib/jsonapi_compliable/{scope → scoping}/filterable.rb +4 -4
  25. data/lib/jsonapi_compliable/{scope → scoping}/paginate.rb +6 -6
  26. data/lib/jsonapi_compliable/scoping/sort.rb +33 -0
  27. data/lib/jsonapi_compliable/sideload.rb +95 -0
  28. data/lib/jsonapi_compliable/stats/dsl.rb +7 -16
  29. data/lib/jsonapi_compliable/stats/payload.rb +6 -14
  30. data/lib/jsonapi_compliable/util/field_params.rb +6 -8
  31. data/lib/jsonapi_compliable/util/hash.rb +14 -0
  32. data/lib/jsonapi_compliable/util/include_params.rb +7 -18
  33. data/lib/jsonapi_compliable/util/render_options.rb +25 -0
  34. data/lib/jsonapi_compliable/version.rb +1 -1
  35. data/lib/jsonapi_compliable.rb +22 -13
  36. metadata +34 -70
  37. data/gemfiles/rails_3.gemfile +0 -9
  38. data/lib/jsonapi_compliable/dsl.rb +0 -90
  39. data/lib/jsonapi_compliable/scope/extra_fields.rb +0 -35
  40. data/lib/jsonapi_compliable/scope/sideload.rb +0 -25
  41. data/lib/jsonapi_compliable/scope/sort.rb +0 -29
  42. data/lib/jsonapi_compliable/util/pagination.rb +0 -11
  43. data/lib/jsonapi_compliable/util/scoping.rb +0 -20
@@ -6,65 +6,56 @@ module JsonapiCompliable
6
6
  MAX_PAGE_SIZE = 1_000
7
7
 
8
8
  included do
9
- class_attribute :_jsonapi_compliable
10
- attr_reader :_jsonapi_scope
9
+ class << self
10
+ attr_accessor :_jsonapi_compliable
11
+ end
11
12
 
12
- before_action :parse_fieldsets!
13
- after_action :reset_scope_flag
13
+ def self.inherited(klass)
14
+ super
15
+ klass._jsonapi_compliable = Class.new(_jsonapi_compliable)
16
+ end
14
17
  end
15
18
 
16
- def default_page_number
17
- 1
19
+ def resource
20
+ @resource ||= self.class._jsonapi_compliable.new
18
21
  end
19
22
 
20
- def default_page_size
21
- 20
23
+ def resource!
24
+ @resource = self.class._jsonapi_compliable.new
22
25
  end
23
26
 
24
- def default_sort
25
- 'id'
27
+ def query
28
+ @query ||= Query.new(resource, params)
26
29
  end
27
30
 
28
- def jsonapi_scope(scope,
29
- filter: true,
30
- includes: true,
31
- paginate: true,
32
- extra_fields: true,
33
- sort: true)
34
- scope = JsonapiCompliable::Scope::DefaultFilter.new(self, scope).apply
35
- scope = JsonapiCompliable::Scope::Filter.new(self, scope).apply if filter
36
- scope = JsonapiCompliable::Scope::ExtraFields.new(self, scope).apply if extra_fields
37
- scope = JsonapiCompliable::Scope::Sideload.new(self, scope).apply if includes
38
- scope = JsonapiCompliable::Scope::Sort.new(self, scope).apply if sort
39
- # This is set before pagination so it can be re-used for stats
40
- @_jsonapi_scope = scope
41
- scope = JsonapiCompliable::Scope::Paginate.new(self, scope).apply if paginate
42
- scope
31
+ def query_hash
32
+ @query_hash ||= query.to_hash[resource.type]
43
33
  end
44
34
 
45
- def reset_scope_flag
46
- @_jsonapi_scope = nil
35
+ # TODO pass controller and action name here to guard
36
+ def wrap_context
37
+ if self.class._jsonapi_compliable
38
+ resource.with_context(self, action_name.to_sym) do
39
+ yield
40
+ end
41
+ end
47
42
  end
48
43
 
49
- def parse_fieldsets!
50
- Util::FieldParams.parse!(params, :fields)
51
- Util::FieldParams.parse!(params, :extra_fields)
44
+ def jsonapi_scope(scope, opts = {})
45
+ resource.build_scope(scope, query, opts)
46
+ end
47
+
48
+ def perform_render_jsonapi(opts)
49
+ JSONAPI::Serializable::Renderer.render(opts.delete(:jsonapi), opts)
52
50
  end
53
51
 
54
52
  def render_jsonapi(scope, opts = {})
55
- scoped = Util::Scoping.apply?(self, scope, opts.delete(:scope)) ? jsonapi_scope(scope) : scope
56
- options = default_jsonapi_render_options
57
- options[:include] = forced_includes || Util::IncludeParams.scrub(self)
58
- options[:jsonapi] = JsonapiCompliable::Util::Pagination.zero?(params) ? [] : scoped
59
- options[:fields] = Util::FieldParams.fieldset(params, :fields) if params[:fields]
60
- options[:meta] ||= {}
61
- options.merge!(opts)
62
- options[:meta][:stats] = Stats::Payload.new(self, scoped).generate if params[:stats]
63
- options[:expose] ||= {}
64
- options[:expose][:context] = self
65
- options[:expose][:extra_fields] = Util::FieldParams.fieldset(params, :extra_fields) if params[:extra_fields]
66
-
67
- render(options)
53
+ scope = jsonapi_scope(scope) unless opts[:scope] == false || scope.is_a?(JsonapiCompliable::Scope)
54
+ opts = default_jsonapi_render_options.merge(opts)
55
+ opts = Util::RenderOptions.generate(scope, query_hash, opts)
56
+ opts[:expose][:context] = self
57
+ opts[:include] = forced_includes if force_includes?
58
+ perform_render_jsonapi(opts)
68
59
  end
69
60
 
70
61
  # render_jsonapi(foo) equivalent to
@@ -74,11 +65,11 @@ module JsonapiCompliable
74
65
  end
75
66
  end
76
67
 
68
+ # Legacy
77
69
  # TODO: This nastiness likely goes away once jsonapi standardizes
78
70
  # a spec for nested relationships.
79
71
  # See: https://github.com/json-api/json-api/issues/1089
80
72
  def forced_includes(data = nil)
81
- return unless force_includes?
82
73
  data = raw_params[:data] unless data
83
74
 
84
75
  {}.tap do |forced|
@@ -95,21 +86,23 @@ module JsonapiCompliable
95
86
  end
96
87
  end
97
88
 
89
+ # Legacy
98
90
  def force_includes?
99
91
  %w(PUT PATCH POST).include?(request.method) and
100
92
  raw_params.try(:[], :data).try(:[], :relationships).present?
101
93
  end
102
94
 
103
95
  module ClassMethods
104
- def jsonapi(&blk)
105
- if !self._jsonapi_compliable
106
- dsl = JsonapiCompliable::DSL.new
107
- self._jsonapi_compliable = dsl
96
+ def jsonapi(resource: nil, &blk)
97
+ if resource
98
+ self._jsonapi_compliable = resource
108
99
  else
109
- self._jsonapi_compliable = self._jsonapi_compliable.copy
100
+ if !self._jsonapi_compliable
101
+ self._jsonapi_compliable = Class.new(JsonapiCompliable::Resource)
102
+ end
110
103
  end
111
104
 
112
- self._jsonapi_compliable.instance_eval(&blk)
105
+ self._jsonapi_compliable.class_eval(&blk) if blk
113
106
  end
114
107
  end
115
108
  end
@@ -113,10 +113,15 @@ module JsonapiCompliable
113
113
 
114
114
  def deserialize_jsonapi!
115
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)
116
+
117
+ if defined?(::Rails) && (is_a?(ActionController::Base) || (defined?(ActionController::API) && is_a?(ActionController::API)))
118
+ hash = params.to_unsafe_h
119
+ hash = hash.with_indifferent_access if ::Rails::VERSION::MAJOR == 4
120
+ deserialized = Deserialization.new(hash).deserialize
121
+ self.params = ActionController::Parameters.new(deserialized)
122
+ else
123
+ self.params = Deserialization.new(params).deserialize
124
+ end
120
125
  end
121
126
  end
122
127
  end
@@ -14,11 +14,7 @@ module JsonapiCompliable
14
14
  next false unless instance_eval(&options[:if])
15
15
  end
16
16
 
17
- if @extra_fields && @extra_fields[jsonapi_type]
18
- @extra_fields[jsonapi_type].include?(name)
19
- else
20
- false
21
- end
17
+ @extra_fields[@_type] && @extra_fields[@_type].include?(name)
22
18
  }
23
19
 
24
20
  attribute name, if: allow_field, &blk
@@ -0,0 +1,153 @@
1
+ # TODO: refactor - code could be better but it's a one-time thing.
2
+
3
+ module JsonapiCompliable
4
+ class Query
5
+ attr_reader :params, :resource
6
+
7
+ def self.default_hash
8
+ {
9
+ filter: {},
10
+ sort: [],
11
+ page: {},
12
+ include: {},
13
+ stats: {},
14
+ fields: {},
15
+ extra_fields: {}
16
+ }
17
+ end
18
+
19
+ def initialize(resource, params)
20
+ @resource = resource
21
+ @params = params
22
+ end
23
+
24
+ def include_directive
25
+ @include_directive ||= JSONAPI::IncludeDirective.new(params[:include])
26
+ end
27
+
28
+ def include_hash
29
+ @include_hash ||= include_directive.to_hash
30
+ end
31
+
32
+ def all_requested_association_names
33
+ @all_requested_association_names ||= Util::Hash.keys(include_hash)
34
+ end
35
+
36
+ def to_hash
37
+ hash = { resource.type => self.class.default_hash }
38
+
39
+ all_requested_association_names.each do |name|
40
+ hash[name] = self.class.default_hash
41
+ end
42
+
43
+ fields = parse_fields({}, :fields)
44
+ extra_fields = parse_fields({}, :extra_fields)
45
+ hash.each_pair do |type, query_hash|
46
+ hash[type][:fields] = fields
47
+ hash[type][:extra_fields] = extra_fields
48
+ end
49
+
50
+ parse_filter(hash)
51
+ parse_sort(hash)
52
+ parse_pagination(hash)
53
+ parse_include(hash, include_hash)
54
+ parse_stats(hash)
55
+
56
+ hash
57
+ end
58
+
59
+ def zero_results?
60
+ !@params[:page].nil? &&
61
+ !@params[:page][:size].nil? &&
62
+ @params[:page][:size].to_i == 0
63
+ end
64
+
65
+ private
66
+
67
+ def association?(name)
68
+ resource.association_names.include?(name)
69
+ end
70
+
71
+ def parse_include(memo, incl_hash, namespace = nil)
72
+ namespace ||= resource.type
73
+
74
+ memo[namespace][:include] = incl_hash
75
+ incl_hash.each_pair do |key, sub_hash|
76
+ memo[key][:include] = parse_include(memo, sub_hash, key)
77
+ end
78
+ end
79
+
80
+ def parse_stats(hash)
81
+ if params[:stats]
82
+ params[:stats].each_pair do |namespace, calculations|
83
+ if namespace == resource.type || association?(namespace)
84
+ calculations.each_pair do |name, calcs|
85
+ hash[namespace][:stats][name] = calcs.split(',').map(&:to_sym)
86
+ end
87
+ else
88
+ hash[resource.type][:stats][namespace] = calculations.split(',').map(&:to_sym)
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ def parse_fields(hash, type)
95
+ field_params = Util::FieldParams.parse(params[type])
96
+ hash[type] = field_params
97
+ end
98
+
99
+ def parse_filter(hash)
100
+ if filter = params[:filter]
101
+ filter.each_pair do |key, value|
102
+ key = key.to_sym
103
+
104
+ if association?(key)
105
+ hash[key][:filter].merge!(value)
106
+ else
107
+ hash[resource.type][:filter][key] = value
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ def parse_sort(hash)
114
+ if sort = params[:sort]
115
+ sorts = sort.split(',')
116
+ sorts.each do |s|
117
+ if s.include?('.')
118
+ type, attr = s.split('.')
119
+ if type.starts_with?('-')
120
+ type = type.sub('-', '')
121
+ attr = "-#{attr}"
122
+ end
123
+
124
+ hash[type.to_sym][:sort] << sort_attr(attr)
125
+ else
126
+ hash[resource.type][:sort] << sort_attr(s)
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ def parse_pagination(hash)
133
+ if pagination = params[:page]
134
+ pagination.each_pair do |key, value|
135
+ key = key.to_sym
136
+
137
+ if [:number, :size].include?(key)
138
+ hash[resource.type][:page][key] = value.to_i
139
+ else
140
+ hash[key][:page] = { number: value[:number].to_i, size: value[:size].to_i }
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ def sort_attr(attr)
147
+ value = attr.starts_with?('-') ? :desc : :asc
148
+ key = attr.sub('-', '').to_sym
149
+
150
+ { key => value }
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,14 @@
1
+ require 'jsonapi/rails'
2
+
3
+ module JsonapiCompliable
4
+ module Rails
5
+ def self.included(klass)
6
+ klass.send(:include, Base)
7
+
8
+ klass.class_eval do
9
+ around_action :wrap_context
10
+ alias_method :perform_render_jsonapi, :render
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,191 @@
1
+ module JsonapiCompliable
2
+ class Resource
3
+ attr_reader :filters,
4
+ :default_filters,
5
+ :default_sort,
6
+ :default_page_size,
7
+ :default_page_number,
8
+ :sorting,
9
+ :stats,
10
+ :sideload_whitelist,
11
+ :pagination,
12
+ :extra_fields,
13
+ :sideloading,
14
+ :adapter,
15
+ :type,
16
+ :context
17
+
18
+ class << self
19
+ attr_accessor :config
20
+ end
21
+
22
+ delegate :sideload, to: :sideloading
23
+
24
+ # Incorporate custom adapter methods
25
+ def self.method_missing(meth, *args, &blk)
26
+ if sideloading.respond_to?(meth)
27
+ sideloading.send(meth, *args, &blk)
28
+ else
29
+ super
30
+ end
31
+ end
32
+
33
+ def self.inherited(klass)
34
+ klass.config = self.config.deep_dup
35
+ end
36
+
37
+ def self.sideloading
38
+ config[:sideloading] ||= Sideload.new(:base, resource: self)
39
+ end
40
+
41
+ def self.sideload_whitelist(whitelist)
42
+ config[:sideload_whitelist] = JSONAPI::IncludeDirective.new(whitelist).to_hash
43
+ end
44
+
45
+ def self.allow_filter(name, *args, &blk)
46
+ opts = args.extract_options!
47
+ aliases = [name, opts[:aliases]].flatten.compact
48
+ config[:filters][name.to_sym] = {
49
+ aliases: aliases,
50
+ if: opts[:if],
51
+ filter: blk
52
+ }
53
+ end
54
+
55
+ def self.allow_stat(symbol_or_hash, &blk)
56
+ dsl = Stats::DSL.new(config[:adapter], symbol_or_hash)
57
+ dsl.instance_eval(&blk) if blk
58
+ config[:stats][dsl.name] = dsl
59
+ end
60
+
61
+ def self.default_filter(name, &blk)
62
+ config[:default_filters][name.to_sym] = {
63
+ filter: blk
64
+ }
65
+ end
66
+
67
+ def self.sort(&blk)
68
+ config[:sorting] = blk
69
+ end
70
+
71
+ def self.paginate(&blk)
72
+ config[:pagination] = blk
73
+ end
74
+
75
+ def self.extra_field(name, &blk)
76
+ config[:extra_fields][name] = blk
77
+ end
78
+
79
+ def self.use_adapter(klass)
80
+ config[:adapter] = klass.new
81
+ end
82
+
83
+ def self.default_sort(val)
84
+ config[:default_sort] = val
85
+ end
86
+
87
+ def self.type(value = nil)
88
+ config[:type] = value
89
+ end
90
+
91
+ def self.default_page_number(val)
92
+ config[:default_page_number] = val
93
+ end
94
+
95
+ def self.default_page_size(val)
96
+ config[:default_page_size] = val
97
+ end
98
+
99
+ def self.config
100
+ @config ||= begin
101
+ {
102
+ sideload_whitelist: {},
103
+ filters: {},
104
+ default_filters: {},
105
+ extra_fields: {},
106
+ stats: {},
107
+ sorting: nil,
108
+ pagination: nil,
109
+ adapter: Adapters::Abstract.new
110
+ }
111
+ end
112
+ end
113
+
114
+ def copy
115
+ self.class.new(@config.deep_dup)
116
+ end
117
+
118
+ def initialize(config = nil)
119
+ config = config || self.class.config.deep_dup
120
+ set_config(config)
121
+ end
122
+
123
+ def set_config(config)
124
+ @config = config
125
+ config.each_pair do |key, value|
126
+ instance_variable_set(:"@#{key}", value)
127
+ end
128
+ end
129
+
130
+ def with_context(object, namespace = nil)
131
+ begin
132
+ prior = context
133
+ @context = { object: object, namespace: namespace }
134
+ yield
135
+ ensure
136
+ @context = prior
137
+ end
138
+ end
139
+
140
+ def context
141
+ @context || {}
142
+ end
143
+
144
+ def build_scope(base, query, opts = {})
145
+ Scope.new(base, self, query, opts)
146
+ end
147
+
148
+ def association_names
149
+ @association_names ||= begin
150
+ if sideloading
151
+ Util::Hash.keys(sideloading.to_hash[:base])
152
+ else
153
+ []
154
+ end
155
+ end
156
+ end
157
+
158
+ def allowed_sideloads(namespace = nil)
159
+ return {} unless sideloading
160
+
161
+ namespace ||= context[:namespace]
162
+ sideloads = sideloading.to_hash[:base]
163
+ if !sideload_whitelist.empty? && namespace
164
+ sideloads = Util::IncludeParams.scrub(sideloads, sideload_whitelist[namespace])
165
+ end
166
+ sideloads
167
+ end
168
+
169
+ def stat(attribute, calculation)
170
+ stats_dsl = stats[attribute] || stats[attribute.to_sym]
171
+ raise Errors::StatNotFound.new(attribute, calculation) unless stats_dsl
172
+ stats_dsl.calculation(calculation)
173
+ end
174
+
175
+ def default_sort
176
+ @default_sort || [{ id: :asc }]
177
+ end
178
+
179
+ def default_page_number
180
+ @default_page_number || 1
181
+ end
182
+
183
+ def default_page_size
184
+ @default_page_size || 20
185
+ end
186
+
187
+ def type
188
+ @type || :undefined_jsonapi_type
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,57 @@
1
+ module JsonapiCompliable
2
+ class Scope
3
+ def initialize(object, resource, query, opts = {})
4
+ @object = object
5
+ @resource = resource
6
+ @query = query
7
+
8
+ # Namespace for the 'outer' or 'main' resource is its type
9
+ # For its relationships, its the relationship name
10
+ # IOW when hitting /states, it's resource type 'states
11
+ # when hitting /authors?include=state its 'state'
12
+ @namespace = opts.delete(:namespace) || resource.type
13
+
14
+ apply_scoping(opts)
15
+ end
16
+
17
+ def resolve_stats
18
+ Stats::Payload.new(@resource, query_hash, @unpaginated_object).generate
19
+ end
20
+
21
+ def resolve
22
+ if @query.zero_results?
23
+ []
24
+ else
25
+ resolved = @object
26
+ # TODO - configurable resolve function
27
+ resolved = @object.to_a if @object.is_a?(ActiveRecord::Relation)
28
+ sideload(resolved, query_hash[:include]) if query_hash[:include]
29
+ resolved
30
+ end
31
+ end
32
+
33
+ def query_hash
34
+ @query_hash ||= @query.to_hash[@namespace]
35
+ end
36
+
37
+ private
38
+
39
+ def sideload(results, includes)
40
+ includes.each_pair do |name, nested|
41
+ if @resource.allowed_sideloads.has_key?(name)
42
+ sideload = @resource.sideload(name)
43
+ sideload.resolve(results, @query)
44
+ end
45
+ end
46
+ end
47
+
48
+ def apply_scoping(opts)
49
+ @object = JsonapiCompliable::Scoping::DefaultFilter.new(@resource, query_hash, @object).apply
50
+ @object = JsonapiCompliable::Scoping::Filter.new(@resource, query_hash, @object).apply unless opts[:filter] == false
51
+ @object = JsonapiCompliable::Scoping::ExtraFields.new(@resource, query_hash, @object).apply unless opts[:extra_fields] == false
52
+ @object = JsonapiCompliable::Scoping::Sort.new(@resource, query_hash, @object).apply unless opts[:sort] == false
53
+ @unpaginated_object = @object
54
+ @object = JsonapiCompliable::Scoping::Paginate.new(@resource, query_hash, @object).apply unless opts[:paginate] == false
55
+ end
56
+ end
57
+ end
@@ -1,12 +1,11 @@
1
1
  module JsonapiCompliable
2
- module Scope
2
+ module Scoping
3
3
  class Base
4
- attr_reader :controller, :dsl, :params, :scope
4
+ attr_reader :resource, :query_hash
5
5
 
6
- def initialize(controller, scope)
7
- @controller = controller
8
- @dsl = controller._jsonapi_compliable
9
- @params = controller.params
6
+ def initialize(resource, query_hash, scope)
7
+ @query_hash = query_hash
8
+ @resource = resource
10
9
  @scope = scope
11
10
  end
12
11
 
@@ -1,9 +1,9 @@
1
1
  module JsonapiCompliable
2
- class Scope::DefaultFilter < Scope::Base
3
- include Scope::Filterable
2
+ class Scoping::DefaultFilter < Scoping::Base
3
+ include Scoping::Filterable
4
4
 
5
5
  def apply
6
- dsl.default_filters.each_pair do |name, opts|
6
+ resource.default_filters.each_pair do |name, opts|
7
7
  next if overridden?(name)
8
8
  @scope = opts[:filter].call(@scope)
9
9
  end
@@ -0,0 +1,25 @@
1
+ module JsonapiCompliable
2
+ class Scoping::ExtraFields < Scoping::Base
3
+ def apply
4
+ each_extra_field do |callable|
5
+ @scope = callable.call(@scope)
6
+ end
7
+
8
+ @scope
9
+ end
10
+
11
+ private
12
+
13
+ def each_extra_field
14
+ resource.extra_fields.each_pair do |name, callable|
15
+ if extra_fields.include?(name)
16
+ yield callable
17
+ end
18
+ end
19
+ end
20
+
21
+ def extra_fields
22
+ query_hash[:extra_fields][resource.type] || []
23
+ end
24
+ end
25
+ end
@@ -1,6 +1,6 @@
1
1
  module JsonapiCompliable
2
- class Scope::Filter < Scope::Base
3
- include Scope::Filterable
2
+ class Scoping::Filter < Scoping::Base
3
+ include Scoping::Filterable
4
4
 
5
5
  def apply
6
6
  each_filter do |filter, value|
@@ -14,7 +14,7 @@ module JsonapiCompliable
14
14
  if custom_scope = filter.values.first[:filter]
15
15
  custom_scope.call(@scope, value)
16
16
  else
17
- @scope.where(filter.keys.first => value)
17
+ resource.adapter.filter(@scope, filter.keys.first, value)
18
18
  end
19
19
  end
20
20
 
@@ -24,7 +24,7 @@ module JsonapiCompliable
24
24
  filter_param.each_pair do |param_name, param_value|
25
25
  filter = find_filter!(param_name.to_sym)
26
26
  value = param_value
27
- value = value.split(',') if value.include?(',')
27
+ value = value.split(',') if value.is_a?(String) && value.include?(',')
28
28
  yield filter, value
29
29
  end
30
30
  end