rails new stimulus_reflex_table_filter --skip-coffee --webpack=stimulus -d postgresql
cd stimulus_reflex_table_filter
rails db:create
git init
git add README.md
git commit -m "first commit"
git remote add origin https://github.com/andrewmcodes/stimulus_reflex_table_filter.git
git push -u origin master
Add stimulus_reflex, annotate, faker, and standard to Gemfile
. If you want to deploy the app to heroku, also add the redis gem.
bundle
Add stimulus_reflex to our package.json
yarn add stimulus_reflex
Add Tailwind
yarn add tailwindcss --dev
./node_modules/.bin/tailwind init
Setup Tailwind: instructions
Scaffold Restaurant
and remove all actions except :index
rails generate model Restaurant name:string stars:integer price:integer category:string
rails generate controller restaurants index
These commands should have generated:
db/migrate/20191013003319_create_restaurants.rb
app/models/restaurant.rb
test/models/restaurant_test.rb
test/fixtures/restaurants.yml
app/controllers/restaurants_controller.rb
app/views/restaurants
app/views/restaurants/index.html.erb
test/controllers/restaurants_controller_test.rb
app/helpers/restaurants_helper.rb
app/assets/stylesheets/restaurants.scss
Lets check the generated migration and modify that slighly:
class CreateRestaurants < ActiveRecord::Migration[6.0]
def change
create_table :restaurants do |t|
t.string :name, null: false
t.integer :stars, null: false, default: 0
t.integer :price, null: false, default: 1
t.string :category, null: false
t.timestamps
end
end
end
Lets also add some seeds:
# db/seeds.rb
101.times do
Restaurant.create(
name: Faker::Restaurant.name,
stars: [1, 2, 3, 4, 5].sample,
price: [1, 2, 3].sample,
category: Faker::Restaurant.type,
)
end
Ok now lets run rails db:migrate db:seed
to run our migration and add some records to our database. Let's also run bundle exec annotate
to annotate some of our files with our table info.
Let's update app/models/restaurant.rb
class Restaurant < ApplicationRecord
validates_inclusion_of :stars, in: 0..5
validates_inclusion_of :price, in: 1..3
FILTERS = %w[name stars price category].freeze
end
Go to config/routes.rb
and add root "restaurants#index
.
Your routes file should now look like:
Rails.application.routes.draw do
root "restaurants#index"
end
Update app/controllers/restaurants_controller.rb
to only support the :index
method.
class RestaurantsController < ApplicationController
def index
session[:filter] = "name" unless filter_permitted?(session[:filter])
@filtered_restaurants = set_filter_ordered_restaurants
end
private
def set_filter_ordered_restaurants
if session[:filter_order] == :reverse
Restaurant.order(session[:filter]).reverse
else
Restaurant.order(session[:filter])
end
end
def filter_permitted?(filter)
Restaurant::FILTERS.include? filter
end
end
Update app/views/restaurants/index.html.erb
so that table column headers are wired to the StimulusReflex action filter
, passing in the current active filter as an attribute.
<thead>
<tr class="text-left bg-gray-800 text-white">
<% Restaurant::FILTERS.each do |filter| %>
<th class="px-4 py-2">
<%= link_to filter.capitalize, "#", class: filter_css(filter), data: { reflex: "click->RestaurantsReflex#filter", room: session.id, filter: filter } %>
</th>
<% end %>
</tr>
</thead>
Create filter
Reflex method.. The sorting order is :normal
unless the user clicks again on the same column heading multiple times.
# app/reflexes/restaurants_reflex.rb
class RestaurantsReflex < StimulusReflex::Reflex
def filter
session[:filter_order] = filter_order
session[:filter] = element.dataset[:filter]
end
private
def filter_order
return :normal unless session[:filter] == element.dataset[:filter]
if session[:filter_order] != :reverse
:reverse
else
:normal
end
end
end
Now you should be able to click on table headers and have the list filter.
Setup encrypted cookies-based session management for ActionCable, as described in the StimulusReflex documentation for Security. This code is literally cut-and-pasted into app/controllers/application_controller.rb
and app/channels/application_cable/connection.rb
.
Let's add something fun... we'll fire off the confetti canon every time someone activates a Reflex.
yarn add dom-confetti
Create a stimulus controller.
// app/frontend/controllers/restaurants_controller.js
import { Controller } from 'stimulus'
import StimulusReflex from 'stimulus_reflex'
import { confetti } from 'dom-confetti'
export default class extends Controller {
connect () {
StimulusReflex.register(this)
}
afterReflex (anchorElement) {
confetti(anchorElement)
}
}
NOTE: If you did not follow my tailwind tutorial than this file will be at app/javascript/controllers/restaurants_controller.js
not app/frontend/controllers/restaurants_controller.js
Now clicking on the table headers should give a fun burst of confetti!
Run Standard
bundle exec standardrb --fix