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. thoughtbot defaults with suspenders
    Dimiter Petrov

    View Slide

  2. View Slide

  3. Suspenders

    View Slide

  4. Suspenders is an opinionated platform for adding functionality to a Rails app.
    It encodes thoughtbot's opinions and decisions in tooling and best practices.

    View Slide

  5. 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)

    View Slide

  6. View Slide

  7. In this talk
    Motivation for suspenders
      Current status
      Future plans
      Implementation
      Tests

    View Slide

  8. 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

    View Slide

  9. Current usage
    1. gem install suspenders
    2. suspenders my-project

    View Slide

  10. 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.

    View Slide

  11. Implementation

    View Slide

  12. App generator
    module Suspenders
    class AppGenerator < Rails::Generators::AppGenerator
    # overrides and extensions
    end
    end
    # bin/suspenders
    Suspenders::AppGenerator.start

    View Slide

  13. class_option :database, type: :string, aliases: "-d", default: "postgresql",
    desc: "Configure for selected database (options: #{DATABASES.join("/")})"

    View Slide

  14. 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

    View Slide

  15. 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

    View Slide

  16. 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

    View Slide

  17. Testing generators

    View Slide

  18. 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

    View Slide

  19. 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

    View Slide

  20. 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

    View Slide

  21. 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

    View Slide

  22. 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

    View Slide

  23. 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

    View Slide

  24. In this talk
    Motivation for suspenders
    Current status
    Future plans
    Implementation
    Tests

    View Slide

  25. Resources
    github.com/thoughtbot/suspenders
    guides.rubyonrails.org/generators.html
    Rails::Generators::AppGenerator
    source code
    api.rubyonrails.org/classes/Rails/AppBuilder.html
    github.com/rails/thor

    View Slide

  26. View Slide

  27. View Slide

  28. View Slide

  29. Helvetic Ruby
    24th November 2023 in Bern
    1 day
    8 speakers
    helvetic-ruby.ch

    View Slide