A Ruby gem that converts GraphQL queries into robust ActiveRecord queries with advanced filtering, sorting, and searching capabilities.
- Installation
- Quick Start
- Features
- Configuration
- Usage
- API Reference
- Advanced Usage
- Performance Considerations
- Troubleshooting
- License
Prerequisites:
- Ruby >= 3.1.0
- Rails with ActiveRecord
- GraphQL Ruby gem
Add the gem to your project:
gem install graphql_query
# Or add to your Gemfile
gem 'graphql_query'Then run:
bundle install- Configure the gem in an initializer (
config/initializers/graphql_query.rb):
require 'graphql'
require 'graphql_query'
GraphqlQuery::Sorter.enum_class = GraphQL::Schema::Enum- Create a simple filter:
class UserFilter < Types::Base::InputObject
extend GraphqlQuery::Filter
graphql_name 'UserFilter'
include_filter_arguments(GraphQL::Types::ID, %i[eq neq in nin])
end- Use it in a resolver:
def users(**args)
query = User.all
GraphqlQuery::Main.new(query, args).to_relation
end- 🔍 Advanced Filtering: Support for 8 different filter operators
- 📊 Flexible Sorting: Multi-field sorting with custom order
- 🔎 Text Search: Full-text search across multiple fields
- ⚡ Performance: Generates efficient ActiveRecord queries
- 🛡️ Security: SQL injection protection built-in
- 🔧 Extensible: Easy to customize and extend
Create an initializer file to configure the gem:
# config/initializers/graphql_query.rb
require 'graphql'
require 'graphql_query'
# Set the GraphQL Schema Enum class for sorting
GraphqlQuery::Sorter.enum_class = GraphQL::Schema::EnumCreate filters by extending the GraphqlQuery::Filter class and calling include_filter_arguments:
| Operator | Description | Example |
|---|---|---|
eq |
Equal | { id: { eq: "123" } } |
neq |
Not equal | { status: { neq: "inactive" } } |
in |
In a list | { category: { in: ["tech", "science"] } } |
nin |
Not in a list | { priority: { nin: ["low", "medium"] } } |
gt |
Greater than | { age: { gt: 18 } } |
gte |
Greater than or equal | { score: { gte: 85 } } |
lt |
Less than | { price: { lt: 100 } } |
lte |
Less than or equal | { quantity: { lte: 50 } } |
Basic ID Filter:
module Types
module Filters
class IdFilter < Types::Base::InputObject
extend GraphqlQuery::Filter
graphql_name 'IDFilter'
description 'Filter commands for ID fields'
include_filter_arguments(GraphQL::Types::ID, %i[eq neq in nin])
end
end
endString Filter:
module Types
module Filters
class StringFilter < Types::Base::InputObject
extend GraphqlQuery::Filter
graphql_name 'StringFilter'
description 'Filter commands for string fields'
include_filter_arguments(GraphQL::Types::String, %i[eq neq in nin])
end
end
endNumeric Filter:
module Types
module Filters
class IntFilter < Types::Base::InputObject
extend GraphqlQuery::Filter
graphql_name 'IntFilter'
description 'Filter commands for integer fields'
include_filter_arguments(GraphQL::Types::Int, %i[eq neq in nin gt gte lt lte])
end
end
endCombined Filter Input:
module Types
module Users
class FilterInput < Types::Base::InputObject
graphql_name 'UsersFilterInput'
description 'Filter criteria for users'
argument :id, Types::Filters::IdFilter, required: false
argument :name, Types::Filters::StringFilter, required: false
argument :age, Types::Filters::IntFilter, required: false
argument :status, Types::Filters::StringFilter, required: false
end
end
endCreate sorters by extending the GraphqlQuery::Sorter class:
module Types
module Users
class SortInput < Types::Base::InputObject
extend GraphqlQuery::Sorter
graphql_name 'UsersSortInput'
description 'Sort criteria for users'
# Model name and sortable fields (in camelCase)
include_sort_arguments('User', %i[name email createdAt updatedAt random])
end
end
endrandom: Provides random ordering usingRANDOM()function- camelCase fields: Automatically converted to snake_case (e.g.,
createdAt→created_at)
Create search inputs for text-based searching:
module Types
module Users
class SearchInput < Types::Base::InputObject
graphql_name 'UsersSearchInput'
description 'Search criteria for users'
argument :name, GraphQL::Types::String,
description: 'Search users by name',
required: false
argument :email, GraphQL::Types::String,
description: 'Search users by email',
required: false
argument :bio, GraphQL::Types::String,
description: 'Search users by bio',
required: false
end
end
endGraphQL Field Definition:
module Types
class QueryType < Types::Base::Object
field :users, UserType.connection_type, null: false do
description 'Fetch users with advanced filtering, sorting, and searching'
argument :filter_by, Types::Users::FilterInput, required: false
argument :search_by, Types::Users::SearchInput, required: false
argument :sort_by, [Types::Users::SortInput], required: false
end
end
endResolver Implementation:
module Resolvers
class UsersResolver < GraphQL::Schema::Resolver
def users(**args)
# Start with your base query
base_query = User.includes(:posts, :comments)
# Apply GraphQL Query processing
GraphqlQuery::Main.new(base_query, args).to_relation
end
end
endGraphQL Query Example:
query GetUsers {
users(
filterBy: {
age: { gte: 18, lte: 65 }
status: { in: ["active", "premium"] }
name: { neq: "admin" }
}
searchBy: {
name: "john"
email: "gmail"
}
sortBy: [
{ field: "name", order: ASC }
{ field: "createdAt", order: DESC }
]
) {
edges {
node {
id
name
email
age
status
}
}
}
}This query will:
- Filter users aged 18-65 with active/premium status, excluding admin
- Search for "john" in name field and "gmail" in email field
- Sort by name (ascending) then by creation date (descending)
The main class that processes GraphQL arguments and generates ActiveRecord queries.
GraphqlQuery::Main.new(relation, args)relation: ActiveRecord relation or model classargs: Hash of GraphQL arguments (typically from resolver)
#to_relation: Returns the processed ActiveRecord relation
Module for creating filter input types.
include_filter_arguments(type, operators): Adds filter arguments to GraphQL input typetype: GraphQL type (e.g.,GraphQL::Types::String)operators: Array of operator symbols (e.g.,%i[eq neq in nin])
Module for creating sort input types.
enum_class=: Set the GraphQL enum class to useenum_class: Get the current GraphQL enum class
include_sort_arguments(model_name, fields): Adds sort arguments to GraphQL input typemodel_name: String name of the modelfields: Array of sortable field names (camelCase)
You can start with complex base queries:
def users(**args)
base_query = User
.joins(:profile)
.includes(:posts)
.where(active: true)
.where('profiles.verified = ?', true)
GraphqlQuery::Main.new(base_query, args).to_relation
endWorks seamlessly with connection-based pagination:
field :users, UserType.connection_type, null: false, max_page_size: 100The gem automatically handles SQL injection protection, but consider:
- Validate input sizes to prevent DoS attacks
- Use reasonable limits on array inputs
- Monitor query complexity and execution time
Ensure proper database indexes for filtered and sorted fields:
# Migration example
class AddIndexesToUsers < ActiveRecord::Migration[7.0]
def change
add_index :users, :age
add_index :users, :status
add_index :users, [:name, :created_at]
end
end- Set reasonable connection limits
- Use pagination for large datasets
- Consider implementing query complexity analysis
1. "Filter X does not exist" Error
# Make sure the operator is supported
include_filter_arguments(GraphQL::Types::String, %i[eq neq]) # ✓ Valid
include_filter_arguments(GraphQL::Types::String, %i[contains]) # ✗ Invalid2. "enum_class not set" Error
# Add to your initializer
GraphqlQuery::Sorter.enum_class = GraphQL::Schema::Enum3. Field Not Found in Database
# Ensure field names match your database columns
# camelCase is automatically converted to snake_case
include_sort_arguments('User', %i[createdAt]) # → created_at4. Search Not Working
# Search uses ILIKE, ensure your database supports it
# For case-sensitive databases, consider adding LOWER() functionsEnable query logging to see generated SQL:
# In Rails console or test
ActiveRecord::Base.logger = Logger.new(STDOUT)We welcome contributions! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests for new functionality
- Ensure all tests pass (
bundle exec rspec) - Run the linter (
bundle exec rubocop) - Commit your changes (
git commit -am 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
git clone https://github.com/zenfi/graphql-query.git
cd graphql-query
bundle install
bundle exec rspecThis gem is available as open source under the terms of the MIT License.
Questions? Open an issue on GitHub or start a discussion.