Setting Empty-String JSONB Values to Null When Sorting with Ransack
Tags: ruby, rails, programming
Published: 2022-08-27 16:30:00

![Purple background banner with blog post title: Setting Empty-String JSONB Values to Null When Sorting with Ransack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wv1cxchxxq1za1e8lumy.png)
 

_This article has been cross-posted from the [orbit.love](https://orbit.love/) blog. You can find the original version [here](https://orbit.love/blog/setting-empty-string-jsonb-values-to-null-when-sorting-with-ransack)._

--------------------------

Using the [Ransack gem](https://github.com/activerecord-hackery/ransack) is a great way to add search functionality to your application without the need for additional libraries or dependencies.

One issue you may have is when sorting data by an attribute that is nested in a JSON column. If that attribute’s value is an empty string, it will be included in the sorted list (even when we’ve declared :nulls_last in [our Ransack configuration](https://activerecord-hackery.github.io/ransack/getting-started/simple-mode/#postgresqls-sort-option)).

In this post, I’ll be covering how to ensure that empty-string JSONB values are treated as null values and placed at the end of any sorted list when searching and sorting with Ransack.

Before we get started, here’s some sample data we’ll be using:

![table data with five members with names and data attributes including organization and location](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/69um6pff8kl0vw3zm9zo.png)
 
_Note: for Member #3 and Member #4 the empty string for location._

Our goal here is to sort our Members table by location, and to ensure that any empty-string Location values are treated as null values in the sorted list.

More specifically, we’ll need the resulting SQL query generated by Ransack to look like this:

```
SELECT DISTINCT "members".*, NULLIF("members"."data" ->> 'location', ")
FROM "members"
ORDER BY NULLIF("members"."data" ->> 'location', '') ASC NULLS LAST
```
## Sorting by JSONB nested attributes using Ransack

ransackers: special methods provided by Ransack for creating additional search functions via Arel

By taking advantage of ransackers, we can sort our table by the members’ Location JSONB attributes.

Let’s add the following to our Member model:

```
class Member < ApplicationRecord
    # ...

    ransacker :location do |parent|
		  Arel::Nodes::InfixOperation.new(
		     '->>', 
		     parent.table[:data], 
		     Arel::Nodes::Quoted.new(‘location’)
		  )
		end

     # ...
end
```
The ransacker above ensures that a member’s location (a nested JSONB attribute) is available as a top-level attribute so that we can build a table that can be sorted by members’ locations.

Our Ransack result is defined as:

```
def search_result

	 members = Member.arel_table
	 data_column = members[:data]

	 workspace
		 .members
		 .select(members[Arel.star], jsonb_attr(data_column, 'location'))
		 .ransack(ransack_filter)
		 .result(distinct: true)
 end

 def jsonb_attr(column, attr)
	 Arel::Nodes::InfixOperation.new(
		 '->>', 
		 column, 
		 Arel::Nodes::Quoted.new(attr.to_s)
 end
```
With the code above, we are making the Location JSONB attribute available in the SELECT statement of our SQL query:

```
SELECT DISTINCT "members".*, "members"."data" ->> 'location'
FROM "members"
```
When we sort our table by Location, it works. Kind of.

The issue is that the empty-string Location values have been placed at the beginning of the sorted list:

![sorted list with null values at the beginning of the list](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v19tipk5h6ss8y5p6pu2.png)

For one record, this may not be a big deal.

But consider dozens of records where the member’s Location attribute is an empty string. In that case, a user would need to sift through several blank Location values just to get to the beginning of the alphabetized list.

## Setting NULLS LAST doesn’t account for empty-string values

According to [the Ransack docs](https://activerecord-hackery.github.io/ransack/getting-started/simple-mode/#postgresqls-sort-option), we can use :nulls_first or :nulls_last to decide if we want null values to appear before or after non-null values when sorting.

We can also force nulls to always be first or last by using :nulls_always_first or :nulls_always_last.

While this is helpful, it doesn’t apply to the empty-string values that may be present in a JSONB column.

What to do? We need to set any empty-string values in the Location column as null values so that they’re sorted after the non-null values.

Setting Empty-String JSONB Values to null values
The method below will allow us to take any empty-string value and return it as NULL when Ransack builds our #search_results query:

```
def null_if(column, value)
  	Arel::Nodes::NamedFunction.new "NULLIF", [column, value]
end
```
In our code, we can place this method below our jsonb_attr method:

```
def jsonb_attr(column, attr)
	Arel::Nodes::InfixOperation.new(
		'->>', 
		column, 
		Arel::Nodes::Quoted.new(attr.to_s)
	 )
end

def null_if(column, value)
	Arel::Nodes::NamedFunction.new "NULLIF", [column, value]
end
```
Next, we need to update our jsonb_attr method by wrapping the method’s code in the null_if helper:

```
def jsonb_attr(column, attr)
	null_if(
            Arel::Nodes::InfixOperation.new('->>', column, Arel::Nodes::Quoted.new(attr.to_s)),
            Arel::Nodes::Quoted.new('')
        )
end
```
For the null_if arguments above, the **column** is the InfixOperation object, and the **value** is an empty string built by the Arel::Nodes::Quoted class.

## Updating the Location ransacker to return a NULLIF ORDER BY clause

Now, if I try to sort by Location in the table, I get a Postgres error:

```
ActiveRecord::StatementInvalid (PG::InvalidColumnReference: ERROR:  
for SELECT DISTINCT, ORDER BY expressions must appear in select list)
```
We’re seeing this error because we are trying to ORDER BY the following:

```
ORDER BY "members"."data" ->> 'location'

```
When what is stated in the SELECT statement is the following:

```
SELECT DISTINCT … NULLIF("members"."data" ->> 'location', '')
```
Since ORDER BY expressions need to be the same expressions set in the SELECT list, we need to make sure that the NULLIF statement is present in the ORDER BY clause.

To do this, let’s update our Location ransacker on the Member model:

```
class Member < ApplicationRecord
    # ...
    ransacker :location do |parent|
			Arel::Nodes::NamedFunction.new 'NULLIF',
                                   [
                                     Arel::Nodes::InfixOperation.new(
                                       '->>',
                                       parent.table[:data],
                                       Arel::Nodes::Quoted.new('location')
                                     ),
                                     Arel::Nodes::Quoted.new('')
                                   ]
		end
    
    #...
end
```

Here, we are taking the code within our null_if method and using it explicitly in our ransacker in the Member model.

The null_if method takes two arguments: **column** (the InfixOperation object) and **value** (the Quoted object).

Adding the null_if wrapper to the Location ransacker allows us to ORDER BY that same NULLIF(...) argument that we’ve stated in our SELECT statement (via our Ransack query).

Here’s what the resulting query looks like:

```
SELECT DISTINCT "members".*, NULLIF("members"."data" ->> 'location', ")
FROM "members"
ORDER BY NULLIF("members"."data" ->> 'location', '') ASC NULLS LAST
```

And there you have it! With this, the resulting sorts by location place the empty string values at the end of list, no matter the sorting direction.

_Sorting by Location - alphabetical_
![List sorted by location in alphabetical order with null values last](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dljjxgitzo00tyry03yt.png)

_Sorting by Location - reverse-alphabetical_
![List sorted by location in reverse-alphabetical order with null values last](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mfi8vmihp21aqrg6yafe.png)
  

## Conclusion

This post covered how to set empty-string values of a JSONB column as null values when using the Ransack gem to search and sort data.

I hope this helps in all of your table-sorting endeavors that involve Ransack, JSONB nested attributes, and null values!

---

Thanks to [@hadees on GitHub](https://gist.github.com/hadees/cff6af2b53d340b9b4b2#file-arel_helpers-rb-L32) for a useful Arel gist that provided the null_if method we used in this post.

‍