Customized resource filters are not applied
griley opened this issue · 9 comments
It appears that trying to customize a filter doesn't work. Given a resource that looks like this:
class UserResource < JSONAPI::Resource
attributes :first_name, :last_name, :full_name
attribute :full_name
has_many :posts
filter :first_name, apply: ->(records, value, _options) {
# customize how the filter is applied - this never runs
records.where(first_name: 'Steve')
}
def full_name
"#{@model.first_name} #{@model.last_name}"
end
end
Assuming a stock controller and routes, the following request:
/users?filter[first_name]=Mark
results in the default filter being applied, not the customized lambda specified.
Hey there, @griley!
As you mentioned, JSON::Utils
only applies equality filters – like the one in the example – automatically. Unfortunately JU
doesn't provide any fancy macro to customize filters since it doesn't assume any responsibility over your controller action's operation.
For example, in our production JSON API, when we need to apply a very specific filter, like filtering records by a given scope, we apply that filter at the interactor/service object level. The reason for such decision is that we understand that it's a good pattern do make non-trivial filters explicit in the context where the operation of the action happens.
Hope it helps you and if you have any other ideas, just let me know.
Thanks @tiagopog . I agree with the decision to make the filters explicit.
However, the problem I'm running into (and the origin of the issue above) is that the default equality filter is still scoped to any custom filtering I may do in a controller.
For example:
class UserResource < JSONAPI::Resource
attributes :first_name, :last_name, :full_name
has_many :posts
# I'd like to expose a filter on a derived attribute
filter :full_name
def full_name
"#{@model.first_name} #{@model.last_name}"
end
end
class UsersController < BaseController
# GET /users
def index
jsonapi_render json: filtered_users
end
private
def filtered_users
first_name, last_name = params[:filter][:full_name].split
User.where(first_name: first_name, last_name: last_name)
end
end
When filtered_users
is run above, the scope implied by the filter in UserResource
is also applied to it. Resulting in:
User.where(first_name: first_name, last_name: last_name).where(full_name: params[:filter][:full_name])
Obviously, this is a problem because the attribute full_name
doesn't actually exist.
Any suggestion on a way around this or a different approach?
I guess I could unscope(where: :full_name)
in filtered_users
but that smells a bit to me.
Ok, now I gotcha! In this case you might simply skip the default equality filter: jsonapi_render json: filtered_users, options: { filter: false }
You might apply the same to pagination (options: { paginate: false }
). I'm sorry for the lack of documentation for this feature. I may write something covering this case in the README.md
later.
Thanks for the quick response @tiagopog and pointers!
The jsonapi_render json: filtered_users, options: { filter: false }
addressed my issue.
I finally realized why Tiago suggested setting a pagination option after I enabled the pagination feature in the initializer:
config.default_paginator = :paged
The record count information applies the same default filter to it. But even after adding the paginate: false
option, it continued to occur. I sniffed around the code a little bit and instead tried to add my own custom record count:
jsonapi_render json: filtered_users, options: { filter: false , count: filtered_users.length }
However, this line of code is thwarting that feature because it's overwriting the options I passed in.
@griley, thanks for reporting it. I might take a look on this possible bug later as well as I'll try to remember if there was any particular motivation behind this line of code.
On a side note...
If the options
weren't reinitialized, the apply_filters(records, options)
found here would cause the record count to be calculated correctly because of the filter: false
This would obviate the need for a count
option in this issue.
Thanks, and good luck on Sunday in the football match vs Germany ;)