Slide 1

Slide 1 text

THE GREAT MIGRATION David Demaree [email protected] • @ddemaree ChicagoRuby October 4, 2011

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

http://j.mp/q2jl54

Slide 4

Slide 4 text

Merb to Rails 3

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

Also, the Merb guys aren’t just abandoning the existing Merb user base and their applications. They’ll still be doing bug fixes, security fixes, and work on easing the upgrade path to Rails 3. This will all progress in a nice, orderly fashion. David Heinemeier Hansson DECEMBER 23, 2008

Slide 9

Slide 9 text

Merb folks will not be left out in the cold. We will continue to support … the merb 1.0.x line. And we will provide a clear upgrade path to Rails 3.0 for merb apps. Ezra Zygmuntowicz DECEMBER 23, 2008

Slide 10

Slide 10 text

We will also release versions of Merb specifically designed to help ease the transition to Rails 3 … with deprecation notices and other transitional mechanisms to assist developers in tracking down the changes that will come between Merb 1.x and Rails 3. Yehuda Katz DECEMBER 23, 2008

Slide 11

Slide 11 text

M E R B C O M M U N I T Y c a . 2 0 1 1

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

Fork and maintain Merb Move to Rails 3

Slide 14

Slide 14 text

Move to Rails 3 Fork and maintain Merb Move to Rails 3

Slide 15

Slide 15 text

D A V I D V S . M E R B

Slide 16

Slide 16 text

S E T U P

Slide 17

Slide 17 text

http://cl.ly/AebP

Slide 18

Slide 18 text

MERB config/init.rb config/router.rb RAILS config/application.rb config/routes.rb Shared Config Directory

Slide 19

Slide 19 text

module Typekit class Application < Rails::Application ... config.before_initialize do Merb.push_path(:view, "#{config.root}/app/views") end config.after_initialize do Merb.start_environment(:environment => Rails.env.to_s) end ... end end

Slide 20

Slide 20 text

app/ controllers/ antiques.rb application.rb application_controller.rb exceptions.rb novelties_controller.rb MERB & RAILS CONTROLLERS, TOGETHER AT LAST

Slide 21

Slide 21 text

class Application < Merb::Controller ... include Typekit::MerbExtensions::RailsHelper include Typekit::MerbExtensions::CsrfSupport ... end Bridging Merb to Rails

Slide 22

Slide 22 text

module Typekit module MerbExtensions module RailsHelper extend ActiveSupport::Concern included do |base| base.class_eval do # Include Rails routes into Merbland include Rails.application.routes.url_helpers # Alias resource/url routes as merb_resource/merb_url, # for API consistency with the Rails app alias merb_resource resource alias merb_url url end end ...

Slide 23

Slide 23 text

# app/helpers/typekit/merb_transition_helper.rb module Typekit module MerbTransitionHelper include AssetBundles def merb_url(named_url, *args) Merb::Router.url(named_url, *args) end ... # app/controllers/application_controller.rb class ApplicationController < ActionController::Base include Typekit::MerbTransitionHelper ... Bridging Rails to Merb

Slide 24

Slide 24 text

R O U T I N G

Slide 25

Slide 25 text

module RackApplication def self.new Rack::Builder.new do use Middleware::ExceptionCatcher map "#{host}/api/v1" do # Sinatra-based API app run Api::V1::Application.new end map "#{host}/" do # Merb app run Merb::Rack::Application.new end end end end

Slide 26

Slide 26 text

module RackApplication def self.new Rack::Builder.new do use Middleware::ExceptionCatcher map "#{host}/" do # Rails app run Typekit::Application end end end end

Slide 27

Slide 27 text

Typekit::Application.routes.draw do # Sinatra app mount Api::V1::Application => "/api/v1" # .. Rails routes here .. # # Merb app match "*wildcard" => Merb::Rack::Application.new # Home page (Rails controller) root :to => "static_pages#home" end config/routes.rb

Slide 28

Slide 28 text

# Passes through entire URI get "/api/v1" => Api::V1::Application # Only passes through the segment after # the given prefix, e.g. /api/v1/foo is # handled by the Sinatra app as /foo mount Api::V1::Application => "/api/v1"

Slide 29

Slide 29 text

Next-Level Routing Tricks

Slide 30

Slide 30 text

constraints(FlipperConstraint.new(:new_browse_ui)) do get "/fonts" => "browse/families#index", :as => :browse_families end # If the :new_browse_ui flag is false, requests # for /fonts are served by the Merb app CONSTRAINTS

Slide 31

Slide 31 text

class FlipperConstraint def initialize(flag) @flag = flag end def matches?(request) request.env['flipper.manager'].flag_enabled?(@flag) end end

Slide 32

Slide 32 text

# config/routes.rb # /backend is normally served by the Merb app get "/backend" => "backend/home#index" # app/controllers/backend/home_controller.rb class Backend::HomeController < ApplicationController def index # The X-Cascade header tells Rack to skip this # endpoint and look for the next suitable one, # which in this case would be the Merb app head :ok, "X-Cascade" => "pass" end end X-CASCADE: PASS

Slide 33

Slide 33 text

M I D D L E W A R E

Slide 34

Slide 34 text

# send all exceptions to hoptoad use Rack::Hoptoad, HOPTOAD_API_KEY do |notifier| notifier.environment_filters << %w(warden rack.session) end # run offline jobs after each request when a # separate process isn't used. use Middleware::OfflineJobRunner # reload the application after each request if # rails is configured to do so. use Middleware::RailsAutoload app/racks/rack_application.rb

