Rails Named Scopes with React Implementation
Named Scopes are class methods in your Rails model that returns an ActiveRecord association. Scopes are great for associations that are queried frequently. This blog post will show you how to use Named Scopes in your Rails project with a React frontend.
This is a spiritual successor to another blog post that I did because I am using data imported from a Restaurant Grades CSV: https://medium.com/@josephharwood_62087/importing-csv-into-rails-a0a3b1af5764
A common use case of Named Scopes is for filtering active users on a website. Instead of doing a hard delete when a user deactivates their account, the user can be marked as inactive and the email address of that user can still be on the backend in case they want to sign up again. The site would then use the Named Scope for filtering active users for querying information about them. It would look something like this:
# models/user.rbscope :active, -> {where(status: "active")}
scope :inactive, -> {where(status: "inactive")}# controllers/users_controller.rbdef index
@users = User.all.active
end
You can also chain multiple scopes together. It always returns an array and it will be empty if none of the criteria is met.
Rails Model
I created 3 scopes for the Grades table.
- Critical_flags: returns all “Critical” restaurants
- Boro: returns all restaurants in a borough
- Boro_critical_flags: returns “Critical” restaurants in whatever borough is inputted
# models/grade.rbclass Grade < ApplicationRecord
scope :critical_flags, -> { where critical_flag: 'Critical' }
scope :boro, ->(boro_name) { # No space between "->" and "("
where(:boro => boro_name)
}
scope :boro_critical_flags, ->(boro_name) { # No space between "->" and "("
where(["boro = :boro and critical_flag = :critical_flag",
{ boro: boro_name, critical_flag: "Critical" }])
}
end
Routes
You will need custom routes to GET and POST to the scopes. I will explain why you need the POST route later in this post.
# config/routes.rbRails.application.routes.draw do
resources :grades
get 'critical', :to => 'grades#critical_restaurants'
get 'query', :to => 'grades#query'
post 'query', :to => 'grades#query'
end
Controller
I scaffolded the Grades controller and added these bits for the scopes I created in the model. The rest of the controller should have all of the normal RESTful methods.
# controllers/grades_controller.rbclass GradesController < ApplicationController
skip_before_action :verify_authenticity_token...def critical_restaurants
@grades = Grade.critical_flags
render 'index'
enddef query
if !!params["boro"] && !!params["critical_flag"]
@grades = Grade.boro_critical_flags(params["boro"])
else if !!params["boro"]
@grades = Grade.boro(params["boro"])
end
end
render 'index'
end...end
You will need this at the top of your controller in order to POST to the backend:
skip_before_action :verify_authenticity_token
Otherwise, you will get an error that is in this StackOverflow post:
Both of these methods reuse the index view so that the data can be displayed.
The Critical_restaurants method allows users to query http://localhost:3000/critical for all “Critical” restaurants.
The Query method uses the POST method to check for certain params and uses conditionals to determine how to filter. Nothing is actually being created in the Grades table. There also isn’t a localhost address being created like in the other method, but you still need a GET route for it to return the desired output in React. The boro input is purely informational. I’ll show you how this lines up with the React app now.
React
I have the fetches in App.js and pass it down in the props to Data.js. I checked the returned JSON in the debugger for each method.
// App.jsfetchCritical = () => {
fetch(`http://localhost:3000/critical`, {
headers : {
'Content-Type': 'application/json',
'Accept': 'application/json'
}})
.then(r => r.json())
.then(r => {
debugger
})
}fetchBoro = (boro) => {
fetch(`http://localhost:3000/query`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
boro: boro,
})
})
.then(r => r.json())
.then(r => {
debugger
})
}fetchBoroCritical = (boro) => {
fetch(`http://localhost:3000/query`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
boro: boro,
critical_flag: "Critical"
})
})
.then(r => r.json())
.then(r => {
debugger
})
}
You can see below how I tested using buttons to see if I was fetching the data that I expected. I was passing “BROOKLYN” in a POST method as the borough to filter by.
// components/Data.jsconst postTest = () => {
props.fetchBoro("BROOKLYN")
}const postTest2 = () => {
props.fetchBoroCritical("BROOKLYN")
}const getTest = () => {
props.fetchCritical()
}return (
<div>
<button onClick={postTest}> Boro Filter </button>
<button onClick={postTest2}> Boro Critical Filter </button>
<button onClick={getTest}> Critical Filter </button>
</div>
)
This should be everything that you need in order to use Named Scopes in your Rails and React projects. The advantage of using scopes is that you don’t have to do all the filtering on the React frontend using state (I always found that way convoluted). The scope approach is cleaner and for me, much easier to comprehend and reuse. It is also more cost efficient and quicker when querying the backend versus filtering on the frontend.
I hope that you find this useful in your projects!