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

Breaking Rules to Ship Products: A Beginner's R...

Breaking Rules to Ship Products: A Beginner's Rails Journey

After a long career in marketing, this is my personal story of learning Ruby and Rails with the goal of building SaaS businesses, and the lessons I had to learn along the way.

Avatar for Onur Ozer

Onur Ozer

January 31, 2026
Tweet

More Decks by Onur Ozer

Other Decks in Programming

Transcript

  1. 20 years in marketing Agency Small agencies
 R/GA Startup Digital

    studio
 E-commerce startup Corporate American Express
 Mastercard
  2. Initial Challenges • Ruby vs. Rails? • What is a

    background job? • What do I test? • What on earth is a service object?
  3. Postgreqsl Enums # migrate up create_enum :kind, ["like", "bookmark"] #

    model enum :kind, {like: "like", bookmark: "bookmark"}, default: “like" # migrate down execute <<-SQL DROP TYPE visibility; SQL
  4. Building too much # user.rb after_validation :set_username, on: :create #

    place.rb has_many :interactions, dependent: :destroy has_many :users, through: :interactions has_many :likers, -> {where interactions: {kind: "like"}}, through: :interactions, source: :user
  5. Lessons Learned • Content before community • Right tool for

    the right job • Features as procrastination in disguise
  6. STI is fine # chat.rb class Chat < ApplicationRecord acts_as_chat

    end # persona.rb class Persona < Chat store_accessor :metadata, :target_audience, :country end
  7. "Marketing site must be separate" Separate repo, separate subdomain Jekyll

    Marketing Site www. Rails Application app. JS request JSON response CORS Request/Response Contract Validation / Error Handling Analytics Writing Javascript
  8. Marketing site can live in a Rails app Single repo,

    root domain Rails Application # pages_controller.rb class PagesController < ApplicationController layout "marketing" def free_persona_generator end end
  9. Bonus: Embracing text enums STATUSES = %w[pending completed failed].freeze enum

    :status, STATUSES.index_by(&:itself), default: :pending
  10. Lessons Learned • Shipping is the only way to learn

    • Vitamins vs. painkillers • Start marketing on day one
  11. Service Objects • Code organization tool • Additional layer on

    Rails conventions • Background jobs are service objects
  12. Lessons Learned • Find a partner • Pursue a genuine

    interest • Commit for the long haul
  13. A Perfect Circle Build
 as little as possible Commit
 for

    the long term Talk
 to customers Code is liability Vitamins vs. Painkillers Marathon, not sprint
  14. Competitive intelligence for SaaS teams • Research companies • Build

    comparison tables • Track product & pricing updates Sign up at cogrid.com