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

Optimize for developer speed

Optimize for developer speed

Presented at XO Ruby San Diego (October 2025)

Avatar for Claudio B.

Claudio B.

October 18, 2025
Tweet

More Decks by Claudio B.

Other Decks in Programming

Transcript

  1. Claudio Baccigalupo, 2025 Optimize for developer speed or how I

    built a modern web app as a single developer in eight weeks
  2. 2005 2010 2015 2020 2025 My career on Rails More

    money, less coding Yearly salary Commits to rails/rails
  3. Company.developers.sole # => me class Company has_many :developers end From

    manager to developer An unexpected turn Before November 2024 After November 2024
  4. Two months on Rails One framework, one developer, one app

    encrypts ActiveSupport::Concern upsert_all normalizes enum signed_id broadcasts_refreshes driven_by after_commit performs turbo_native_app?
  5. How it all started A simple mantra: code fast $

    git log --reverse | head -n1 c4d7c9 2024-11-17 rails new -d postgresql --main bookmaker code fast means use what you know code fast means automate early
  6. Optimize for developer speed Claudio Baccigalupo, 2025 part 1 Models

    part 2 Controllers part 3 Tests part 4 Views
  7. # A user who books an appointment. class User <

    ApplicationRecord include Recoverable, Searchable encrypts :first_name, :last_name, :phone encrypts :email, deterministic: { fixed: false }, downcase: true normalizes :phone, with: ->(phone) { phone.delete '^0-9' } # @return [String] the full name of the user. def full_name [first_name, last_name].compact.join ' ' end … Write meaningful models Embrace the power of Active Record app/model/user.rb
  8. # A user who books an appointment. class User <

    ApplicationRecord include Recoverable, Searchable encrypts :first_name, :last_name, :phone encrypts :email, deterministic: { fixed: false }, downcase: true normalizes :phone, with: ->(phone) { phone.delete '^0-9' } # @return [String] the full name of the user. def full_name [first_name, last_name].compact.join ' ' end … Sprinkle inline comments Automatically generate documentation with Yard
  9. # A user who books an appointment. class User <

    ApplicationRecord include Recoverable, Searchable encrypts :first_name, :last_name, :phone encrypts :email, deterministic: { fixed: false }, downcase: true normalizes :phone, with: ->(phone) { phone.delete '^0-9' } select * from users; id | first_name | last_name | phone | email | created_at | 1 | x1p6f7Q="… | YRnZKSrq… | SY+l… | K0m1… | 2024-12-16 11:23:12 | 2 | bq1DGDGsa… | 1SWIri3A… | 2aiu… | YsA=… | 2024-12-16 11:23:13 | 3 | BsmNejafE… | HNyQI2Bk… | P1vd… | p9Bd… | 2024-12-17 09:14:23 | Protect sensitive data Avoid data breach with ActiveRecord::Encryption
  10. # A user who books an appointment. class User <

    ApplicationRecord include Recoverable, Searchable encrypts :first_name, :last_name, :phone encrypts :email, deterministic: { fixed: false }, downcase: true normalizes :phone, with: ->(phone) { phone.delete '^0-9' } # @return [String] the full name of the user. def full_name [first_name, last_name].compact.join ' ' end … Normalize stored data Simplify queries with ActiveModel::Attributes::Normalization app/model/user.rb
  11. # A user who books an appointment. class User <

    ApplicationRecord include Recoverable, Searchable encrypts :first_name, :last_name, :phone encrypts :email, deterministic: { fixed: false }, downcase: true normalizes :phone, with: ->(phone) { phone.delete '^0-9' } # @return [String] the full name of the user. def full_name [first_name, last_name].compact.join ' ' end … Keep files short Extract behavior with ActiveSupport::Concern app/model/user.rb
  12. Use verified tokens Avoid tampering with ActiveRecord::SignedId module User::Recoverable extend

    ActiveSupport::Concern RECOVERY_LINK_EXPIRY_DURATION = 4.hours def recovery_token signed_id purpose: :recover, expires_in: RECOVERY_LINK_EXPIRY_DURATION end class_methods do def find_by_recovery_token(token) find_signed token, purpose: :recover end end end app/model/user/recoverable.rb
  13. Optimize for developer speed Claudio Baccigalupo, 2025 part 1 Models

    part 2 Controllers part 3 Tests part 4 Views
  14. Rails.application.routes.draw do root "pets#index" resource :user, only: %i[ edit update

    ] do resource :session, only: %i[ destroy ] end resources :locations, only: %i[ new create ] resources :pets, only: %i[ new create ] do resources :items, only: %i[ edit update destroy ] end resources :bookings, only: %i[ new create show ] do resource :confirmation, only: %i[ new create ] end end Maintain resourceful routes Reduce surprise adopting the REST pattern con fi g/routes.rb
  15. Avoid bottlenecks Perform slow tasks asynchronously with ActiveJob class Bookings::ConfirmationsController

    before_action { @booking = Booking.find params[:id] } # Show the form to confirm a booking def new end # Enqueue a job to confirm with the service provider # Immediately redirect to the booking page def create BookingConfirmationJob.perform_later @booking redirect_to @booking, status: :see_other end end app/controllers/bookings/con fi rmations_controller.rb
  16. Avoid bottlenecks Perform slow tasks asynchronously with ActiveJob class Bookings::ConfirmationsController

    before_action { @booking = Booking.find params[:id] } # Show the form to confirm a booking def new end # Enqueue a job to confirm with the service provider # Immediately redirect to the booking page def create BookingConfirmationJob.perform_later @booking redirect_to @booking, status: :see_other end end app/controllers/bookings/con fi rmations_controller.rb
  17. Minimize JavaScript Keep pages fresh with Turbo 8 class BookingsController

    < ApplicationController def show @booking = Booking.find params[:id] end end app/controllers/bookings_controller.rb
  18. Minimize JavaScript Keep pages fresh with Turbo 8 class BookingsController

    < ApplicationController def show @booking = Booking.find params[:id] end end <%= turbo_stream_from @booking %> class Booking < ApplicationRecord broadcasts_refreshes end app/model/booking.rb app/controllers/bookings_controller.rb app/views/bookings/show.html.erb
  19. Optimize for developer speed Claudio Baccigalupo, 2025 part 1 Models

    part 2 Controllers part 3 Tests part 4 Views
  20. class FlowsTest < ApplicationSystemTestCase test 'a typical flow' do visit

    root_url # Enter valid contact data assert_text 'Let’s complete your contact information' fill_in 'First name', with: 'Flow' fill_in 'Last name', with: 'Test' fill_in 'ZIP code', with: '90210' fill_in 'Email address', with: '[email protected]' click_button 'Continue' assert_text 'What is your service address?' … Gain visibility over the code Mimic user behavior with ActionDispatch::SystemTestCase test/system/ fl ow_test.rb
  21. require 'simplecov' SimpleCov.minimum_coverage 100 class ApplicationSystemTestCase < ActionDispatch::SystemTestCase browser =

    ENV['CI'] ? :headless_chrome : :chrome driven_by :selenium, using: browser, screen_size: [1400, 1400] end Boost confidence with full coverage Verify all code is executed with Simplecov $ rake Finished in 22.852212s, 0.0438 runs/s, 2.0567 assertions/s. 1 runs, 47 assertions, 0 failures, 0 errors, 0 skips Line Coverage: 100.0% (1080 / 1080) test/application_system_test_case.rb
  22. Optimize for developer speed Claudio Baccigalupo, 2025 part 1 Models

    part 2 Controllers part 3 Tests part 4 Views
  23. Make it nice Prefer a low-e ff ort CSS framework

    without CSS <%= form_with model: @user do |form| %> <label> First name <%= form.text_field :first_name %></label> <label> Last name <%= form.text_field :last_name %></label> <label> Email address <%= form.text_field :email %></label> <label> Phone number (optional) <%= form.text_field :phone %></label> <%= form.submit 'Continue' %> <% end %> app/views/users/new.html.erb
  24. <%= form_with model: @user do |form| %> <label> First name

    <%= form.text_field :first_name %></label> <label> Last name <%= form.text_field :last_name %></label> <label> Email address <%= form.text_field :email %></label> <label> Phone number (optional) <%= form.text_field :phone %></label> <%= form.submit 'Continue' %> <% end %> Make it nice Pico CSS minimizes changes to HTML fi les with Pico CSS app/views/users/new.html.erb
  25. Make it mobile Deliver iOS and Android apps with Hotwire

    Native <%= form_with model: @user do |form| %> <label> First name <%= form.text_field :first_name %></label> <label> Last name <%= form.text_field :last_name %></label> <label> Email address <%= form.text_field :email %></label> <label> Phone number (optional) <%= form.text_field :phone %></label> <%= form.submit 'Continue' %> <% end %> app/views/users/new.html.erb
  26. Make it mobile Incrementally improve with Hotwire Native Bridge components

    <%= form_with model: @user, data: {controller: 'bridge--form'} do |form| %> <!-- … other form elements … --> <%= form.submit 'Continue', data: {'bridge-title': 'Next', 'bridge--form-target': 'submit'} %> <% end %> <% unless turbo_native_app? %> <h1>Let’s complete your information.</h1> <% end %> $ ./bin/importmap pin @hotwired/hotwire-native-bridge app/views/layouts/application.html.erb app/views/users/new.html.erb
  27. Rails is the best one-person framework Here are some recommendations

    Point to the main branch of Rails Contribute to open source Encrypt and normalize data Keep fi les short Generate documentation from code Stick to resourceful routes Run slow tasks asynchronously Favor system tests Enforce complete code coverage Pick a low-impact CSS framework Harness Turbo 8 for page speed Build native apps incrementally Thank you!