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.

78b475797a14c84799063c7cd073962f?s=128

Zach Holman

February 29, 2012
Tweet

Transcript

  1. from github’s codebase Ruby Patterns

  2. @holman

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

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

  6. So let’s talk about a few.

  7. bootstrapping making your app newbie-proof™

  8. your company is going to have TONS OF SUCCESS which

    means you’ll have to hire TONS OF PEOPLE
  9. GitHub has 200+ repositories. and 60+ employees. and 20+ languages.

    and about a billion egos.
  10. It needs to be QUICK AND EASY to step into

    an unfamiliar app
  11. script/bootstrap

  12. Most of our Ruby projects have a file called in

    the directory. bootstrap script/ Easily jump into a project and start contributing.
  13. { script/bootstrap static page compilation language compilation db seeding dependency

    checks bundler db creation db migration
  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)
  15. { static page compilation language compilation db seeding dependency checks

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

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

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

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

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

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

    bundler db creation db migration t
  22. cache bootstrap results

  23. md5 << File.read('Gemfile') checksum = md5.hexdigest installed = File.read('.bundle/checksum').strip Check

    if we need to bundle install
  24. should be fast, straightforward, and require no knowledge of the

    underlying language or app setup script/bootstrap
  25. You can also run during any process that loads the

    environment, like script/bootstrap script/server
  26. You can do the same with your testing environment, too.

  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
  28. { script/cibuild export RACK_ROOT=["..."] export RACK_ENV="test" script/bootstrap bin/rake

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

    up.
  30. conditional monkeypatching just like a good rubyist

  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
  32. JOSH PEEK Yo let’s just make it staff-only like everything

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

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

  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)
  36. Use Ruby’s strengths to limit your liability during big code

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

  38. api design github api v3

  39. Sinatra is (naturally) great for an API. get “/” do

    “an web app” end translates easily to GET / HTTP/1.1
  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
  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
  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
  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
  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
  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
  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
  47. USE HELPERS, SIMPLIFY ROUTES Extract all these little methods out

    Your API becomes extremely Your API becomes extremely readable testable
  48. inner-app projects for services that aren’t yet services

  49. Big apps suck, services are great.

  50. Single responsibility principle and all that jazz. But it’s harder

    to do! LET ME JUST THROW THIS IN THE MODEL!!!!!!111
  51. Compromise: library-in-an-app

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

  53. INNER-APP README GitHub displays READMEs for every directory we find

    Clear area to add documentation
  54. Easy access to your models y

  55. (Potentially) easier to extract

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

  57. As you grow, it’s harder to mentally grasp your entire

    app. Splitting into well- documented chunks can really help.
  58. github hd™ zooooooooooooooooooooooom

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

  60. ⌘+ ⌘- ZOOM IN ZOOM OUT

  61. None
  62. None
  63. None
  64. None
  65. <img src=”logo-4x.png” width=”70” height=”30” /> 280px 120px SCALE IN-BROWSER

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

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

  68. As you grow larger, it’s harder to understand your entire

    app. (I’m totally a broken record)
  69. DOCUMENTATION! TO THE RESCUE!

  70. We TomDoc the shit out of everything

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

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

    AMAZING.
  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
  74. Debugging a STRONGLY DYNAMIC METAPROGRAMMING-HAPPY FLEXIBLE LANGUAGE is hard.

  75. SPEC: TOMDOC GOAL: internal API documentation for your developers tomdoc.org

    (generated docs not a priority)
  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
  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
  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
  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
  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
  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
  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
  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.
  84. IT’S FLEXIBLE: Document as you see fit; it’s a loose

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

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

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

  88. TOMDOC IS NOT REQUIRED If you don’t dig TomDoc, choose

    something to document your code.
  89. LONG-FORM IS GREAT TOO Inner-app READMEs, wiki entries, internal posts.

  90. DOCUMENT EVERYTHING DOCUMENT EVERYTHING — no, really —

  91. beware novelty custom vs. stock

  92. GitHub has opinionated employees.

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

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

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

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

    you grow.
  97. Technical scaling is sometimes easier than organizational scaling.

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

    Thing.
  99. Thanks.

  100. Zach Holman zachholman.com/talks @holman