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! :)