Jason Fleetwood-Boldt’s Rails Cookbook

Here is my unique Rails cookbook for bootstrapping any Rails project on Rails 7.

While most developers should pay attention to the Gems and setup you are adding to your codebases, these quick start recipies are great as a teaching tool and for setting demo apps up quickly.

1/ Rails 7 JS Strategy Setup (pick A, B, C or D)

Make sure to understand if you are building a Rails app using Importmap, JSBundling, Shakapacker, or Vite Rails. To help you decide, read this post.

These scripts use your current Ruby and Node versions (to switch, use a manager like RVM and NVM).

Be sure to switch to the desired Ruby and Node versions before copying & pasting below. You will be prompted for your new app name.

Option A: Make a new JSBundling Rails App (recommended for people getting started with Rails 7)

echo "please give your new JSBundling Rails app a TitleCase name:" && read APP_NAME && rails new $APP_NAME --javascript=esbuild --database=postgresql && cd $APP_NAME && CURRENT_NODE=$(nvm current) && echo $CURRENT_NODE >> .node-version && printf "Node + Ruby versions are in \`.node-version\` and \`.ruby-version\`, respectively.\n\n# Setup\n\n\`bin/setup\`\n\n# Start Rails\n\n\`bin/dev\`\n\n# Run Specs\n\nrun with \`bin/rake\`" > README.md && git add . && git commit -m "initial commit with $(rails -v), Node $CURRENT_NODE, Ruby $(more ./.ruby-version)" && sed -i '' -e 's/ruby-//g' .ruby-version && RUBY_STRING="ruby \"$(more ./.ruby-version)\"" && sed -i '' -e "s/$RUBY_STRING/ruby File.read('.ruby-version').strip/g" Gemfile && git add . && git commit -m "fixes .ruby-version file and sets Gemfile to use .ruby-version file" && bin/dev rails db:create db:migrate && git add . && git commit -m "adds schema file"

For the complete JS Bundling setup guide, see this post.

Be sure to start up your app with ./bin/dev

Typescript install for JSBundling app (optional)

First, confirm that you have correctly installed the tsc executable on your system. Assuming you are using Homebrew on macOS, type which tsc to confirm that you see this as the output:

/opt/homebrew/bin/tsc

If you don’t see this, run

brew install typescript

Next, install Typescript, the watcher, and modify the Procfile with this quick script:

yarn add --dev tsc-watch && yarn add typescript && git add . && git commit -m "adds typescript and tsc-watch as node packages" && tsc --init && sed -i '' -e "s/\/\/ \"noImplicitAny\": true,/\"noImplicitAny\": true,   /g" tsconfig.json && git add . && git commit -m "initial tsconfig.json file with noImplicitAny" && sed -i '' -e "s/\"scripts\": {/\"scripts\": {\n    \"failure:js\": \"rm .\/app\/assets\/builds\/application.js \&\& rm .\/app\/assets\/builds\/application.js.map\", \n    \"compile:typescript\": \"tsc-watch --noClear -p tsconfig.json --onSuccess 'yarn build' --onFailure 'yarn failure:js'\", /g" package.json && sed -i '' -e "s/js: yarn build --watch/js: yarn compile:typescript --watch/g" Procfile.dev && touch app/javascript/empty.ts && git add . && git commit -m "adds typescript support"

Option B: Make a new Rails app using ImportMap β€” No Node.

Use this setup for a Node-free Rails 7 app.

echo "please give your new Importmap Rails app a TitleCase name:" && read APP_NAME && rails new $APP_NAME --database=postgresql && cd $APP_NAME && git add . && git commit -m "initial commit with $(rails -v), Ruby $(more ./.ruby-version)" && printf "Ruby version is in \`.ruby-version\`.\n\n# Setup\n\n\`bin/setup\`\n\n# Start Rails\n\n\`bin/rails s\`\n\n# Run Specs\n\nrun with \`bin/rake\`" > README.md  && sed -i '' -e 's/ruby-//g' .ruby-version && RUBY_STRING="ruby \"$(more ./.ruby-version)\"" && sed -i '' -e "s/$RUBY_STRING/ruby File.read('.ruby-version').strip/g" Gemfile  && git add . && git commit -m "fixes .ruby-version file and set Gemfile to use .ruby-version file" && ./bin/setup && git add . && git commit -m "adds schema file"

Start up your app with rails server

For more information about Importmap, see this post.

Option C: Shakapacker

