Upgrade to Pro — share decks privately, control downloads, hide ads and more …

thoughtbot defaults with suspenders

thoughtbot defaults with suspenders

Dimiter Petrov

July 13, 2023
Tweet

More Decks by Dimiter Petrov

Other Decks in Programming

Transcript

  1. Suspenders is an opinionated platform for adding functionality to a

    Rails app. It encodes thoughtbot's opinions and decisions in tooling and best practices.
  2. Examples Project setup script Code linting and formatting with Standard

    Ruby Background jobs performed with Sidekiq N+1 query warnings with Bullet, with a custom configuration (and many more)
  3. In this talk Motivation for suspenders   Current status  

    Future plans   Implementation   Tests
  4. History & future Previous: clone from a repository Current: suspenders

    executable runs an app generator which augments Rails' Next: development dependency with no executable, run generators on demand
  5. Goal usage 1. rails new my-project && cd my-project 2.

    echo 'gem "suspenders", group: :development' >> Gemfile && bundle 3. rails g suspenders:ci 4. Repeat with other generators as needed.
  6. App generator module Suspenders class AppGenerator < Rails::Generators::AppGenerator # overrides

    and extensions end end # bin/suspenders Suspenders::AppGenerator.start
  7. def finish_template invoke :suspenders_customization super end def suspenders_customization invoke :customize_gemfile

    invoke :setup_development_environment invoke :setup_production_environment invoke :setup_secret_token invoke :configure_app invoke :copy_miscellaneous_files invoke :setup_database invoke :create_github_repo invoke :generate_default invoke :create_heroku_apps invoke :generate_deployment_default invoke :remove_config_comment_lines invoke :remove_routes_comment_lines invoke :run_database_migrations invoke :generate_views end
  8. module Suspenders class AppGenerator < Rails::Generators::AppGenerator def setup_development_environment say "Setting

    up the development environment" # ... build :raise_on_delivery_errors # ... end end end module Suspenders class AppBuilder < Rails::AppBuilder include Suspenders::Actions def raise_on_delivery_errors replace_in_file "config/environments/development.rb", "raise_delivery_errors = false", "raise_delivery_errors = true" end end end
  9. Work in progress: focused generators module Suspenders class CiGenerator <

    Generators::Base def simplecov_gem gem "simplecov", require: false, group: [:test] Bundler.with_unbundled_env { run "bundle install" } end def simplecov_test_integration prepend_template_to_file(test_helper_file, "partials/ci_simplecov.rb") end end end
  10. Testing new app generation RSpec.describe "Suspend a new project with

    default configuration", type: :feature do before(:all) do drop_dummy_database clear_tmp_directory run_suspenders setup_app_dependencies end it "uses custom Gemfile" it "ensures project specs pass" it "adds bin/setup file" it "makes bin/setup executable" it "includes the bundle:audit task" # ... end
  11. Example test of generated project it "configures production environment to

    enforce SSL" do production_config = read_project_file %w[config environments production.rb] expect(production_config).to match( /^ +config.force_ssl = true/ ) end def read_project_file(path) IO.read(File.join(project_path, *path)) end
  12. Testing a single generator RSpec.describe Suspenders::LintGenerator, type: :generator do describe

    "invoke" do it "bundles the standardrb gem" do with_fake_app do invoke! Suspenders::LintGenerator expect("Gemfile") .to match_contents(/gem .standard./) .and have_no_syntax_error end end end end
  13. Setup for single generator tests def with_fake_app EnvPath.with_prepended_env_path(fake_bundler_bin_path) do |path|

    FakeBundler.stub_unbundled_env!(self, path: path) execute = lambda do clear_tmp_directory create_fake_app_dir Dir.chdir(app_path) { yield } end if @no_silence execute.call else OutputStub.silence { execute.call } end end end
  14. Testing command output it "adds the standardrb rake tasks to

    rake" do with_fake_app do invoke! Suspenders::LintGenerator rake_output = `rake -T` expect(rake_output.lines.size).to eq 3 expect(rake_output.lines[0]).to start_with("rake standard") expect(rake_output.lines[1]).to start_with("rake standard:fix") expect(rake_output.lines[2]).to start_with("rake standard:fix_unsafely") end end
  15. Undoing a generator's work describe "revoke" do it "removes the

    standardrb gem from Gemfile" do with_fake_app do invoke_then_revoke! Suspenders::LintGenerator expect("Gemfile") .to not_match_contents(/gem .standard./) .and have_no_syntax_error end end it "removes the standardrb rake tasks from rake" do with_fake_app do invoke_then_revoke! Suspenders::LintGenerator expect(`rake -T`).to be_empty end end end