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

[Tropical on Rails] From 1 to 2

[Tropical on Rails] From 1 to 2

Your MVP shipped. Now what? Getting from a working Rails app to one that survives its own success takes more than shipping features. It takes a proper architecture to build upon, AI-readiness and processes that compound instead of decay.

This talk is a field guide for the jump from MVP to a Rails app ready for rapid growth — in the AI era.

Avatar for Vladimir Dementyev

Vladimir Dementyev

April 10, 2026

More Decks by Vladimir Dementyev

Other Decks in Programming

Transcript

  1. SPACE CONQUEST 7 0 -> 1 -> 2 1897 Rocket

    equation 1957 Sputnik launch 1961 1st man in space
  2. 12

  3. Support for complex domain architectures and bigger teams GROWING PAINS

    18 Clear prescription on where to put business logic; solutions for services, forms, policies... Observability default: JSON logs, tracing, metrics... What Rails developers at 1-2 want First-class native async support
  4. 22

  5. 26

  6. 27 class Dashboard::CoursesController < InertiaController def index courses_scope = Current.user.enrolled_courses

    filtered_scope = CoursesFilter.filter(courses_scope, params) @pagy, @courses = pagy(filtered_scope) end end app/controllers/dashboard/courses_controller.rb EXAMPLE INTERFACE
  7. 28 class Dashboard::CoursesController < InertiaController def index @courses, @current_filter =

    filtery( Current.user.courses.not_removed ) @pagy, @courses = pagy(@courses) end end app/controllers/dashboard/courses_controller.rb ACTUAL INTERFACE
  8. gem "rubanok" #sortable_by filter_spec.rb Types-ready class Dashboard::CoursesFilter < ApplicationFilter process

    :filters do map :states do |states:| raw.where(state: states) end end sortable_by :title, :completed_lessons_count, default: "title" do having "enrolled_at" do |sort_direction: "asc"| raw.order(enrollments: {purchased_at: sort_direction}) .order(id: :asc) end having "is_course_completed" do |sort_direction: "asc"| col = Course.arel_table[:finished_at] ordering = sort_direction == "desc" ? col.desc.nulls_last : col.asc.nulls_first raw.order(ordering).order(id: :asc) end end end app/controllers/dashboard/courses_filter.rb FILTER OBJECT 29 The Thicket Way
  9. gem "rubanok" #sortable_by filter_spec.rb Types-ready describe Dashboard::CoursesFilter do context "sorting"

    do it "applies default sorting (title asc)" it "sorts by completed_lessons_count" it "sorts by enrolled_at via purchased_at" it "sorts by is_course_completed with NULLs" it "falls back to default for unknown fields" end context "filtering" do it "filters by state" it "ignores empty state arrays" end end spec/controllers/dashboard/courses_filter_spec.rb FILTER OBJECT 30 The Thicket Way
  10. gem "rubanok" #sortable_by filter_spec.rb Types-ready describe Dashboard::CoursesFilter do context "sorting"

    do it "applies default sorting (title asc)" it "sorts by completed_lessons_count" it "sorts by enrolled_at via purchased_at" it "sorts by is_course_completed with NULLs" it "falls back to default for unknown fields" end context "filtering" do it "filters by state" it "ignores empty state arrays" end end spec/controllers/dashboard/courses_filter_spec.rb FILTER OBJECT 31 The Thicket Way describe "/dashboard/courses" do it "sorts by completed_lessons_count via CoursesFilter" d expect do get dashboard_courses_path( sort_field: "completed_lessons_count", sort_direction: "asc" ) end.to have_filtered(student.enrolled_courses) .with(Dashboard::CoursesFilter) expect(response).to have_http_status(:ok) expect(inertia.props).to include("sort_field" => "completed_lessons_count", "sort_direction" => "asc") end end spec/requests/dashboard/courses_spec.rb
  11. MISSION COMPLETE Extracted an abstraction layer Introduced a convention Improved

    stability and DX 33 One small step toward maintainability
  12. MORE SMALL STEPS 34 You've learned at Tropical on Rails

    2026 Migrate from STI before it's too late Reordering using int columns can only get you so far Write stronger migrations to stop being scared
  13. 37

  14. AI IS AN AMPLIFIER 38 A IA ecoa a qualidade

    do seu código Garbage in, garbage out
  15. 40

  16. 41

  17. Re-implement filtering Same prompt/process Unlayered vs. Layered EXPERIMENT 44 thicket.com

    × AI rails-app — zsh ▐▛███▜▌ Código Claude v10.04.2026 ▝▜█████▛▘ Vovas 3.7 (1M context) · Claude M ▘▘ ▝▝ > Add filtering and sorting to the student' Requirements: 1. Add a category filter. Param: filters[ca When present, only courses in that categ When absent or blank, all enrolled cours 2. Add sorting. The existing default sort i sort fields: - "completed_lessons_count" — sorts by t count (the completed_lessons_count col
  18. EXPERIMENT 45 UNLAYERED LAYERED Bugs 0 0 Mistakes 3 0

    Contoller LOC 33 2 Net LOC 215 128
  19. 48 DIMENSION UNLAYERED LAYERED Discoverability 3–4 hops 2 hops Isolation

    Shared controller namespace, instance vars Standalone object, returns data Predictability Inconsistent inline patterns, invented a new variant Uniform DSL, matches all existing filters Blast radius 2 files / 33 controller LOC 4 files / 2 controller LOC
  20. The layer didn't make the agent smarter It made the

    agent's mistakes smaller and its correct code cheaper 49
  21. !!! "!! ""! """ AI orchestration patterns Architectural patterns Design

    patterns Idioms ARCHITECTURE × AI 53 Pattern-oriented architecture 2026
  22. GUIDE THE AMPLIFIER Processes # compound-engineering Skills # palkan/skills, superpowers-ruby

    Context enrichment # Tidewave, ruby-lsp Trusted references 54 Quality code and context in, quality code out
  23. 55

  24. Platform that scales and stay resilient Processes that let the

    team deliver SPF CHECKLIST 57 What makes up point 2? Consistent dev environment!
  25. SPACE CONQUEST 60 1897 Rocket equation 1957 Sputnik launch 1961

    1st man in space 1969 Moon landing 1971 1st space station 2026 Just checking in 2126 Welcome!
  26. PREVENT THE GAP Analyze # /layered-rails:analyze Reflect # fixed bugs

    and stale PRs Monitor # know your hot and weak paths Follow Evil Martians 62 Measuring the software-product fit