echo "please give your new Shakapcker Rails app a TitleCase name:" && read APP_NAME && rails new $APP_NAME --database=postgresql --skip-javascript && cd $APP_NAME && git add . && git commit -m "initial commit with $(rails -v), Ruby $(more ./.ruby-version)" && printf "Ruby version is in \`.ruby-version\`.\n\n# Setup\n\n\`bin/setup\`\n\n# Start Rails\n\n\bin/rails s\\n\n# Run Specs\n\nrun with \bin/rake\" > README.md  && sed -i '' -e 's/ruby-//g' .ruby-version && RUBY_STRING="ruby \"$(more ./.ruby-version)\"" && sed -i '' -e "s/$RUBY_STRING/ruby File.read('.ruby-version').strip/g" Gemfile  && git add . && git commit -m "fixes .ruby-version file and set Gemfile to use .ruby-version file" && bundle exec rails db:create db:migrate && git add . && git commit -m "adds schema file"  && bundle add react_on_rails shakapacker && bundle install && git add . && git commit -m "Adds shakapacker and react_on_rails to gemfile" && bundle exec rails webpacker:install && git add . && git commit -m "webpacker install" && rails generate react_on_rails:install && git add . && git commit -m "react_on_rails install" && rm app/controllers/hello_world_controller.rb && rm app/views/layouts/hello_world.html.erb && rm -rf app/views/hello_world/ && sed -i '' -e "s/get \'hello_world\', to: \'hello_world#index\'//g" config/routes.rb && git add . && git commit -m "removing shakapacker hello world" && rails generate controller Welcome &&
sed -i '' -e 's/class WelcomeController < ApplicationController/class WelcomeController < ApplicationController\n  def index\n\n  end/g' app/controllers/welcome_controller.rb &&
printf "Hello Shakapacker" > app/views/welcome/index.html.erb &&
sed -i '' -e  's/# root "articles#index"//g' config/routes.rb &&
sed -i '' -e  's/Rails.application.routes.draw do/Rails.application.routes.draw do\n  root to: "welcome#index"/g' config/routes.rb && git add . && git commit -m "generates Welcome controller" && mv app/javascript/application.js app/javascript/packs/application.js  && echo "\nimport './hello-world-bundle'"  >> app/javascript/packs/application.js && echo '\n\n<%= react_component("HelloWorld", props: {name: '`$APP_NAME`'}, prerender: false) %>' >> app/views/welcome/index.html.erb && git add . && git commit -m "adding the HelloWorld react component to our Welcome index"

For more about Shakapacker, see this blog post

Option D: Vite Rails with React

echo "please give your new Vite Rails app a TitleCase name:" && read APP_NAME  && rails new $APP_NAME --skip-javascript --database=postgresql  && cd $APP_NAME && git checkout -b main && CURRENT_NODE=$(nvm current) && printf $CURRENT_NODE >> .node-version && printf "Node + Ruby versions are in \`.node-version\` and \`.ruby-version\`, respectively.\n\n# Setup\n\n\`bin/setup\`\n\n# Start Rails\n\n\`bin/vite dev\` + \`bin/rails s\` in two separate windows\nor run \`bin/dev\` in single window and access site at http://127.0.0.1:5100\n\n# Run Specs\n\nrun with \`bin/rake\`" > README.md && git add . && git commit -m "initial commit with $(rails -v), Node $CURRENT_NODE, Ruby $(more ./.ruby-version)" &&  ./bin/setup  && sed -i '' -e 's/ruby-//g' .ruby-version && RUBY_STRING="ruby \"$(more ./.ruby-version)\"" && sed -i '' -e "s/$RUBY_STRING/ruby File.read('.ruby-version').strip/g" Gemfile  && git add . && git commit -m "fixes .ruby-version file and sets Gemfile to use .ruby-version file" && npm init -y && git add . && git commit -m "initalizes npm" && bundle add vite_rails && bundle install && git add . && git commit -m "adds vite-rails gem" && bundle exec vite install && git add . && git commit -m "vite rails default setup" && yarn add react react-dom && git add . && git commit -m "yarn add react react-dom" && rails generate controller Welcome && sed -i '' -e 's/class WelcomeController < ApplicationController/class WelcomeController < ApplicationController\n  def index\n\n  end/g' app/controllers/welcome_controller.rb &&
printf "<div id=\"${APP_NAME}App\"></div>" > app/views/welcome/index.html.erb &&
sed -i '' -e  's/# root "articles#index"//g' config/routes.rb &&
sed -i '' -e  's/Rails.application.routes.draw do/Rails.application.routes.draw do\n  root to: "welcome#index"/g' config/routes.rb &&  git add . && git commit -m "generates Welcome controller" && printf "import App from '~/components/App'\nimport React from 'react'\nimport { createRoot } from 'react-dom/client'\nconst root = createRoot(document.getElementById('${APP_NAME}App'))\nroot.render(React.createElement(App))" >> app/frontend/entrypoints/application.js && mkdir app/frontend/components  && printf "import React from 'react';\nexport default function App () {\n  return (<h1>Hello Vite Rails</h1>)\n}" >> app/frontend/components/App.jsx && git add . && git commit -m "basic shell for React app" && printf '#!/usr/bin/env sh\n\nif ! gem list foreman -i --silent; then\n  echo "Installing foreman..."\n  gem install foreman\nfi\n\nexec foreman start -f Procfile.dev "$@"' > bin/dev && chmod 0755 bin/dev && git add . && git commit -m "adding bin/dev option to start both vite + rails in one terminal window using Foreman"

