Slide 1

Slide 1 text

code spelunking basecamp next @qrush

Slide 2

Slide 2 text

i’m @qrush

Slide 3

Slide 3 text

@ i live here

Slide 4

Slide 4 text

i work at 37signals

Slide 5

Slide 5 text

old & new patterns neato gems how stuff works™

Slide 6

Slide 6 text

50 days months commits code-test 12 14k 1:1

Slide 7

Slide 7 text

architecture overview

Slide 8

Slide 8 text

rails services

Slide 9

Slide 9 text

rails mysql

Slide 10

Slide 10 text

rails mysql memcached

Slide 11

Slide 11 text

rails mysql redis memcached

Slide 12

Slide 12 text

rails mysql redis memcached elastic search

Slide 13

Slide 13 text

rails mysql redis memcached depot elastic search

Slide 14

Slide 14 text

rails mysql redis memcached portfolio depot elastic search

Slide 15

Slide 15 text

rails launchpad mysql redis memcached portfolio depot elastic search

Slide 16

Slide 16 text

rails launchpad mysql redis memcached queenbee portfolio depot elastic search

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

rails frontends

Slide 21

Slide 21 text

poller rails

Slide 22

Slide 22 text

poller rails rainbows

Slide 23

Slide 23 text

poller rails rainbows nginx

Slide 24

Slide 24 text

poller rails unicorn rainbows nginx

Slide 25

Slide 25 text

poller rails unicorn nginx rainbows nginx

Slide 26

Slide 26 text

poller rails unicorn unicorn nginx nginx rainbows ... ... nginx rails

Slide 27

Slide 27 text

rails local

Slide 28

Slide 28 text

rails mysql

Slide 29

Slide 29 text

pow http://pow.cx

Slide 30

Slide 30 text

rails mysql pow

Slide 31

Slide 31 text

rails mysql pow launchpad

Slide 32

Slide 32 text

rails mysql pow launchpad pow

Slide 33

Slide 33 text

rails mysql pow launchpad portfolio pow

Slide 34

Slide 34 text

rails mysql pow launchpad portfolio pow pow

Slide 35

Slide 35 text

bcx.dev launchpad.dev 37img.dev

Slide 36

Slide 36 text

testing

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

Test::Unit mocha capybara m http://github.com/qrush/m testing

Slide 39

Slide 39 text

functional pow unit integration

Slide 40

Slide 40 text

functional pow unit integration api

Slide 41

Slide 41 text

functional pow unit integration api poller

Slide 42

Slide 42 text

functional pow unit integration api poller 37id

Slide 43

Slide 43 text

functional pow unit integration api poller 37id acceptance client

Slide 44

Slide 44 text

chain rake tasks

Slide 45

Slide 45 text

require 'rails/test_unit/sub_test_task' Rails::SubTestTask.new api: 'test:prepare' do |t| t.libs << 'test' t.pattern = 'test/api/**/*_test.rb' end

Slide 46

Slide 46 text

Rake::Task['test:run'].enhance do Rake::Task['test:api'].invoke end

Slide 47

Slide 47 text

development setup

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

the reset button

Slide 50

Slide 50 text

one bash script

Slide 51

Slide 51 text

./script/setup

Slide 52

Slide 52 text

./script/setup; rake

Slide 53

Slide 53 text

1. setup app 2. reset database 3. restart server 4. custom wrangling

Slide 54

Slide 54 text

keep it quiet

Slide 55

Slide 55 text

echo "Installing libraries..." { (gem list -i bundler || gem install bundler) && rbenv rehash; bundle install && rbenv rehash } >&3 2>&1

Slide 56

Slide 56 text

use flags to speed up

Slide 57

Slide 57 text

