Jsonapi-resources: [Question] Advanced filtering - using "like" etc. - is it possible?

Created on 25 Sep 2015  路  6Comments  路  Source: cerebris/jsonapi-resources

Hi,
I just started looking at JSONAPI::Resources and I have some questions regarding filtering:

1) Is there any way to filter resource by using "like" searches? For example find all posts whose title contains a word "bird". It appears that there is no standard way to do it, at least I didn't find any.

2) If I want to use some complicated default filter in an "index" action for example, I was trying to use serializers - but I have no way how to easily serialize array of my records, what I mean is this:
render json:

JSONAPI::ResourceSerializer.new(Api::V1::PostResource).serialize_to_hash(Api::V1::PostResource.new(Post.all))

This of course doesn't work - how can I easily convert a Relation or an array of records into an array of Resources? It looks like I have to iterate over each record and concert it mannualy.

Thank you for the answer.

Most helpful comment

I think it would be nice to use filters with predicates (like ransack does)
something like the following example

class BaseResource < JSONAPI::Resource
  abstract

  def self.ransack_filter(name, opts = {})
    opts[:apply] = ->(records, value, _options) do
      records.ransack({name => value}).result
      # or without ransack:
      # records.public_send(name, value)
    end

    filter name, opts
  end

  def ransack_filters(*names)
    options = names.extract_options!
    names.each { |name| ransack_filter(name, options) }
end

class ProductResource < BaseResource
  attribute :name

  ransack_filters :name_equals, :name_contains
end

class Product < ActiveRecord::Base
  # without ransack:
  # scope :name_equals, ->(value) { where(name: value) }
  # scope :name_contains, ->(value) { where('name LIKE ?', "%#{value}%") }
end

All 6 comments

1) You can override the apply_filter method on the resource. This allows you to use custom where clauses.

2) This shouldn't be needed (see 1 above). But if you want to do this you will need to create an Array of Resources separately. So loop through Post.all and instantiate and collect one resource per post. Pass that to the Serializer. This should work:

Post.collect do |post|
  PostResource.new(post, context)
end

or

Post.collect do { |post| PostResource.new(post, context) }

Answering 1)

I think your question is less about the gem itself and more about the JSON API spec which the gem implements. I would posit that the Filter section of the JSON API Spec has the answer: that it's "agnostic about the [filtering] strategies supported by a server". So, you're likely correct: the spec doesn't require a standard methodology.

In developing KnowPlace, we (@allthesignals and I) decided to implement a simple search via the filter[search] parameter. The link highlights the apply_filter override mentioned by @lgebhardt.

Thank you for the quick response, I have missed the apply_filter funcionality, yes that is sufficient for all my needs. Thank you.

@PavelJ If that fixed it for you, would you mind closing the issue? Thanks! :)

If someone else wants to filter resources by using "like" searches, this is what I did:

class ProductResource < JSONAPI::Resource
  attribute :name

  filter :name

  def self.apply_filter(records, filter, value, options = {})
    strategy = _allowed_filters.fetch(filter.to_sym, Hash.new)[:apply]

    if strategy
      if strategy.is_a?(Symbol) || strategy.is_a?(String)
        send(strategy, records, value, options)
      else
        strategy.call(records, value, options)
      end
    else
      value_regex = value.join('|')
      records.where("#{filter} REGEXP '#{value_regex}'")
    end
  end

end

I think it would be nice to use filters with predicates (like ransack does)
something like the following example

class BaseResource < JSONAPI::Resource
  abstract

  def self.ransack_filter(name, opts = {})
    opts[:apply] = ->(records, value, _options) do
      records.ransack({name => value}).result
      # or without ransack:
      # records.public_send(name, value)
    end

    filter name, opts
  end

  def ransack_filters(*names)
    options = names.extract_options!
    names.each { |name| ransack_filter(name, options) }
end

class ProductResource < BaseResource
  attribute :name

  ransack_filters :name_equals, :name_contains
end

class Product < ActiveRecord::Base
  # without ransack:
  # scope :name_equals, ->(value) { where(name: value) }
  # scope :name_contains, ->(value) { where('name LIKE ?', "%#{value}%") }
end
Was this page helpful?
0 / 5 - 0 ratings