You will find your new React app root component (the root of your React app) in app/frontend/components/App.jsx. Look at the bottom of the file app/frontend/entrypoints/application.js to see how it gets mounted into the Welcome controller’s index view using a simple DOM ID.

You can use two windows to start Vite + Rails according to the Vite instructions(bin/vite dev + bin/rails s) and access your site at the normal http://localhost:3000/. Alternatively, you can avoid two-terminal windows by running bin/dev (which runs Foreman). Under this setup, your app is accessible at port 5100 (http://localhost:5100)

When you go to the home page, you should see this in your browser:

Important: Vite server boots by default only at http://localhost:3036/vite-dev/ (not 127.0.0.1). Because the Rails server passes its own domain onto where it looks for the the Vite server (at a different port), you must then also develop locally at localhost, or when accessing Rails, at http://localhost:3000.

If you try to access your site at 127.0.0.1, you will see errors like this:

GET http://127.0.0.1:3036/vite-dev/ net::ERR_CONNECTION_REFUSED

Prefer Typescript over JS? If so, run this additional script, which will switch your existing JSX setup to Typescript.

sed -i '' -e 's/vite_javascript_tag/vite_typescript_tag/g' app/views/layouts/application.html.erb && mv app/frontend/components/App.jsx app/frontend/components/App.tsx && mv app/frontend/entrypoints/application.js app/frontend/entrypoints/application.ts && sed -i '' -e "s/import App from '~\/components\/App'/import App from '..\/components\/App'/g" app/frontend/entrypoints/application.ts && git add . && git commit -m "switches to typescript" 

2/ Testing Setup (Always Installβ€” Pick A or B)

Choose your testing paradigm: MInitest or Rspec. Minitest is for you if you like the simplicity Ruby’s raw structures. Rspec is a language DSL that you must learn some of to write your specs. Minitest is older and more elite and for people who prefer Rubyish syntax.

Rspec is far and away the most popular choice in the Rails world, but it adds a language to learn, which requires a little more initial work to get comfortable with it, but feels very natural for many.

With either choice (Minitest or Rspec), these scripts add Dotenv, FactoryBot, FFaker, VCR, and SimpleCov. These are excellent defaults for beginners just getting started with TDD.

Adds to and modifies .gitignore for DotEnv and configures coverage reports with SimpeCov, setting them up to print out automatically after every test (see coverage/ β€” a folder not checked into your repo).

Remember to install one option (Option 2A for Minitest or Option 2B for Rspec) β€” not both. If you pick Rspec (3B), you can also choose Capybara, Option 2C (recommended).

If you don’t know what to choose, I recommend Rspec.

Option 2A: Minitest + Friends

bundle add minitest-rails minitest-spec-rails factory_bot_rails ffaker vcr simplecov dotenv-rails --group "development, test" && bundle add simplecov-rcov launchy --group "test" && git add . && git commit -m "adds minitest, factory_bot_rails, ffaker, VCR, simplecov, dotenv-rails" && printf "\n.env\n.env.local\n.env.*.local\n\ncoverage/" >> .gitignore && printf "" >> .env.local && git add . && git commit -m "adds .env, etc and coverage/ to .gitignore file"  && sed -i '' -e "s/require \"rails\/test_help\"/require \"rails\/test_help\"\nrequire 'simplecov'\nrequire 'simplecov-rcov'\nclass SimpleCov::Formatter::MergedFormatter\n  def format(result)\n    SimpleCov::Formatter::HTMLFormatter.new.format(result)\n    SimpleCov::Formatter::RcovFormatter.new.format(result)\n  end\nend\nSimpleCov.formatter = SimpleCov::Formatter::MergedFormatter\nSimpleCov.start 'rails' do\n  add_filter \"\/vendor\"\nend\n\n/g" test/test_helper.rb && 
git add . && git commit -m "configuring simplecov"

Capybara for Minitest

printf "require 'minitest/autorun'\nrequire 'application_system_test_case'\n\n\nclass HomepageTest < Minitest::Test \n  test 'can load' do\n    visit '/'\n    assert(page.has_content?('Hello World'), 'page missing Hello World')\n  end\nend" >  test/system/homepage_test.rb && printf "Capybara.register_driver :selenium do |app|\n  options = Selenium::WebDriver::Chrome::Options.new(\n    # It's the headlese arg that make Chrome headless\n    # + you also need the disable-gpu arg due to a bug\n    args: ['headless', 'disable-gpu window-size=1366,1200'],\n    )\n\n  Capybara::Selenium::Driver.new(\n    app,\n    browser: :chrome,\n    options: options\n  )\nend\n\nCapybara.default_driver = :selenium" >> test/test_helper.rb && git add . && git commit -m "basic capybara example"

