jsonapi_compliable 0.4.0 → 0.5.1

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