Building on our Rails GraphQL API from previous articles, we will now look at filtering data using two tools:
- Custom Fields in our GraphQL Queries
- Class Methods directly on our Rails Models (as inspired by this great StackOverflow response)
We will add a status
column to our Payments
table, and add a filter to only return "Successful"
payments in our queries.
Once again, this tutorial is largely adapted from this AMAZING tutorial by Matt Boldt. Thanks again, Matt!!
Overview
In this third article, we'll go through the steps to:
- add a
status
column to ourPayments
table (and update our seed file) - add a
successful
class method to ourOrder
model to filter by"Successful"
payments - add a custom field to our GraphQL
order_type
to call the.successful
class method - write and execute a GraphQL query to demonstrate the filter in Insomnia
Let's dive in!
Use Case: Filtering by Status
Let's say we want our API to know the difference between "Successful"
and "Failed"
payments. This would allow us to use only "Successful"
payments when doing things like calculating a total balance, generating receipts, or other situations where we don't want to expose every single payment.
Rails Migration: Add status
to Payments
Run rails g migration AddColumnStatusToPayments
. This will create a new database migration file. Open it up, and create a new column for status
on the Payments
table:
# /db/migrate/20190929153644_add_column_status_to_payments.rb
class AddColumnStatusToPayments < ActiveRecord::Migration[5.2]
def change
add_column :payments, :status, :string
end
end
We'll also update our seed file to add some "Successful"
payments to our database, along with one "Failed"
payment on our first Order
:
# /db/seeds.rb
Order.destroy_all
Payment.destroy_all
order1 = Order.create(description: "King of the Hill DVD", total: 100.00)
order2 = Order.create(description: "Mega Man 3 OST", total: 29.99)
order3 = Order.create(description: "Punch Out!! NES", total: 0.75)
payment1 = Payment.create(order_id: order1.id, amount: 20.00, status: "Successful")
payment2 = Payment.create(order_id: order2.id, amount: 1.00, status: "Successful")
payment3 = Payment.create(order_id: order3.id, amount: 0.25, status: "Successful")
payment4 = Payment.create(order_id: order1.id, amount: 5.00, status: "Failed")
Now, run rails db:migrate
and rails db:seed
to run the migration and re-seed the database.
Check out our database with rails c
Go ahead and run rails c
to open up a Rails console in your terminal.
Remember that we created our Order
model to have a has_many
relationship with Payments
:
# /app/models/order.rb
class Order < ApplicationRecord
has_many :payments
end
In our Rails console, we can use Order.all[0]
to check out the first Order
in the database, and Order.all[0].payments
to see its Payments
:
[10:28:27] (master) devto-graphql-ruby-api
// ♥ rails c
Running via Spring preloader in process 6225
Loading development environment (Rails 5.2.3)
2.6.1 :001 > Order.all[0]
Order Load (0.5ms) SELECT "orders".* FROM "orders"
=> #<Order id: 16, description: "King of the Hill DVD", total: 100.0, created_at: "2019-09-29 17:20:34", updated_at: "2019-09-29 17:20:34">
2.6.1 :002 > Order.all[0].payments
Order Load (0.2ms) SELECT "orders".* FROM "orders"
Payment Load (0.2ms) SELECT "payments".* FROM "payments" WHERE "payments"."order_id" = ? LIMIT ? [["order_id", 16], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Payment id: 1, order_id: 16, amount: 20.0, created_at: "2019-09-29 17:20:34", updated_at: "2019-09-29 17:20:34", status: "Successful">, #<Payment id: 4, order_id: 16, amount: 5.0, created_at: "2019-09-29 17:20:34", updated_at: "2019-09-29 17:20:34", status: "Failed">]>
Cool! We can see that our first Order
has both a "Successful"
and a "Failed"
payment in its associations.
Now, let's look at filtering our results to only return "Successful"
payments with our GraphQL queries!
Class Methods on Rails Models
In my previous Rails projects, I didn't do much in my Models' files beyond setting up has_many / belongs_to
relationships. However, a great StackOverflow discussion showed me we can expand a has_many
declaration with additional functionality. The article itself demonstrates this with a has_many-through
relationship, but the pattern works the same for simple has_many
relationships too!
Open up our Order
model, and build out the has_many
declaration by adding a do...end
block:
# /app/models/order.rb
class Order < ApplicationRecord
has_many :payments do
# we can add additional functionality here!
end
end
This is the perfect place to filter our payments: any methods we define here can be chained directly onto order.payments
!
Let's make a method to use SQL to filter payments
to only "Successful"
ones:
# /app/models/order.rb
class Order < ApplicationRecord
has_many :payments do
def successful
where("status = ?", "Successful")
end
end
end
Now, if we run order.payments.successful
, we will automatically invoke the ActiveRecord where
method. This will only allow payments
with the status
equal to "Successful"
to be returned!
Save the order.rb
file, and open up a Rails console with rails c
again. Now run Order.all[0].payments
, then Order.all[0].payments.successful
to see the filter in action:
[10:41:36] (master) devto-graphql-ruby-api
// ♥ rails c
Running via Spring preloader in process 6277
Loading development environment (Rails 5.2.3)
2.6.1 :001 > Order.all[0].payments
Order Load (1.0ms) SELECT "orders".* FROM "orders"
Payment Load (0.2ms) SELECT "payments".* FROM "payments" WHERE "payments"."order_id" = ? LIMIT ? [["order_id", 16], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Payment id: 1, order_id: 16, amount: 20.0, created_at: "2019-09-29 17:20:34", updated_at: "2019-09-29 17:20:34", status: "Successful">, #<Payment id: 4, order_id: 16, amount: 5.0, created_at: "2019-09-29 17:20:34", updated_at: "2019-09-29 17:20:34", status: "Failed">]>
2.6.1 :002 > Order.all[0].payments.successful
Order Load (0.2ms) SELECT "orders".* FROM "orders"
Payment Load (0.4ms) SELECT "payments".* FROM "payments" WHERE "payments"."order_id" = ? AND (status = 'Successful') LIMIT ? [["order_id", 16], ["LIMIT", 11]]
=> #<ActiveRecord::AssociationRelation [#<Payment id: 1, order_id: 16, amount: 20.0, created_at: "2019-09-29 17:20:34", updated_at: "2019-09-29 17:20:34", status: "Successful">]>
Great! Now we can chain order.payments.successful
to use this filter. Now let's connect this functionality to our GraphQL query!
Add a Custom Field to a Query
Update PaymentType
with the new :status
field
Turning our attention back to our GraphQL Types, here's what our current PaymentType
and its fields look like:
# /app/graphql/types/payment_type.rb
module Types
class PaymentType < Types::BaseObject
field :id, ID, null: false
field :amount, Float, null: false
end
end
Since we've added a status
column to the Rails model, let's add a :status
field to our GraphQL type:
# /app/graphql/types/payment_type.rb
module Types
class PaymentType < Types::BaseObject
field :id, ID, null: false
field :amount, Float, null: false
field :status, String, null: false
end
end
We can now update our previous query for allOrders
to include :status
too:
query {
allOrders {
id
description
total
payments {
id
amount
status
}
paymentsCount
}
}
Run rails s
to start the Rails server, then send the query in Insomnia to http://localhost:3000/graphql/ :
Now let's get filterin'!
Update OrderType
with a new :successful_payments
custom field
Our OrderType
currently looks like this:
# /app/graphql/types/order_type.rb
module Types
class OrderType < Types::BaseObject
field :id, ID, null: false
field :description, String, null: false
field :total, Float, null: false
field :payments, [Types::PaymentType], null: false
field :payments_count, Integer, null: false
def payments_count
object.payments.size
end
end
end
Our :payments
field uses the has_many
relationship to pull all the belonging PaymentType
instances into the response.
We also have one custom field, :payments_count
, where we can call class methods from the Order
object. (Don't forget that quirk about using object
to refer to the Order
instance!)
Let's add a new custom field, :successful_payments
, and define a method (with the same name) that will simply use our new order.payments.successful
method chain:
# /app/graphql/types/order_type.rb
module Types
class OrderType < Types::BaseObject
field :id, ID, null: false
field :description, String, null: false
field :total, Float, null: false
field :payments, [Types::PaymentType], null: false
field :payments_count, Integer, null: false
field :successful_payments, [Types::PaymentType], null: false
def payments_count
object.payments.size
end
def successful_payments
object.payments.successful
end
end
end
Our new custom field :successful_payments
returns an array of PaymentTypes
via [Types::PaymentType]
(just like the :payments
field does). We also set null: false
by default to catch errors with the data.
Let's update the allOrders
query to include the new :successful_payments
field. (I've also taken out the payments
and paymentCount
fields.)
Don't forget to change the snake_case to camelCase! (:successful_payments
=> successfulPayments
)
query {
allOrders {
id
description
total
successfulPayments {
id
amount
status
}
}
}
Start the Rails server with rails s
, and run the query in Insomnia:
Awesome! Now, our Rails model Order
is providing a simple, one-word .successful
filter for its Payments
. Using it in our GraphQL query is as simple as making a new field that calls that method!
Conclusion
We've now implemented a GraphQL API with the ability to filter the Payments
belonging to an Order
by their "Successful"
status! From here, we can build additional functionality to use the successful payments` data--for instance, calculating a current balance based on the order's total.
Here's the repo for the code in this article, too. Feel free to tinker around with it!
Here's another shameless plug for that awesome StackOverflow reply demonstrating how you can build out functionality on a Rails model's has_many
and has_many-through
relationship: https://stackoverflow.com/a/9547179
And once again -- thank you to Matt Boldt and his AWESOME Rails GraphQL tutorial for helping me get this far! <3
Any tips or advice for using GraphQL in Rails, or GraphQL in general? Feel free to contribute below!
Top comments (2)
Hi Isa, thanks for these tutorials, so great to get started with graphql.
I ran into a problem, looks like there shouldn't be a
do
in this def:Yes, you are absolutely right Maia--thank you for catching that! The only
do
should be afterhas_many :payments do
. Snippet has been corrected! :)