Option 2B: Rspec + Friends

bundle add rspec-rails rspec-wait factory_bot_rails ffaker vcr simplecov dotenv-rails webmock --group "development, test" && bundle add simplecov-rcov launchy --group "test" &&  
rails generate rspec:install && 
git add . && git commit -m "adds rspec, factory bot, ffaker, vcr, simplecov, and launchy" && printf "\n.env\n.env.local\n.env.*.local\n\ncoverage/" >> .gitignore && printf "" >> .env.local && git add . && git commit -m "adds .env, etc and coverage/ to .gitignore file" && sed -i '' -e 's/RSpec.configure do |config|/RSpec.configure do |config|\n  config.include FactoryBot::Syntax::Methods\n/g' spec/rails_helper.rb && sed -i '' -e "s/RSpec.configure do |config|/require 'simplecov'\nrequire 'simplecov-rcov'\nclass SimpleCov::Formatter::MergedFormatter\n  def format(result)\n    SimpleCov::Formatter::HTMLFormatter.new.format(result)\n    SimpleCov::Formatter::RcovFormatter.new.format(result)\n  end\nend\nSimpleCov.formatter = SimpleCov::Formatter::MergedFormatter\nSimpleCov.start 'rails' do\n  add_filter \"\/vendor\"\nend\n\nVCR.configure do |config|\n  config.cassette_library_dir = \"spec\/fixtures\/vcr_cassettes\"\n  config.hook_into :webmock\n  config.ignore_request do |request|\n    [\"127.0.0.1\", \"chromedriver.storage.googleapis.com\" ,  \"googlechromelabs.github.io\", \"edgedl.me.gvt1.com\"].include? URI(request.uri).host\n  end\nend\n\n\nRSpec.configure do |config|/g" spec/rails_helper.rb  &&  git add . && git commit -m "adding factorybot and simplecov to Rspec config" && sed -i '' -e 's/< Rails::Application/< Rails::Application\n	config.generators do |generate|\n      generate.helper false\n\n      generate.assets false\n      generate.helper false\n      generate.stylesheets false\n      generate.test_framework :rspec,\n                              request_specs: false,\n                              view_specs: false,\n                              controller_specs: false,\n                              helper_specs: false,\n                              routing_specs: false,\n                              fixture: false,\n                              fixture_replacement: "factory_bot"\n    end\n/g' config/application.rb && git add . && git commit -m "disables extraneous generators"

Capybara for Rspec

mkdir spec/features  && printf "require 'rails_helper'\n\ndescribe 'homepage' do\n  it 'can load' do\n    visit '/'\n    expect(page).to have_content('Hello World')\n  end\nend" >>  spec/features/homepage_spec.rb && printf "Capybara.register_driver :selenium do |app|\n  options = Selenium::WebDriver::Chrome::Options.new(\n    # It's the headlese arg that make Chrome headless\n    # + you also need the disable-gpu arg due to a bug\n    args: ['headless', 'disable-gpu window-size=1366,1200'],\n    )\n\n  Capybara::Selenium::Driver.new(\n    app,\n    browser: :chrome,\n    options: options\n  )\nend\n\nCapybara.default_driver = :selenium" >> spec/rails_helper.rb && git add . && git commit -m "basic capybara example"

To get the Hello World working (to make the spec pass), see section #4.

July 2023: There is currently an issue with the latest version of chromedriver; if you see this:

Webdrivers::VersionError:
       Unable to find latest point release version for 115.0.5790. You appear to be using a non-production version of Chrome. Please set `Webdrivers::Chromedriver.required_version = <desired driver version>` to a known chromedriver version: https://chromedriver.storage.googleapis.com/index.html

To fix this, remove the webdrivers gem from your Gemfile.

bundle remove webdrivers

The other (newer) gem selenium-webdrivers is the only gem you need and you can remove this old gem to eliminate the problem. This affects any Rails app that was created before Rails 7.0.7. (Note that upgrading Rails does not fix the problem because upgrading Rails does not remove gems by default.)

2C: Github Actions CI Setup β€” Adds a .github/workflows/test_suite.yml file for a Rails app with Node and Postgres

You can use this script after 2A or 2B to set up your app for running on Github Actions as your CI runner. Use this for a JSBundling, Vite, or Shakapacker app only.


