diff --git a/README.md b/README.md index 8c95c08..06633cb 100644 --- a/README.md +++ b/README.md @@ -255,6 +255,37 @@ $ curl -X GET \ &sort=-model_attr,relationship_attr ``` +#### Using scopes + +You can use scopes for filtering as well, to enable scopes, use the option +flags: + +```ruby +options = { allowed_scopes: User.ransackable_scopes } +jsonapi_filter(User.all, [:created_before], options) do |filtered| + render jsonapi: result.group('id').to_a +end +``` + +Assuming your model `User` has the following scope defined: + +```ruby +class User < ActiveRecord::Base + scope :created_before, ->(date) { where('created_at < ?', date) } + + + def self.ransackable_scopes(_auth_object = nil) + [:created_before] + end +end +``` + +This allows you to run queries like: + +```bash +$ curl -X GET /api/resources?filter[created_before]='2021-01-29' +``` + #### Sorting using expressions You can use basic aggregations like `min`, `max`, `avg`, `sum` and `count` diff --git a/lib/jsonapi/filtering.rb b/lib/jsonapi/filtering.rb index 70d47db..14c6c43 100644 --- a/lib/jsonapi/filtering.rb +++ b/lib/jsonapi/filtering.rb @@ -34,7 +34,7 @@ def self.extract_attributes_and_predicates(requested_field) # @return [ActiveRecord::Base] a collection of resources def jsonapi_filter(resources, allowed_fields, options = {}) allowed_fields = allowed_fields.map(&:to_s) - extracted_params = jsonapi_filter_params(allowed_fields) + extracted_params = jsonapi_filter_params(allowed_fields, options) extracted_params[:sorts] = jsonapi_sort_params(allowed_fields, options) resources = resources.ransack(extracted_params) block_given? ? yield(resources) : resources @@ -46,11 +46,13 @@ def jsonapi_filter(resources, allowed_fields, options = {}) # See: https://github.com/activerecord-hackery/ransack#search-matchers # # @param allowed_fields [Array] a list of allowed fields to be filtered + # @param options [Hash] extra flags to enable/disable features # @return [Hash] to be passed to [ActiveRecord::Base#order] - def jsonapi_filter_params(allowed_fields) + def jsonapi_filter_params(allowed_fields, options = {}) filtered = {} requested = params[:filter] || {} allowed_fields = allowed_fields.map(&:to_s) + scopes = Array(options[:allowed_scopes]).map(&:to_s) requested.each_pair do |requested_field, to_filter| field_names, predicates = JSONAPI::Filtering @@ -62,7 +64,14 @@ def jsonapi_filter_params(allowed_fields) to_filter = to_filter.split(',') end - if predicates.any? && (field_names - allowed_fields).empty? + # Disable predicate validation for scopes if the scope name is allowed + allow = predicates.any? || ( + predicates.none? && + scopes.include?(requested_field) && + allowed_fields.include?(requested_field) + ) + + if allow && (field_names - allowed_fields).empty? filtered[requested_field] = to_filter end end diff --git a/spec/dummy.rb b/spec/dummy.rb index 6f0c434..fb6c749 100644 --- a/spec/dummy.rb +++ b/spec/dummy.rb @@ -31,6 +31,12 @@ class User < ActiveRecord::Base has_many :notes + + scope :created_before, ->(date) { where('created_at < ?', date) } + + def self.ransackable_scopes(_auth_object = nil) + [:created_before] + end end class Note < ActiveRecord::Base @@ -83,9 +89,12 @@ class UsersController < ActionController::Base def index allowed_fields = [ :first_name, :last_name, :created_at, - :notes_created_at, :notes_quantity + :notes_created_at, :notes_quantity, :created_before ] - options = { sort_with_expressions: true } + options = { + sort_with_expressions: true, + allowed_scopes: User.ransackable_scopes + } jsonapi_filter(User.all, allowed_fields, options) do |filtered| result = filtered.result diff --git a/spec/filtering_spec.rb b/spec/filtering_spec.rb index f3bd80a..40939be 100644 --- a/spec/filtering_spec.rb +++ b/spec/filtering_spec.rb @@ -96,6 +96,22 @@ expect(response_json['data'][0]).to have_id(second_user.id.to_s) end end + + context 'returns users filtered by scope' do + let(:params) do + third_user.update(created_at: '2013-01-01') + + { + filter: { created_before: '2013-02-01' } + } + end + + it do + expect(response).to have_http_status(:ok) + expect(response_json['data'].size).to eq(1) + expect(response_json['data'][0]).to have_id(third_user.id.to_s) + end + end end end end