Slide 35

Slide 35 text

config/initializers/middleware.rb Rails.application.config.middleware.tap do |m| # send all exceptions to hoptoad m.use Rack::Hoptoad, HOPTOAD_API_KEY do |notifier| notifier.environment_filters << %w(warden rack.session) end # run offline jobs after each request when a # separate process isn't used. m.use Middleware::OfflineJobRunner # reload the application after each request if # rails is configured to do so. m.use Middleware::RailsAutoload end

Slide 36

Slide 36 text

Cookies, Sessions, & the Rails Flash

Slide 37

Slide 37 text

module RackApplication def self.new Rack::Builder.new do use Middleware::ExceptionCatcher use Rack::Session::Cookie, :key => ::Merb::Config[:session_cookie_name], :secret => ::Merb::Config[:session_secret_key], :domain => typekit_cookie_domain, :httponly => true ... end end end app/racks/rack_application.rb Stage 1: Rack sessions

Slide 38

Slide 38 text

# In a middleware session['user.id'] = 1234 #=> 1234 # In a Rails controller session['user.id'] #=> nil

Slide 39

Slide 39 text

# Initialize Rails cookie store use Middleware::SecretToken, "sekrit token here" use ActionDispatch::Cookies # Enable Rails session storage for all endpoints use ActionDispatch::Session::CookieStore, :key => Merb::Config[:session_cookie_name], :domain => typekit_cookie_domain, :httponly => true # Enable Rails flash use ActionDispatch::Flash Stage 2: Port Rails session middlewares to RackApplication

Slide 40

Slide 40 text

module Middleware class SecretToken TOKEN_KEY = "action_dispatch.secret_token".freeze def initialize(app, secret) @app = app; @secret = secret end def call(env) env[TOKEN_KEY] ||= @secret return @app.call(env) end end end

Slide 41

Slide 41 text

Stage 3: Switch to standard Rails sessions # config/initializers/session_store.rb Typekit::Application.config.session_store :cookie_store, :key => Typekit.config.session_cookie_name, :domain => typekit_cookie_domain, :httponly => true

Slide 42

Slide 42 text

Cross-Site Request Forgery (CSRF) Protection

Slide 43

Slide 43 text

module Typekit module MerbExtensions module CsrfSupport extend ActiveSupport::Concern def verified_request? request.method == :get || form_authenticity_token == params[csrf_param_name] || form_authenticity_token == request.env['HTTP_X_CSRF_TOKEN'] end def verify_csrf_token unless verified_request? raise Merb::Controller::PreconditionFailed end end end end end

Slide 44

Slide 44 text

module Middleware class CsrfToken def initialize(app) @app = app end def call(env) env['rack.session'][:_csrf_token] ||= SecureRandom.base64(32) @app.call(env) end end end

Slide 45

Slide 45 text

T E S T I N G

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

A test suite should be a trusted system.

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

Custom Example Groups Our solution:

Slide 50

Slide 50 text

module Typekit module LegacyRequestExampleGroup extend ActiveSupport::Concern # Include Merb request stuff include Merb::Test::RouteHelper include Merb::Test::RequestHelper include Merb::Test::MultipartRequestHelper ... end end Defining a custom example group

Slide 51

Slide 51 text

RSpec.configure do |config| config.include Typekit::LegacyRequestExampleGroup, :type => :legacy_request, :example_group => { :file_path => config.escaped_path(%w[spec requests]) } end Adding a custom example group to RSpec

Slide 52

Slide 52 text

# BTW, specs in spec/requests are automatically tagged thusly describe "GET /login over non-SSL", :type => :legacy_request do it "should redirect to SSL" do # Returns a Merb::Test::MakeRequest struct object @response = request("/login") @response.should redirect_to("https://secure.example.org/login") end end

Slide 53

Slide 53 text

describe "GET /login over non-SSL", :type => :legacy_request do it "should redirect to SSL" do # Returns a Merb::Test::MakeRequest struct object @response = request("/login") @response.should redirect_to("https://secure.example.org/login") end end describe "GET /login over SSL", :type => :legacy_request do it "should be successful" do # Returns an ActionDispatch::Request object @response = request(path_over_ssl("/login")).body @response.status.should == 200 end end

Slide 54

Slide 54 text

Cucumber Merb legacy request specs Rails integration specs Capybara-driven acceptance specs Choose your tools wisely

Slide 55

Slide 55 text

Q&A to make up for your speaker’s woeful lack of preparation

Slide 56

Slide 56 text

S T R AT E G Y

Slide 57

Slide 57 text

Merb and Rails co-exist for the foreseeable future

Slide 58

Slide 58 text

New work should be done in Rails

Slide 59

Slide 59 text

Favor small iterations over big rewrites

Slide 60

Slide 60 text

Try to change just one thing at a time Trustworthy test coverage, passing before and after Code review Merge and deploy once tests are passing Big, complex features can be migrated in stages

Slide 61

Slide 61 text

Build bridges between Rails & Merb when necessary

Slide 62

Slide 62 text

Use Rack

Slide 63

Slide 63 text

Use integration tests

Slide 64

Slide 64 text

When in doubt, read the source code

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

No content

Slide 69

Slide 69 text

No content

Slide 70

Slide 70 text

No content

Slide 71

Slide 71 text

No content

Slide 72

Slide 72 text

No content