mkdir .github && mkdir .github/workflows && printf '# This workflow uses actions that are not certified by GitHub.  They are\n# provided by a third-party and are governed by separate terms of service,\n# privacy policy, and support documentation.\n#\n# This workflow will install a prebuilt Ruby version, install dependencies, and\n# run tests and linters.\nname: "Test Suite"\non:\n  push:\n    branches: [ "main" ]\n  pull_request:\n    branches: [ "main" ]\njobs:\n  test:\n    runs-on: ubuntu-latest\n    services:\n      postgres:\n        image: postgres:11-alpine\n        ports:\n          - "5432:5432"\n        env:\n          POSTGRES_DB: rails_test\n          POSTGRES_USER: rails\n          POSTGRES_PASSWORD: password\n      chrome:\n        image: selenium/standalone-chrome:latest\n        ports:\n          - 4444:4444\n    env:\n      RAILS_ENV: test\n      DATABASE_URL: "postgres://rails:password@localhost:5432/rails_test"\n\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v3\n      - name: Install Ruby and gems\n        uses: ruby/setup-ruby@55283cc23133118229fd3f97f9336ee23a179fcf # v1.146.0\n        with:\n          bundler-cache: true\n\n      - name: Setup Node\n        uses: actions/setup-node@v2\n        with:\n          node-version: 18\n\n      - name: npm install\n        run: npm install\n        \n      - name: Set up database schema\n        run: bin/rails db:schema:load\n      - name: Run tests\n        run: bin/rake\n\n' > .github/workflows/test_suite.yml && bundle lock --add-platform x86_64-linux && git add . && git commit -m "github actions CI setup" && bin/rails db:migrate && git add db/schema.rb && git commit -m "db/schema.rb file"

If you are using Rubocop, add this lint job: (see Section 3B)

sed -i '' -e 's/jobs:/jobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v3\n      - uses: ruby\/setup-ruby@55283cc23133118229fd3f97f9336ee23a179fcf # v1.146.0\n    \n\n      - run: bundle install\n      - name: Rubocop\n        run: rubocop/g' .github/workflows/test_suite.yml && git add . && git commit -m "adds rubocop lint job to Github Actions"

2D: Circle CI (Rspec)

TODO: finish me

β€’ bundle add rspec_junit_formatter

3/ Generate a Default Welcome Controller

Use this for quick demo apps that need a “home page.” All you get here is a root route pointing to Welcome#index, a corresponding WelcomeController with an index action, and a view at app/views/welcome/index.html.erb. This can be considered your “home page” for your new app.

You can mix this with a default setup from Section 1 if you have chosen JSBundling (1A) or Importmap (1B). Shakapacker (1C) and Vite Rails (1D) have their own hello world page (created in React) which is already included.

rails generate controller Welcome &&
sed -i '' -e 's/class WelcomeController < ApplicationController/class WelcomeController < ApplicationController\n  def index\n\n  end/g' app/controllers/welcome_controller.rb &&
printf "Hello World" > app/views/welcome/index.html.erb &&
sed -i '' -e  's/# root "articles#index"//g' config/routes.rb && 
sed -i '' -e  's/Rails.application.routes.draw do/Rails.application.routes.draw do\n  root to: "welcome#index"/g' config/routes.rb && git add . && git commit -m "generates Welcome controller"

4/ Generator Settings, Lint, Typecheck

4A/ Disables generators (Rspec)

Disables Rails from generating auxiliary files β€” empty helpers, assets, and stylesheets. Also disables all but model specs from being generated automatically by Rspec. I don’t like having the empty files around, which happens as you use the generators and don’t actually add anything to the empty files it creates. I realize that the empty files are supposed to remind you to use them, but I prefer to have them not created at all.

sed -i '' -e "s/class Application < Rails::Application/class Application < Rails::Application\n   config.generators do |generate|\n      generate.helper false\n\n      generate.assets false\n      generate.stylesheets false\n      generate.test_framework :rspec,\n                              request_specs: false,\n                              view_specs: false,\n                              controller_specs: false,\n                              helper_specs: false,\n                              routing_specs: false,\n                              fixture: false,\n                              fixture_replacement: 'factory_bot'\n    end\n/g" config/application.rb && git add . && git commit -m "disables auxillary files in generators, disables most rspec generators"

4B/ Add Rubocop (Linting)

bundle add rubocop && printf "AllCops:\n NewCops: enable\n SuggestExtensions: false\n\nStyle/Documentation:\n Enabled: false\n\nStyle/FetchEnvVar:\n Enabled: false\n\nMetrics/MethodLength:\n Enabled: false\n\nLayout/LineLength:\n Max: 120\n\nMetrics/BlockLength:\n Enabled: false\n\n\nLint/EmptyBlock:\n  Enabled: false\n\n" >> .rubocop.yml && printf '#!/usr/bin/env ruby\n# frozen_string_literal: true\n\n#\n# This file was generated by Bundler.\n#\n# The application 'rubocop' is installed as part of a gem, and\n# this file is here to facilitate running it.\n#\n\nrequire "pathname"\nENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",\n  Pathname.new(__FILE__).realpath)\n\nbundle_binstub = File.expand_path("../bundle", __FILE__)\n\nif File.file?(bundle_binstub)\n  if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/\n    load(bundle_binstub)\n  else\n    abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.\nReplace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")\n  end\nend\n\nrequire "rubygems"\nrequire "bundler/setup"\n\nload Gem.bin_path("rubocop", "rubocop")' >> bin/rubocop && chmod 0755 bin/rubocop && git add . && git commit -m "adds rubocop" && (bin/rubocop -A) || git add . && git commit -m "rubocop autocorrections" 

