$30 off During Our Annual Pro Sale. View Details »

Ruby Patterns from GitHub's Codebase

Ruby Patterns from GitHub's Codebase

We've had some pretty terrible ideas at GitHub. But they're not all bad, honest! Let's talk about some of the good Ruby-themed patterns we use.

Zach Holman

February 29, 2012
Tweet

More Decks by Zach Holman

Other Decks in Programming

Transcript

  1. from github’s codebase
    Ruby Patterns

    View Slide

  2. @holman

    View Slide

  3. View Slide

  4. Like everyone, we have
    some really dumb ideas.

    View Slide

  5. But we have some
    good ideas too! Honest!

    View Slide

  6. So let’s talk about a few.

    View Slide

  7. bootstrapping
    making your app newbie-proof™

    View Slide

  8. your company is going to have
    TONS OF SUCCESS
    which means you’ll have to hire
    TONS OF PEOPLE

    View Slide

  9. GitHub has 200+ repositories.
    and 60+ employees.
    and 20+ languages.
    and about a billion egos.

    View Slide

  10. It needs to be QUICK AND EASY
    to step into an unfamiliar app

    View Slide

  11. script/bootstrap

    View Slide

  12. Most of our Ruby projects have a
    file called in the
    directory.
    bootstrap script/
    Easily jump into a project and start
    contributing.

    View Slide

  13. {
    script/bootstrap
    static page compilation
    language compilation
    db seeding
    dependency checks
    bundler
    db creation
    db migration

    View Slide

  14. {
    static page compilation
    language compilation
    db seeding
    dependency checks
    bundler
    db creation
    db migration
    is mysql installed?
    (here’s how to install it)
    is redis running?
    (here’s how to run it)

    View Slide

  15. {
    static page compilation
    language compilation
    db seeding
    dependency checks
    bundler
    db creation
    db migration
    bundle install \
    --binstubs \
    --local \
    --path=vendor/gems \
    --without=production

    View Slide

  16. {
    static page compilation
    language compilation
    db seeding
    dependency checks
    bundler
    db creation
    db migration
    rake db:create

    View Slide

  17. {
    static page compilation
    language compilation
    db seeding
    dependency checks
    bundler
    db creation
    db migration
    rake db:migrate

    View Slide

  18. {
    static page compilation
    language compilation
    db seeding
    dependency checks
    bundler
    db creation
    db migration
    script/replicate-repo
    (see rtomayko/replicate)

    View Slide

  19. {
    static page compilation
    language compilation
    db seeding
    dependency checks
    bundler
    db creation
    db migration
    404, 500

    View Slide

  20. {
    static page compilation
    language compilation
    db seeding
    dependency checks
    bundler
    db creation
    db migration
    python, c, erlang...

    View Slide

  21. {
    static page compilation
    language compilation
    db seeding
    dependency checks
    bundler
    db creation
    db migration
    t

    View Slide

  22. cache bootstrap results

    View Slide

  23. md5 << File.read('Gemfile')
    checksum = md5.hexdigest
    installed = File.read('.bundle/checksum').strip
    Check if we need to bundle install

    View Slide

  24. should be fast,
    straightforward, and require no
    knowledge of the underlying
    language or app setup
    script/bootstrap

    View Slide

  25. You can also run
    during any process that loads the
    environment, like
    script/bootstrap
    script/server

    View Slide

  26. You can do the same with your
    testing environment, too.

    View Slide

  27. Our CI server only needs to
    ever run .
    script/cibuild
    We add to most
    projects. Lets us keep test
    config in the repository.
    script/cibuild

    View Slide

  28. {
    script/cibuild
    export RACK_ROOT=["..."]
    export RACK_ENV="test"
    script/bootstrap
    bin/rake

    View Slide

  29. QUICK AND EASY
    Make sure your environment is
    to set up.

    View Slide

  30. conditional monkeypatching
    just like a good rubyist

    View Slide

  31. - GitHub is a Rails 2.3(ish) app
    PROBLEM:
    - Upgrading to Rails 3.x sucks
    - Biggest concern is escaping-by-default
    - We wanted incremental rollout

    View Slide

  32. JOSH PEEK Yo let’s just make it
    staff-only like
    everything else we do

    View Slide

  33. def enable_xss_escaping(&block)
    if staff?
    ActiveSupport::SafeBuffer.enable_escaping(&block)
    else
    yield
    end
    end

    View Slide

  34. def enable_xss_escaping(&block)
    if staff?
    ActiveSupport::SafeBuffer.enable_escaping(&block)
    else
    yield
    end
    end

    View Slide

  35. def enable_xss_escaping(&block)
    if staff?
    ActiveSupport::SafeBuffer.enable_escaping(&block)
    else
    yield
    end
    end
    (#enable_escaping is just a patch on SafeBuffer)

    View Slide

  36. Use Ruby’s strengths to limit your
    liability during big code changes.

    View Slide

  37. Also, use partial deployments to
    further limit your exposure.

    View Slide

  38. api design
    github api v3

    View Slide

  39. Sinatra is (naturally) great for an API.
    get “/” do
    “an web app”
    end
    translates
    easily to
    GET / HTTP/1.1

    View Slide

  40. GITHUB API V3
    ~30 Sinatra apps
    located in app/api in our Rails app
    mounted at api.github.com/
    (version in headers)
    routed in rack with path regex

    View Slide

  41. USE HELPERS, SIMPLIFY ROUTES
    # List a user's followers.
    get "/users/:user/followers" do
    control_access :public_read
    deliver find_user.followers.paginate(pagination)
    end

    View Slide

  42. USE HELPERS, SIMPLIFY ROUTES
    # List a user's followers.
    get "/users/:user/followers" do
    control_access :public_read
    deliver find_user.followers.paginate(pagination)
    end
    control_access
    verifies user has permission for the request
    pulls user data from currently-authenticated user

    View Slide

  43. USE HELPERS, SIMPLIFY ROUTES
    # List a user's followers.
    get "/users/:user/followers" do
    control_access :public_read
    deliver find_user.followers.paginate(pagination)
    end
    find_user
    auto-populated based on params[:user]
    manages error handling for nonexistent users

    View Slide

  44. USE HELPERS, SIMPLIFY ROUTES
    # List a user's followers.
    get "/users/:user/followers" do
    control_access :public_read
    deliver find_user.followers.paginate(pagination)
    end
    pagination
    auto-populated based on per_page & page params

    View Slide

  45. USE HELPERS, SIMPLIFY ROUTES
    # List a user's followers.
    get "/users/:user/followers" do
    control_access :public_read
    deliver find_user.followers.paginate(pagination)
    end
    deliver
    handles nil objects (404)
    appends OAuth scope & rate limiting headers
    encodes as JSON and sets Content-Type
    handles JSONP callbacks

    View Slide

  46. USE HELPERS, SIMPLIFY ROUTES
    # List a user's followers.
    get "/users/:user/followers" do
    control_access :public_read
    deliver find_user.followers.paginate(pagination)
    end

    View Slide

  47. USE HELPERS, SIMPLIFY ROUTES
    Extract all these little methods out
    Your API becomes extremely
    Your API becomes extremely
    readable
    testable

    View Slide

  48. inner-app projects
    for services that aren’t yet services

    View Slide

  49. Big apps suck, services are great.

    View Slide

  50. Single responsibility principle and all
    that jazz.
    But it’s harder to do!
    LET ME JUST THROW
    THIS IN THE MODEL!!!!!!111

    View Slide

  51. Compromise: library-in-an-app

    View Slide

  52. lib/#{project}
    test/#{project}

    View Slide

  53. INNER-APP README
    GitHub displays READMEs for every
    directory we find
    Clear area to add documentation

    View Slide

  54. Easy access to your models
    y

    View Slide

  55. (Potentially) easier to extract

    View Slide

  56. TEST HELPERS
    Separate your project’s helper methods
    Fixtures, too

    View Slide

  57. As you grow, it’s harder to mentally
    grasp your entire app. Splitting into well-
    documented chunks can really help.

    View Slide

  58. github hd™
    zooooooooooooooooooooooom

    View Slide

  59. Okay this totally isn’t Ruby, but it’s cool.

    View Slide

  60. ⌘+ ⌘-
    ZOOM IN ZOOM OUT

    View Slide

  61. View Slide

  62. View Slide

  63. View Slide

  64. View Slide


  65. 280px
    120px
    SCALE IN-BROWSER

    View Slide

  66. This is great for iPhones and iPads, too.

    View Slide

  67. documentation
    really doesn’t have to suck. honest.

    View Slide

  68. As you grow larger, it’s harder to
    understand your entire app.
    (I’m totally a broken record)

    View Slide

  69. DOCUMENTATION!
    TO THE RESCUE!

    View Slide

  70. We TomDoc the
    shit out of everything

    View Slide

  71. Pretty much every method has 3-6
    lines of documentation

    View Slide

  72. Sure, you can laugh.
    But working on highly-documented code is
    AMAZING.

    View Slide

  73. ...particularly in Ruby, because following this sucks:
    def mailing_address
    full_name + “\n”
    street_address_1 + “\n” +
    street_address_2 + “\n” +
    “#{city}, #{state} #{zip}”
    end
    def street_address_1
    house_with_street.formatted
    end
    def house_with_street
    StreetAddress.new(data)
    end

    View Slide

  74. Debugging a STRONGLY DYNAMIC
    METAPROGRAMMING-HAPPY
    FLEXIBLE LANGUAGE is hard.

    View Slide

  75. SPEC:
    TOMDOC
    GOAL: internal API documentation for
    your developers
    tomdoc.org
    (generated docs not a priority)

    View Slide

  76. # Cures cancer. Beware of race conditions or else you’ll turn into a
    # newt or something.
    #
    # options - A Hash consisting of:
    # age - The Integer age of the patient.
    # money - The Float amount of money in the patient’s account
    # valuable - A Boolean value of whether the patient is valuable
    # to society or not.
    # rollback - A Boolean of whether we should troll the patient and give
    # cancer again once they’re cured.
    #
    # This returns a Patient record. Raises UncurableError if the patient can’t
    # be cured.
    def cure_cancer (options, rollback)
    # boring implementation details, you can figure it out yourself
    end

    View Slide

  77. # Cures cancer. Beware of race conditions or else you’ll turn into a
    # newt or something.
    #
    # options - A Hash consisting of:
    # age - The Integer age of the patient.
    # money - The Float amount of money in the patient’s account
    # valuable - A Boolean value of whether the patient is valuable
    # to society or not.
    # rollback - A Boolean of whether we should troll the patient and give
    # cancer again once they’re cured.
    #
    # This returns a Patient record. Raises UncurableError if the patient can’t
    # be cured.
    def cure_cancer (options, rollback)
    # boring implementation details, you can figure it out yourself
    end

    View Slide

  78. # Cures cancer. Beware of race conditions or else you’ll turn into a
    # newt or something.
    #
    # options - A Hash consisting of:
    # age - The Integer age of the patient.
    # money - The Float amount of money in the patient’s account
    # valuable - A Boolean value of whether the patient is valuable
    # to society or not.
    # rollback - A Boolean of whether we should troll the patient and give
    # cancer again once they’re cured.
    #
    # This returns a Patient record. Raises UncurableError if the patient can’t
    # be cured.
    def cure_cancer (options, rollback)
    # boring implementation details, you can figure it out yourself
    end

    View Slide

  79. # Cures cancer. Beware of race conditions or else you’ll turn into a
    # newt or something.
    #
    # options - A Hash consisting of:
    # age - The Integer age of the patient.
    # money - The Float amount of money in the patient’s account
    # valuable - A Boolean value of whether the patient is valuable
    # to society or not.
    # rollback - A Boolean of whether we should troll the patient and give
    # cancer again once they’re cured.
    #
    # This returns a Patient record. Raises UncurableError if the patient can’t
    # be cured.
    def cure_cancer (options, rollback)
    # boring implementation details, you can figure it out yourself
    end

    View Slide

  80. # Cures cancer. Beware of race conditions or else you’ll turn into a
    # newt or something.
    #
    # options - A Hash consisting of:
    # age - The Integer age of the patient.
    # money - The Float amount of money in the patient’s account.
    # valuable - A Boolean value of whether the patient is valuable
    # to society or not.
    # rollback - A Boolean of whether we should troll the patient and give
    # cancer again once they’re cured.
    #
    # This returns a Patient record. Raises UncurableError if the patient can’t
    # be cured.
    def cure_cancer (options, rollback)
    # boring implementation details, you can figure it out yourself
    end

    View Slide

  81. # Cures cancer. Beware of race conditions or else you’ll turn into a
    # newt or something.
    #
    # options - A Hash consisting of:
    # age - The Integer age of the patient.
    # money - The Float amount of money in the patient’s account.
    # valuable - A Boolean value of whether the patient is valuable
    # to society or not.
    # rollback - A Boolean of whether we should troll the patient and give
    # cancer again once they’re cured.
    #
    # This returns a Patient record. Raises UncurableError if the patient can’t
    # be cured.
    def cure_cancer (options, rollback)
    # boring implementation details, you can figure it out yourself
    end

    View Slide

  82. # Cures cancer. Beware of race conditions or else you’ll turn into a
    # newt or something.
    #
    # options - A Hash consisting of:
    # age - The Integer age of the patient.
    # money - The Float amount of money in the patient’s account
    # valuable - A Boolean value of whether the patient is valuable
    # to society or not.
    # rollback - A Boolean of whether we should troll the patient and give
    # cancer again once they’re cured.
    #
    # This returns a Patient record. Raises UncurableError if the patient can’t
    # be cured.
    def cure_cancer (options, rollback)
    # boring implementation details, you can figure it out yourself
    end

    View Slide

  83. DON’T WORRY, IT’S NOT VERBOSE:
    Most of your TomDoc will only be two lines:
    # EXPLANATION
    #
    # RETURNS
    Complicated methods (like the previous example)
    will require more detailed TomDoc.

    View Slide

  84. IT’S FLEXIBLE:
    Document as you see fit; it’s a loose spec.

    View Slide

  85. IT’S ALWAYS UP-TO-DATE:
    If you update the code, update the fucking docs.

    View Slide

  86. IT’S OUTRAGEOUSLY HELPFUL
    Humans will always read English easier than code.

    View Slide

  87. IT HELPS WITH TESTS
    You’re detailing input + output already.

    View Slide

  88. TOMDOC IS NOT REQUIRED
    If you don’t dig TomDoc, choose something to
    document your code.

    View Slide

  89. LONG-FORM IS GREAT TOO
    Inner-app READMEs, wiki entries, internal posts.

    View Slide

  90. DOCUMENT EVERYTHING
    DOCUMENT EVERYTHING
    — no, really —

    View Slide

  91. beware novelty
    custom vs. stock

    View Slide

  92. GitHub has opinionated employees.

    View Slide

  93. Custom hacks become difficult to
    deal with as you grow larger.

    View Slide

  94. jump to Rails 3.x
    speed of new hires
    possible reliability
    IMPACTS OUR

    View Slide

  95. “Doing It Yourself” isn’t wrong
    ...but be wary of the implications.

    View Slide

  96. maintainability,
    A lot of this is focused on
    especially as you grow.

    View Slide

  97. Technical scaling is sometimes
    easier than organizational scaling.

    View Slide

  98. Keep that in mind as you build
    your Next Big Thing.

    View Slide

  99. Thanks.

    View Slide

  100. Zach Holman
    zachholman.com/talks
    @holman

    View Slide