Ruby on Rails GraphQL API Tutorial: Filtering with Custom Fields and Class Methods
Tags: rails, graphql, api, tutorial
Published: 2019-09-29 18:39:38

---
title: "Ruby on Rails GraphQL API Tutorial: Filtering with Custom Fields and Class Methods"
published: true
description: expanding has_many functionality on rails models to add filtering for a graphql api
tags: rails, graphql, api, tutorial
series: "Ruby on Rails GraphQL API Tutorial"
---

Building on our Rails GraphQL API from previous articles, we will now look at filtering data using two tools:
1. **Custom Fields** in our GraphQL Queries
2. **Class Methods** directly on our Rails Models (as inspired by [this great StackOverflow response](https://stackoverflow.com/a/9547179))

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](https://mattboldt.com/2019/01/07/rails-and-graphql/)**. Thanks again, Matt!!



# Overview 
In this third article, we'll go through the steps to:
* add a `status` column to our `Payments` table (and update our seed file)
* add a `successful` class method to our `Order` 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](https://insomnia.rest/)

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:

```ruby
# /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`:

```ruby
# /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`:

```ruby
# /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](https://stackoverflow.com/a/9547179), 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:

```ruby
# /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:

```ruby
# /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](https://apidock.com/rails/ActiveRecord/QueryMethods/where). 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:

```ruby
# /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:

```ruby
# /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:

```graphql
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/ :

![GraphQL query in Insomnia showing status field added](https://thepracticaldev.s3.amazonaws.com/i/ll207oyxapphzqnj4unh.png)


Now let's get filterin'!

### Update `OrderType` with a new `:successful_payments` custom field

Our `OrderType` currently looks like this:

```ruby
# /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:

```ruby
# /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`)**

```graphql
query {
    allOrders {
        id
        description
        total      
        successfulPayments {
          	id
          	amount
          	status
        }
    }
}
```

Start the Rails server with `rails s`, and run the query in Insomnia:

![GraphQL query in Insomnia showing successfulPayments field](https://thepracticaldev.s3.amazonaws.com/i/ntmb97ovoquzemq7uvb1.png)



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!](https://github.com/isalevine/devto-graphql-ruby-api)

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](https://stackoverflow.com/a/9547179)

And once again -- __*thank you*__ to Matt Boldt and [his AWESOME Rails GraphQL tutorial](https://mattboldt.com/2019/01/07/rails-and-graphql/) 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!