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
statuscolumn to ourPaymentstable (and update our seed file) - add a
successfulclass method to ourOrdermodel to filter by"Successful"payments - add a custom field to our GraphQL
order_typeto call the.successfulclass 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
doin this def:Yes, you are absolutely right Maia--thank you for catching that! The only
doshould be afterhas_many :payments do. Snippet has been corrected! :)