Graphql-ruby: Ransack like filter support

Created on 23 May 2018  路  5Comments  路  Source: rmosolgo/graphql-ruby

Is there an easy way to get ransack like filters?

I currently use something like this

class Types::RansackPredicateType < GraphQL::Schema::InputObject
  argument :eq, String, "Equals", required: false
  argument :cont, String, "Contains", required: false
  argument :nil, Boolean, "Is Null", required: false
end
class Types::RansackUserType < GraphQL::Schema::InputObject
  User.ransackable_attributes.each do |attr|
    argument attr, Types::RansackPredicateType, required: false
  end
  argument :m, String, "and, or", required: false
  argument :g, [Types::RansackUserType], required: false
end



md5-b03279ab841bf182554e09ed355e5fbf



which then allows queries like



md5-3733f12fc7ac7363184c8c48cf87daa2



```graphql
{user(q: {g: [{m: "or", email: {cont: "@example.com"}, name: {eq: "test"}}, {confirmed_at: {nil: false}}]}) {id}}

the types should probably be stricter e.g. :m could be an enum and RansackPredicateType could be split into several types so that integer_column: { cont: "a" } would be disallowed. i think it should also be possible to add other nested RansackXTypes for associated records.

(side questian: is there a way to get rid of the q: without essentially duplicating RansackUserType?)

or is there a completely different approach to filtering which dosn't require writing hundreds of filters?

Most helpful comment

I found myself looking for this as well, and came up with a solution that is pretty simple:

module GraphQL
  module RansackSupport
    def ransack(base, **args)
      base.ransack(build_ransack_query(base, **args)).result
    end

    def build_ransack_query(base, **args)
      atts = base.ransackable_attributes
      sort = args.delete(:sort)
      ransack_params = args.reduce({}) do |memo, (k,v)|
        memo[atts.include?(k.to_s) ? :"#{k}_eq" : k] = v
        memo
      end
      if sort
        ransack_params[:s] = "#{sort[:column]} #{sort[:direction]}"
      end
      ransack_params
    end
  end
end

Include the module into your base type, then you can resolve fields like so:

field :posts, [Types::Post], null: false do
  argument :id, ID, required: false
  argument :title_cont, String, required: false
end

def posts(**args)
  # you can also do ransack(self.object.posts, **args)
  ransack(Post, **args)
end

All 5 comments

That would be really cool, but I'm not sure this gem should support this. It looks like this would be ideal gem/extension that hooks into this gem. What do you think?

Btw, have you seen https://github.com/opencrud/opencrud? I know some folks on the GraphQL Slack's #ruby channel were talking about building something to support this syntax, maybe interesting for you?

I have not but it's indeed what i have been searching for.

You are probably right that it belongs in an extension so i'll close this issue.

I found myself looking for this as well, and came up with a solution that is pretty simple:

module GraphQL
  module RansackSupport
    def ransack(base, **args)
      base.ransack(build_ransack_query(base, **args)).result
    end

    def build_ransack_query(base, **args)
      atts = base.ransackable_attributes
      sort = args.delete(:sort)
      ransack_params = args.reduce({}) do |memo, (k,v)|
        memo[atts.include?(k.to_s) ? :"#{k}_eq" : k] = v
        memo
      end
      if sort
        ransack_params[:s] = "#{sort[:column]} #{sort[:direction]}"
      end
      ransack_params
    end
  end
end

Include the module into your base type, then you can resolve fields like so:

field :posts, [Types::Post], null: false do
  argument :id, ID, required: false
  argument :title_cont, String, required: false
end

def posts(**args)
  # you can also do ransack(self.object.posts, **args)
  ransack(Post, **args)
end

I solved problem using this:

class Types::CompanyFilterType < Types::BaseInputObject
  Company.ransackable_attributes.each do |attr|
    Ransack.predicates.keys.map do |predicate|
      argument "#{attr}_#{predicate}".to_sym, String, required: false, camelize: false
    end
  end
end

and

module Resolvers
  class CompaniesResolver < BaseResolver
    type [Types::CompanyType], null: false

    argument :filter, Types::CompanyFilterType, required: false

    def resolve(filter:)
      Company.ransack(filter.to_hash).result
    end
  end
end

Basically, Types::CompanyFilterType create a bunch predicates as argument for all attributes

irb(main):001:0> Types::CompanyFilterType.arguments.keys
=> ["id_eq", "id_eq_any", "id_eq_all", "id_not_eq", "id_not_eq_any", "id_not_eq_all", "id_matches", "id_matches_any", "id_matches_all", "id_does_not_match", "id_does_not_match_any", "id_does_not_match_all", "id_lt", "id_lt_any", "id_lt_all", "id_lteq", "id_lteq_any", "id_lteq_all", "id_gt", "id_gt_any", "id_gt_all", "id_gteq", "id_gteq_any", "id_gteq_all", "id_in", "id_in_any", "id_in_all", "id_not_in", "id_not_in_any", "id_not_in_all", "id_cont", "id_cont_any", "id_cont_all", "id_not_cont", "id_not_cont_any", "id_not_cont_all", "id_i_cont", "id_i_cont_any", "id_i_cont_all", "id_not_i_cont", "id_not_i_cont_any", "id_not_i_cont_all", "id_start", "id_start_any", "id_start_all", "id_not_start", "id_not_start_any", "id_not_start_all", "id_end", "id_end_any", "id_end_all", "id_not_end", "id_not_end_any", "id_not_end_all", "id_true", "id_not_true", "id_false", "id_not_false", "id_present", "id_blank", "id_null", "id_not_null", "canonical_name_eq", "canonical_name_eq_any", "canonical_name_eq_all", "canonical_name_not_eq", "canonical_name_not_eq_any", "canonical_name_not_eq_all", "canonical_name_matches", ..."]
Was this page helpful?
0 / 5 - 0 ratings

Related issues

theodorton picture theodorton  路  3Comments

peterphan1996 picture peterphan1996  路  3Comments

ecomuere picture ecomuere  路  3Comments

dmc2015 picture dmc2015  路  3Comments

sayduck-daniel picture sayduck-daniel  路  3Comments