module Types
class PostType < Types::BaseObject
field :id, ID, null: false
field :title, String, null: false
field :body, String, null: false
field :excerpt, String, null: true
field :published_at, GraphQL::Types::ISO8601DateTime, null: true
field :author, Types::UserType, null: false
field :comments, [Types::CommentType], null: false
field :views, Integer, null: false
field :likes_count, Integer, null: false
def excerpt
object.body&.truncate(200)
end
def comments
# Use dataloader to batch load comments
dataloader.with(Sources::AssociationSource, Comment, :post_id).load(object.id)
end
end
end
module Types
class QueryType < Types::BaseObject
field :posts, [Types::PostType], null: false do
argument :first, Integer, required: false, default_value: 20
argument :offset, Integer, required: false, default_value: 0
end
field :post, Types::PostType, null: true do
argument :id, ID, required: true
end
def posts(first:, offset:)
Post.published.includes(:author).limit(first).offset(offset)
end
def post(id:)
Post.find(id)
end
end
end
GraphQL provides clients flexibility to request exactly the data they need, reducing over-fetching and under-fetching compared to REST. The graphql-ruby gem integrates GraphQL into Rails with a schema-first approach. I define types for each model, fields for attributes, and resolvers for custom logic. The N+1 query problem is more pronounced in GraphQL, so I use graphql-batch for automatic batching and caching of database queries. Authorization happens at the field level using policies or pundit integration. GraphQL's introspection enables powerful tooling like GraphiQL for exploration. The trade-off is increased complexity—REST is simpler for straightforward CRUD, but GraphQL shines for complex frontend requirements with varied data needs.
Related snips
class CommentsController < ApplicationController
before_action :set_post
def create
@comment = @post.comments.build(comment_params)
System test: asserting Turbo Stream responses
class Post < ApplicationRecord
belongs_to :author, class_name: 'User'
has_many :comments, dependent: :destroy
scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
scope :draft, -> { where(published_at: nil) }
ActiveRecord scopes for reusable query logic
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
class PostsController < ApplicationController
def index
@posts = Post.includes(:author)
.order(created_at: :desc)
.page(params[:page])
.per(10)
Turbo Frames: infinite scroll with lazy-loading frame
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
Share this code
Here's the card — post it anywhere.