rails new stimulus_reflex_table_filter --skip-coffee --webpack=stimulus -d postgresql
cd stimulus_reflex_table_filter
rails db:creategit 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 masterAdd stimulus_reflex, annotate, faker, and standard to Gemfile. If you want to deploy the app to heroku, also add the redis gem.
bundleAdd stimulus_reflex to our package.json
yarn add stimulus_reflexAdd Tailwind
yarn add tailwindcss --dev
./node_modules/.bin/tailwind initSetup 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 indexThese commands should have generated:
db/migrate/20191013003319_create_restaurants.rbapp/models/restaurant.rbtest/models/restaurant_test.rbtest/fixtures/restaurants.ymlapp/controllers/restaurants_controller.rbapp/views/restaurantsapp/views/restaurants/index.html.erbtest/controllers/restaurants_controller_test.rbapp/helpers/restaurants_helper.rbapp/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
endLets 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,
)
endOk 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
endGo to config/routes.rb and add root "restaurants#index.
Your routes file should now look like:
Rails.application.routes.draw do
root "restaurants#index"
endUpdate 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
endUpdate 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
endNow 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