Run rubocop with bin/rubocop -A

Because the default Rails code produces code that does not lint correctly with Rubocop, you’ll need to fix the Rubocop errors, check in the fixes, and re-run bin/rubocop -A until you see it lint cleanly. For details, see this post.

When bin/rubocop -A runs with only green dots, it will say “no offenses detected” like so:

4C/ Sorbet (Ruby Type checking)

bundle add sorbet-static-and-runtime && bundle add tapioca --group development &&  bundle exec srb typecheck -e 'puts "Hello, world!"' && bundle exec ruby -e 'puts(require "sorbet-runtime")' && bundle exec tapioca init && git add . && git commit -m "adds sorbet"

Now run srb tc to run the type checker.

5/ Debugging Tools

Adds Byebug, Bullet, Active Record Query Trace, and Ruby Critic as default debugging tools.

Byebug can be your go-to debugger and you can drop into a debugger anywhere by putting the statement byebug on its own line of code. (Tip: try not to put this at the end of a block or method.)

Bullet is used to detect and alert you any N+1 queries you have made by accident. It is always enabled (look for it in your Rails logs).

Active Record Query Trace allows you to examine each and every database query and see exactly which line of code it comes from β€” yours or lines of code in your gem code. This default setup does not enable it, but a config file is added for you. To use it, enable set ActiveRecordQueryTrace.enabled to true in config/initializers/active_record_query_trace.rb (then restart your Rails server and look for the trace output in your Rails logs). To get to understand the tool, try to adjust the setting for ActiveRecordQueryTrace.lines β€” this determines how long (many lines) the backtrace is for each traced database query. You use this tool to identify where in your code a specific database query is being invoked.

Finally, Ruby Critic is a tool for measuring your code’s cyclomatic complexity. You run it using bin/rubycritic .

bundle add bullet active_record_query_trace byebug --group "development, test" && sed -i '' -e "s/Rails.application.configure do/Rails.application.configure do\n  config.after_initialize do\n    Bullet.enable        = true\n    Bullet.alert         = false\n    Bullet.bullet_logger = true\n    Bullet.console       = true\n    Bullet.rails_logger  = true\n    Bullet.add_footer    = true\n  end\n/g" config/environments/development.rb && printf "if Rails.env.development?\n  ActiveRecordQueryTrace.enabled = false\n\n  ActiveRecordQueryTrace.level = :full # :app, :rails, or :full\n  ActiveRecordQueryTrace.lines = 10\n  \nend" >> config/initializers/active_record_query_trace.rb && 
git add . && git commit -m "adding bullet, active_record_query_trace" && bundle add rubycritic --group dev && bundle install && printf "bundle exec rubycritic" > bin/rubycritic && chmod 0755 bin/rubycritic && git add  . && git commit -m "adds rubycritic; run with bin/rubycritic"

6/ Hot Glue (pick A or B)

β€’ Be sure to run Rspec install first (see section 3).

Option A: Add Hot Glue With Bootstrap

bundle add hot-glue && git add . && git commit -m "adds hot-glue" && 
rails generate hot_glue:install --layout=bootstrap && git add . && git commit -m "hot glue setup (bootstrap)"

Option B: Add Hot Glue with Tailwind (experimental)

bundle add hot-glue && git add . && git commit -m "adds hot-glue" && 
rails generate hot_glue:install --layout=tailwind && git add . && git commit -m "hot glue setup (bootstrap)"

7/ CSS Options (pick one A or B)

If you go through the steps above, you will have a JSBundlnig app without CSSBundling. Let’s add cssbundling-rails with either Bootstrap (A) or Tailwind (B).

Option 7A: Bootstrap + SASSC-Rails

bundle add cssbundling-rails && rails css:install:bootstrap && sed -i '' -e 's/Rails.application.configure do/Rails.application.configure do\n  config.sass.inline_source_maps = true/g' config/environments/development.rb && git add . && git commit -m "adds cssbundling-rails with bootstrap" && sed -i '' -e 's/# gem "sassc-rails"/gem "sassc-rails"/g' Gemfile && bundle install && git add . && git commit -m "adds sassc-rails" 

Add Bootstrap Navbar:

