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

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 full-size slide

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

    View full-size slide

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

    View full-size slide

  4. So let’s talk about a few.

    View full-size slide

  5. bootstrapping
    making your app newbie-proof™

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  9. script/bootstrap

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  12. {
    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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  20. cache bootstrap results

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  25. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  28. conditional monkeypatching
    just like a good rubyist

    View full-size slide

  29. - 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  33. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  36. api design
    github api v3

    View full-size slide

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

    View full-size slide

  38. 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 full-size slide

  39. 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 full-size slide

  40. 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 full-size 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
    find_user
    auto-populated based on params[:user]
    manages error handling for nonexistent users

    View full-size 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
    pagination
    auto-populated based on per_page & page params

    View full-size 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
    deliver
    handles nil objects (404)
    appends OAuth scope & rate limiting headers
    encodes as JSON and sets Content-Type
    handles JSONP callbacks

    View full-size 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  47. Big apps suck, services are great.

    View full-size slide

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

    View full-size slide

  49. Compromise: library-in-an-app

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  52. Easy access to your models
    y

    View full-size slide

  53. (Potentially) easier to extract

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  56. github hd™
    zooooooooooooooooooooooom

    View full-size slide

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

    View full-size slide

  58. ⌘+ ⌘-
    ZOOM IN ZOOM OUT

    View full-size slide


  59. 280px
    120px
    SCALE IN-BROWSER

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  63. DOCUMENTATION!
    TO THE RESCUE!

    View full-size slide

  64. We TomDoc the
    shit out of everything

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  67. ...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 full-size slide

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

    View full-size slide

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

    View full-size slide

  70. # 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 full-size slide

  71. # 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 full-size slide

  72. # 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 full-size slide

  73. # 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 full-size slide

  74. # 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 full-size slide

  75. # 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 full-size 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 full-size slide

  77. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  84. DOCUMENT EVERYTHING
    DOCUMENT EVERYTHING
    — no, really —

    View full-size slide

  85. beware novelty
    custom vs. stock

    View full-size slide

  86. GitHub has opinionated employees.

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  91. Technical scaling is sometimes
    easier than organizational scaling.

    View full-size slide

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

    View full-size slide

  93. Zach Holman
    zachholman.com/talks
    @holman

    View full-size slide