if [ -z "$KEEPDB" ]; then echo "Reloading the database" { # lots o’ rake tasks } >&3 2>&1 fi

Slide 58

Slide 58 text

concerns everywhere

Slide 59

Slide 59 text

sharing code is hard

Slide 60

Slide 60 text

use a concern!

Slide 61

Slide 61 text

module YourConcern extend ActiveSupport::Concern included do # class methods here end # instance methods here end

Slide 62

Slide 62 text

module Ajax extend ActiveSupport::Concern included do before_filter :set_ajax_cookies end private def set_ajax_cookies

Slide 63

Slide 63 text

models trashing searching buckets permissions

Slide 64

Slide 64 text

controllers rendering assets authorization authentication current person, account

Slide 65

Slide 65 text

group alike methods

Slide 66

Slide 66 text

class ApplicationController include CurrentAccount include CurrentPerson include ExceptionNotification include Ajax include Mobile include EnsureCompatibleBrowser include WrongContentTypeFallback

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

modules != classes

Slide 69

Slide 69 text

still a ball of mud

Slide 70

Slide 70 text

assets lots of them

Slide 71

Slide 71 text

~9700 ruby tests coffeescript ~10800 ~9600 lines of:

Slide 72

Slide 72 text

lots of SCSS too!

Slide 73

Slide 73 text

No content

Slide 74

Slide 74 text

vendor/assets jquery backbone underscore node’s EventEmitter wysihtml5 eco

Slide 75

Slide 75 text

eco http://github.com/sstephenson/eco

Slide 76

Slide 76 text

wysihtml5 http://github.com/xing/wysihtml5

Slide 77

Slide 77 text

jquery plugins scrollTo easing embedly cookies md5 outsideEvents

Slide 78

Slide 78 text

javascript patterns

Slide 79

Slide 79 text

extend jQuery

Slide 80

Slide 80 text

$.fn.highlight = -> $(this). effect("highlight", {}, 1500)

Slide 81

Slide 81 text

$.fn.resetForm = -> @removeClass('submitting'). get(0). reset() this

Slide 82

Slide 82 text

window.bcx

Slide 83

Slide 83 text

// application.js.coffee.erb window.bcx ?= {} _.extend bcx, models: {} collections: {} views: {} env: <%= Rails.env.to_json %>

Slide 84

Slide 84 text

data-behaviors

Slide 85

Slide 85 text

access-toggle account_name_cancel account_name_form account_name_header account_name_header_name account_name_link activate add_calendar add_to_all_calendars add_to_all_projects admin_permission alt_date_field assigned_to assignee_name assignee_options attachments_required autoresize autosave backbone_collection backbone_model bounce bounce_nav bucket_selector caching_classic_projects calendar_accesses calendar_alert calendar_event_drag_area calendar_invitees calendar_todo can_create_projects_permissi on collapse_on_click complete confirm confirm_action create_project date_entry date_picker delete dirty_tracking document_version_link due_date edit edit_bucket edit_calendar_event_form edit_calendar_todo edit_identity edit_project_header editable_field_prompt email_preferences enlargeable expand_exclusively expand_on_click expandable expandable expand_exclusively file_drop_target filter_and filter_due format_timeline grant-all has_hover_content input_change_emitter invite invite_notice invitees jump_to_month jump_to_new_calendar_event lazy_invitees lazy_load_subscribers link_container load_assignee_options load_completed_todos member members migrate_account move_action move_operation_form navigate new new_calendar_event new_group new_message new_project new_project_dialog new_subgroup new_todolist new_upload no_due_date no_reset notification_email nubbin pending_attachments project_palette project_star read_only refresh_timeline remove remove_duplicates remove_group remove_invitee remove_member rename_group resend_invitation resubscribe reveal_event_subsc revoke revoke-all save_document save_group scroll_content scroll_forward scroll_reverse scroll_view select_on_focus select_suggestion set_start_of_week show_all_todos show_link_if_acces slide slider sortable sortable_container sortable_handle

Slide 86

Slide 86 text

access-toggle account_name_cancel account_name_form account_name_header account_name_header_name account_name_link activate add_calendar add_to_all_calendars add_to_all_projects admin_permission alt_date_field assigned_to assignee_name assignee_options attachments_required autoresize autosave backbone_collection backbone_model bounce bounce_nav bucket_selector caching_classic_projects calendar_accesses calendar_alert calendar_event_drag_area calendar_invitees calendar_todo can_create_projects_permissi on collapse_on_click complete confirm confirm_action create_project date_entry date_picker delete dirty_tracking document_version_link due_date edit edit_bucket edit_calendar_event_form edit_calendar_todo edit_identity edit_project_header editable_field_prompt email_preferences enlargeable expand_exclusively expand_on_click expandable expandable expand_exclusively file_drop_target filter_and filter_due format_timeline grant-all has_hover_content input_change_emitter invite invite_notice invitees jump_to_month jump_to_new_calendar_event lazy_invitees lazy_load_subscribers link_container load_assignee_options load_completed_todos member members migrate_account move_action move_operation_form navigate new new_calendar_event new_group new_message new_project new_project_dialog new_subgroup new_todolist new_upload no_due_date no_reset notification_email nubbin pending_attachments project_palette project_star read_only refresh_timeline remove remove_duplicates remove_group remove_invitee remove_member rename_group resend_invitation resubscribe reveal_event_subsc revoke revoke-all save_document save_group scroll_content scroll_forward scroll_reverse scroll_view select_on_focus select_suggestion set_start_of_week show_all_todos show_link_if_acces slide slider sortable sortable_container sortable_handle

Slide 87

Slide 87 text

out the wazoo! keeps javascript logic out of views stops tying behavior to css classes still easy to query for

Slide 88

Slide 88 text

$('[data-behavior~=date_picker]'). live 'focus', -> $(this).datepicker()

Slide 89

Slide 89 text

rails-behaviors http://josh.github.com/rails-behaviors/ * not using this yet!

Slide 90

Slide 90 text

page events

Slide 91

Slide 91 text

use custom events! _.bind() is your new best friend trigger custom events for what you need keep namespacing consistent

Slide 92

Slide 92 text

No content

Slide 93

Slide 93 text

Slide 94

Slide 94 text

history.js https://github.com/balupton/History.js/

Slide 95

Slide 95 text

bcx.on 'page:beforechange', 'set bcx.currentBucket', -> bcx.on 'page:change', 'lazily load invitees', -> bcx.on 'page:update', 'install invitee field lists', ->

Slide 96

Slide 96 text

page:beforechange setting the current project/calendar apply classes to start performance timing

Slide 97

Slide 97 text

page:change fired when a new “page” is loaded install views hide/show elements based on roles lazily load data

Slide 98

Slide 98 text

page:update fired after change, on every ajaxSuccess install views, behavior moved loaded data into place stop “busy” animations

Slide 99

Slide 99 text

No content

Slide 100

Slide 100 text

No content

Slide 101

Slide 101 text

console mastery

Slide 102

Slide 102 text

console.count() +1

Slide 103

Slide 103 text

console.count("rendered a todo") 1 console.count("rendered a todo") 2

Slide 104

Slide 104 text

console.count("rendered a todo") 1 console.count("rendered a todo") 2

Slide 105

Slide 105 text

console.warn() console.error() make messages stand out

Slide 106

Slide 106 text

console.group() console.groupEnd() clump together logs

Slide 107

Slide 107 text

No content

Slide 108

Slide 108 text

No content

Slide 109

Slide 109 text

No content

Slide 110

Slide 110 text

console.profile() console.profileEnd() see what’s slow

Slide 111

Slide 111 text

No content

Slide 112

Slide 112 text

No content

Slide 113

Slide 113 text

No content

Slide 114

Slide 114 text

console.profile("something slow") console.profileEnd()

Slide 115

Slide 115 text

anywhere in your code: debugger when stopped: console.trace()

Slide 116

Slide 116 text

chrome.csi()

Slide 117

Slide 117 text

console.csi

Slide 118

Slide 118 text

chrome.csi() see how long it’s been since page load

Slide 119

Slide 119 text

No content

Slide 120

Slide 120 text

No content

Slide 121

Slide 121 text

api building with love

Slide 122

Slide 122 text

Repo for API docs http://github.com/37signals/bcx-api

Slide 123

Slide 123 text

jbuilder http://github.com/rails/jbuilder

Slide 124

Slide 124 text

json.(@document, :id, :title, :content, :created_at, :updated_at) json.last_updater @document.last_updater, :id, :name json.partial! "api/comments/comments", comments: @document.comments

Slide 125

Slide 125 text

{ "id": 963979453, "title": "By Jove!", "content": "I’ve figured it out!", "created_at": "2012-03-27T13:19:29-05:0 "updated_at": "2012-03-27T13:53:24-05:0 "last_updater": { "id": 149087659, "name": "Sherlock Holmes" }, "comments": [ ] }

Slide 126

Slide 126 text

why not to_json? keep view data in the views use partials! public vs private JSON

Slide 127

Slide 127 text

strong_parameters http://github.com/rails/strong_parameters

Slide 128

Slide 128 text

class Api::DocumentsController < Api::BaseController def update @document.update_attributes! document_params render :show end def document_params params.required(:document). permit(:title, :content) end end

Slide 129

Slide 129 text

class Api::DocumentsController < Api::BaseController def update @document.update_attributes! document_params render :show end def document_params params.required(:document). permit(:title, :content) end end

Slide 130

Slide 130 text

class Api::DocumentsController < Api::BaseController def update @document.update_attributes! document_params render :show end def document_params params.required(:document). permit(:title, :content) end end

Slide 131

Slide 131 text

class Api::DocumentsController < Api::BaseController def update @document.update_attributes! document_params render :show end def document_params params.required(:document). permit(:title, :content) end end

Slide 132

Slide 132 text

why not attr_accessible? controller, not a model problem can still share via concerns easier to return error, status codes

Slide 133

Slide 133 text

HTTP 204 No Content http://httpstatus.es/204 Use on create, destroy actions!

Slide 134

Slide 134 text

can’t read headers from HTTP 204 http://bugs.jquery.com/ticket/1450

Slide 135

Slide 135 text

No content

Slide 136

Slide 136 text

No content

Slide 137

Slide 137 text

logging is worth it

Slide 138

Slide 138 text

No content

Slide 139

Slide 139 text

log EVERYTHING unique ID per request account ID for log splitting controller, actions on each SQL query

Slide 140

Slide 140 text

ActiveSupport::TaggedLogger new in Rails 3.2!

Slide 141

Slide 141 text

YourApp::Application.configure do config.log_tags = [:uuid] end [71ba53fd717c67a6677a058f4a5acdf4] Processing by ProjectsController#index as HTML

Slide 142

Slide 142 text

BCX::Application.configure do config.log_tags = [ -> request { request.env['bcx.account.queenbee_id'] } ] end

Slide 143

Slide 143 text

That just happened. <3 Ruby 1.9 lambdas Extremely configurable logging Use Rack’s environment for sharing data

Slide 144

Slide 144 text

# github.com/rails/rails # 3-2-stable # railties/lib/rails/application.rb middleware.use ::Rails::Rack::Logger, config.log_tags

Slide 145

Slide 145 text

module Rails::Rack::Logger # if tags are defined... Rails.logger.tagged(compute_tags(env)) { call_app(env) } # else call_app(env)

Slide 146

Slide 146 text

def compute_tags(env) request = ActionDispatch::Request.new(env) @tags.collect do |tag| case tag when Proc tag.call(request) when Symbol request.send(tag) else tag end end end

Slide 147

Slide 147 text

def compute_tags(env) request = ActionDispatch::Request.new(env) @tags.collect do |tag| case tag when Proc tag.call(request) when Symbol request.send(tag) else tag end end end

Slide 148

Slide 148 text

def compute_tags(env) request = ActionDispatch::Request.new(env) @tags.collect do |tag| case tag when Proc tag.call(request) when Symbol request.send(tag) else tag end end end

Slide 149

Slide 149 text

def compute_tags(env) request = ActionDispatch::Request.new(env) @tags.collect do |tag| case tag when Proc tag.call(request) when Symbol request.send(tag) else tag end end end

Slide 150

Slide 150 text

def compute_tags(env) request = ActionDispatch::Request.new(env) @tags.collect do |tag| case tag when Proc tag.call(request) when Symbol request.send(tag) else tag end end end

Slide 151

Slide 151 text

No content

Slide 152

Slide 152 text

marginalia http://github.com/37signals/marginalia

Slide 153

Slide 153 text

Account Load (0.3ms) SELECT `accounts`.* FROM `accounts` WHERE `accounts`.`queenbee_id` = 1234567890 LIMIT 1 /*application:BCX, controller:project_imports,action:show*/

Slide 154

Slide 154 text

# in your Gemfile gem 'query_comments' # in an initializer Marginalia.application_name = "BCX"

Slide 155

Slide 155 text

MORE logging? Know where slow queries came from Immediately identify web or job query MySQL, sqlite, maybe Postgres now?

Slide 156

Slide 156 text

stats count everything

Slide 157

Slide 157 text

statsd http://github.com/etsy/statsd http://github.com/jeremy/statsd-ruby

Slide 158

Slide 158 text

tracks: usage, performance successful, failed logins processed emails people invited

Slide 159

Slide 159 text

ActiveSupport::Notifications http://api.rubyonrails.org/classes/ ActiveSupport/Notifications.html

Slide 160

Slide 160 text

ActiveSupport::Notifications.instrument :usage, measurement: "logins.succeeded"

Slide 161

Slide 161 text

ActiveSupport::Notifications.instrument :usage, measurement: "logins.succeeded" event name

Slide 162

Slide 162 text

ActiveSupport::Notifications.instrument :usage, measurement: "logins.succeeded" hash payload

Slide 163

Slide 163 text

ActiveSupport::Notifications.subscribe(/usage/) do |name, start, finish, id, payload| Statsd.increment(payload[:measurement]) end

Slide 164

Slide 164 text

resque-statsd https://github.com/jamster/resque-statsd

Slide 165

Slide 165 text

No content

Slide 166

Slide 166 text

No content

Slide 167

Slide 167 text

tracks: enqueue count completion count failure count time to process

Slide 168

Slide 168 text

we’re still learning don’t stop evolving

Slide 169

Slide 169 text

thanks for listening! @qrush