bundle exec rails runner "puts Rails.application.class.module_parent_name" > $APP_NAME && sed -i '' -e 's/<body>/<body>\n<nav class="navbar navbar-expand-lg navbar-light bg-light">\n  <div class="container-fluid">\n    <a class="navbar-brand" href="#">'$APP_NAME'<\/a>\n    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">\n      <span class="navbar-toggler-icon"><\/span>\n    <\/button>\n    <div class="collapse navbar-collapse" id="navbarSupportedContent">\n      <ul class="navbar-nav me-auto mb-2 mb-lg-0">\n        <li class="nav-item">\n          <a class="nav-link active" aria-current="page" href="#">Home<\/a>\n        <\/li>\n        <li class="nav-item">\n          <a class="nav-link" href="#">Link<\/a>\n        <\/li>\n        <li class="nav-item dropdown">\n          <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">\n            Dropdown\n          <\/a>\n          <ul class="dropdown-menu" aria-labelledby="navbarDropdown">\n            <li><a class="dropdown-item" href="#">Action<\/a><\/li>\n            <li><a class="dropdown-item" href="#">Another action<\/a><\/li>\n            <li><hr class="dropdown-divider"><\/li>\n            <li><a class="dropdown-item" href="#">Something else here<\/a><\/li>\n          <\/ul>\n        <\/li>\n        <li class="nav-item">\n          <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled<\/a>\n        <\/li>\n      <\/ul>\n      <form class="d-flex">\n        <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">\n        <button class="btn btn-outline-success" type="submit">Search<\/button>\n      <\/form>\n    <\/div>\n  <\/div>\n<\/nav>/g' app/views/layouts/application.html.erb && git add . && git commit -m "adds bootstrap navbar"

Be sure to confirm that the drop-down menu works in the navbar. It will look like this when you click on (or tap for mobile) the menu “Dropdown”.

If the drop-down menu does not show up, you have not installed the Javascript correctly. Try to fix by taking these steps manually.

Also check the mobile view, which shows a hamburger menu like so:

QUICK TROUBLESHOOTING STEPS FOR BOOTSTRAP ON RAILS 7

  • If you have a JSBundlnig, Vite Rails, or Shakapacker app, be sure you are starting it correctly using Foreman (bin/dev. If you boot Rails only the old way (rails server or rails s), your CSS compiler isn’t recompiling your assets.
  • run yarn add @popperjs/core bootstrap bootstrap-icons sass and confirm that these are added as dependencies in package.json
  • confirm that the Procfile.dev contains css: yarn watch:css
  • confirm that the package.json file contains a scripts entry for watch:css and build:css
  • confirm there is a file at app/assets/sytlesheets/application.bootstrap.css that contains:
    @import 'bootstrap/scss/bootstrap';
    @import 'bootstrap-icons/font/bootstrap-icons';
  • confirm that app/javascript/application.js contains
    import * as bootstrap from "bootstrap"

Option B: Tailwind

bundle add cssbundling-rails &&
./bin/rails css:install:tailwind &&
git add . && git commit -m "adds cssbundling-rails with tailwind" &&
rails generate controller Articles &&
printf '<div class="bg-blue-900 text-center py-4 lg:px-4"><div class="p-2 bg-blue-800 items-center text-blue-100 leading-none lg:rounded-full flex lg:inline-flex" role="alert"><span class="flex rounded-full bg-blue-500 uppercase font-bold px-2 py-1 text-xs mr-3">New</span><span class="font-semibold mr-2 text-left flex-auto">Hello Tailwind</span></div></div>' >> app/views/articles/index.html.erb
sed -i '' -e 's/# root "articles#index"/ root "articles#index"/g' config/routes.rb 
git add . && git commit -m "adds tailwind element" 

8/ Kaminari for Pagination

bundle add kaminari && 
rails generate kaminari:views bootstrap4 && 
git add . && git commit -m "adding kaminari and views"

9/ Devise Setup For Rails 7

Devise 4.9.0 and above now works with Rails 7 with no issues.

bundle add devise && bundle install && 
git add . && git commit -m "adding devise gem" && 
rails generate devise:install && 
git add . && git commit -m 'devise install' && 
sed -i '' -e  's/Rails.application.configure do/Rails.application.configure do\n  config.action_mailer.default_url_options = { host: "localhost", port: 3000 }/g' config/environments/development.rb && 
git add . && git commit -m 'devise setup'

To make an authenticated user. This also adds a quick Logout button in the main layout app/views/layouts/application.html.erb, but you can move that anywhere (like your nav bar).

rails generate devise User name:string && rails db:migrate && git add . && git commit -m "generating devise user" && sed -i '' -e 's/<body>/<body><\% if user_signed_in? %>\n   <div>  Logged in as <%= current_user.email %>\\n      <%= button_to "Logout", destroy_user_session_path, method: :delete %><hr \/><\/div>\n  <% elsif ! controller.class.to_s.include?("Devise")%>\n    <%= link_to "Login", new_user_session_path %> | <%= link_to "Signup", new_user_registration_path %><hr \/><\/div>\n  <% end %>/g' app/views/layouts/application.html.erb

10/ Image Processing with Amazon S3 + Dropzone

if ( command -v vips &> /dev/null ); then; printf "vips already installed...";  else; echo "installing vips with brew..."; brew install vips; fi && bundle add image_processing aws-sdk-s3 && ./bin/rails active_storage:install && ./bin/rails db:migrate && git add . && git commit -m "installing active storage" && sed -i '' -e 's/config.active_storage.service = :local/config.active_storage.service = :amazon/g' config/environments/development.rb && sed -i '' -e 's/config.active_storage.service = :local/config.active_storage.service = :amazon/g' config/environments/production.rb  && yarn add dropzone && yarn add @rails/activestorage &&
sed -i '' -e 's/# amazon:/amazon:/g' config/storage.yml  && sed -i '' -e 's/#   service: S3/  service: S3/g' config/storage.yml &&
sed -i '' -e 's/#   access_key_id: \<%= Rails.application.credentials.dig(:aws, :access_key_id) %\>/  access_key_id: \<%= Rails.application.credentials.dig(:aws, :access_key_id) %\>/g' config/storage.yml && 
sed -i '' -e 's/#   secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>/  secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>/g' config/storage.yml &&
sed -i '' -e 's/#   region: us-east-1/  region: us-east-1/g' config/storage.yml && git add . && git commit -m "adds image processing, amazon s3, configures for S3" && echo "REMEMBER TO 1) Set your bucket name is config/storage.yml and 2) set your access_key_id and secret_access_key in Rails credentials and 3) Configure CORS on the bucket"

Also, add this to your github actions

      - name: Install libvips library ubuntu
        run: |
          sudo apt-get update
          sudo apt-get install -y libvips

11/ 12-Factor, Nonschema Migrations (for Heroku/ Fly.io/any other 12-Factor provider)

Note that 12-Factor apps have special requirements (like not writing to the disk). Read more about 12-factor apps here. The most important parts of building a 12 Factor app are:

  • Dev-prod parity (you run things the same way in dev and prod)
  • Nothing can be written to the localfile system, so you must use an external service for things like images
  • The logs themselves should not be written to the filesystem. Instead, they’ll get piped out to an external service.

For all apps that you intend to deploy to a cloud environment. Not needed for demo apps or teaching apps that you will only run locally. Here, we add:

  • a Procfile that defines for Heroku the types of processes your app will run
  • a release-tasks.sh file that tells Heroku what to do on releasing
  • my gem Nondestructive Migrations

(Note that the old “12Factor Gem” is no longer needed for new Rails apps, so it is not installed anymore even though this step is called the “12 Factor” step in this guide.)

printf "web: bundle exec rails server -p \$PORT\nrelease: ./release-tasks.sh" >> Procfile && printf "# Step to execute\nbundle exec rails db:migrate data:migrate\n# check for a good exit\nif [ $? -ne 0 ]\nthen\n  puts '*** RELEASE COMMAND FAILED'\n  # something went wrong; convey that and exit\n  exit 1\nfi" >> ./release-tasks.sh && chmod 0755 release-tasks.sh && sed -i '' -e 's/# config.force_ssl = true/config.force_ssl = true/g' config/environments/production.rb && bundle install && bundle lock --add-platform x86_64-linux && git add . && git commit -m "setup for Heroku" && bundle add nonschema_migrations  && rails generate data_migrations:install && rails db:migrate && git add . && git commit -m "adds nonschema_migrations"

12/ Sidekiq & Sidekiq Scheduler

For all apps that will require jobs or scheduled jobs.

β€’ Be sure to run the 12-Factor (Heroku) setup above first.

bundle add sidekiq sidekiq-scheduler && printf "require 'sidekiq/web'\n" >> config/initializers/sidekiq.rb && printf "\nworker: bundle exec sidekiq" >> Procfile && printf "\nworker: bundle exec sidekiq" >> Procfile.dev && sed -i '' -e 's/Rails.application.routes.draw do/Rails.application.routes.draw do\n  mount Sidekiq::Web => "\/sidekiq" /g' config/routes.rb && printf "#:scheduler:\n# add your scheduled jobs here and uncomment line above" >> config/sidekiq.yml && git add . && git commit -m "adds sidekiq with configuration"

13/ Pundit and Roles

Add Pundit

bundle add pundit && sed -i '' -e 's/ApplicationController < ActionController::Base/ApplicationController < ActionController::Base\n  include Pundit::Authorization/g' app/controllers/application_controller.rb && bundle install && rails g pundit:install && git add . && git commit -m "pundit gem"

Use an array column to store roles

https://stackoverflow.com/questions/32409820/add-an-array-column-in-rails

14/ Stripe

TODO: WIP

bundle add stripe-rails

Add to config/environments/production.rb,

config.stripe.publishable_key = 'pk_live_XXXYYYZZZ'

Add to config/environments/development.rb

Add

config.stripe.secret_key = Rails