decyphering rails 3

70
Deciphering Rails 3 with Gregg Pollack

Upload: toster

Post on 08-May-2015

1.342 views

Category:

Technology


3 download

DESCRIPTION

Код ядра Rails был существенно улучшен с выпуском Rails 3, в основном из-за использования эффективных паттернов проектирования. Мы разберем некоторые из ключевых изменений, которые привели к улучшению качества кода, и на их примере научимся применять такие техники к своему собственному коду.Вот некоторые из таких техник:Компилирование методов vs method_missingМикроядерная архитектураalias_method_chain vs superActiveSupport::ConcernCatch/Throw в BundlerСлушатели намного улучшат свои знания о некоторых сложных паттернах проектирования в Ruby и станут лучше разбираться во внутренностях Rails 3.

TRANSCRIPT

Page 1: Decyphering Rails 3

Deciphering Rails 3with Gregg Pollack

Page 2: Decyphering Rails 3

# Basi

c implemen

tations fo

r content_

type=, loc

ation=, an

d headers

are

# prov

ided to re

duce the d

ependency

on the Rac

kDelegatio

n module

# in R

enderer an

d Redirect

or.

def co

ntent_type

=(type)

head

ers["Conte

nt-Type"]

= type.to_

s

end

def co

ntent_type

head

ers["Conte

nt-Type"]

end

def lo

cation

head

ers["Locat

ion"]

end

def lo

cation=(ur

l)

head

ers["Locat

ion"] = ur

l

end

def st

atus

@_st

atus

end

def st

atus=(stat

us)

@_st

atus = Rac

k::Utils.s

tatus_code

(status)

end

response_

body=(val)

val.respo

nd_to?(:ea

ch) ? val

: [val]

Don’t be afraid of readi

ng the source

Don’t be afraid of making it your own

Page 3: Decyphering Rails 3

Deciphering Rails 3

Method Compilation

Microkernel Architecture

alias_method_chain vs super

ActiveSupport::Concern

Catch/Throw in Bundler

Page 4: Decyphering Rails 3

let’s test the water

Page 5: Decyphering Rails 3

class SimplePrint attr_accessor :city

def method_missing(meth) puts "#{meth} #{city}" endend

def initialize(val) self.city = val end

def city return @city end def city=(val) @city = val end

> s = SimplePrint.new("moscow") => #<SimplePrint:0x000001020013d0 @city="moscow"> > s.toastertoaster moscow => nil

s = SimplePrint.new("moscow")

s.toaster

Basics

Page 6: Decyphering Rails 3

ToasterMoscow

eval

hash = {'name' => 'Toaster', 'location' => 'Moscow'}

hash.each_pair do |key, value| eval <<-RUBY @#{key} = value RUBYend

puts @name + @location @location = 'Moscow'

@name = 'Toaster'

Page 7: Decyphering Rails 3

instance_eval

class Test def print puts "Hello" endend

Test.new.instance_eval do print end

t = Test.newt.print

Hello

Hello2

t = Test.newt.instance_eval do def print2 puts "Hello2" endend

t.print2

Hello

NoMethodError: undefined method ‘print2’ for #<Test:0x100124b60>

Test.new.print2

Page 8: Decyphering Rails 3

class_eval

class Testend

Test.class_eval do def print puts "Ruby5" endend

Test.new.print

Ruby5 Ruby5

class Testend

class Test def print puts "Ruby5" endend

Test.new.print

Page 9: Decyphering Rails 3

Method Compilation

Page 10: Decyphering Rails 3

s = SimplePrint.new [:push_it, :pop_it, :top_it]

s.push_its.pop_its.top_it

=> “called push_it”=> “called pop_it”=> “called top_it”

output

class SimplePrint attr_accessor :actions def initialize(arr) self.actions = arr end

def print(meth) puts "called #{meth}" end

def method_missing(meth) print(meth) if actions.include? meth endend

Page 11: Decyphering Rails 3

s = SimplePrint.new [:push_it, :pop_it, :top_it]

30000.times do s.push_it s.pop_it s.top_itend

Using Method Missing 5 Seconds

Page 12: Decyphering Rails 3

class SimplePrint attr_accessor :actions def initialize(arr) self.actions = arr end

def print(meth) puts "called #{meth}" end

def method_missing(meth) print(meth) if actions.include? meth endend

class BetterPrint

def initialize(arr) arr.each do |meth|

end end def print(meth) puts "called #{meth}" end

end

instance_eval <<-RUBY

RUBY

def #{meth} print("#{meth}") end

s = SimplePrint.new [:push_it, :pop_it, :top_it]

Page 13: Decyphering Rails 3

class BetterPrint

def initialize(arr) arr.each do |meth|

end end def print(meth) puts "called #{meth}" end

end

instance_eval <<-RUBY

RUBY

def #{meth} print("#{meth}") end

def push_it print("push_it")end

def pop_it print("pop_it")end

def top_it print("top_it")end

[:push_it, :pop_it, :top_it]

Page 14: Decyphering Rails 3

s = SimplePrint.new [:push_it, :pop_it, :top_it]

30000.times do s.push_it s.pop_it s.top_itend

Using Method Missing 5 Seconds

Using Instance Eval 3.5 Seconds

30% Faster!

Page 15: Decyphering Rails 3

ruby-profUsing Method Missing

Using Method Compliation

Page 16: Decyphering Rails 3

Yehuda’s first commit

rails/actionpack/lib/abstract_controller/metal/mime_responds.rb

rails/actionpack/lib/abstract_controller/collector.rb

Page 17: Decyphering Rails 3

ActiveSupport CallbacksRefactored for Speed

before_filter :authenticate

after_filter :fetch_extra_data

around_filter :transaction

skip_filter :admin_only, :except => [:new]

Used in ActionPack, Test::Unit, and ActionController

10x Faster with method compilation

Page 18: Decyphering Rails 3

method compliation

class PostsController < ApplicationController layout 'custom'end

layout :symbol

layout proc{ |c| c.logged_in? ? "member" : "non-member" }

layout false

layout nil

Are  you  a  string,  symbol,  proc,  boolean,  or  nil?

Page 19: Decyphering Rails 3

method compliationrails/actionpack/lib/abstract_controller/layouts.rb

layout_definition = case _layout when String _layout.inspect when Proc define_method :_layout_from_proc, &_layout "_layout_from_proc(self)" when false nil when true raise ArgumentError when nil name_clauseend

self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def _layout #{layout_definition} end private :_layoutRUBY

Page 20: Decyphering Rails 3

Method Compilation

Page 21: Decyphering Rails 3

let’s test the water

Page 22: Decyphering Rails 3

including modules

class Test include PrintMethend

Test.new.print

class Test extend PrintMethend

Test.print

Ruby5 Ruby5

module PrintMeth def print puts "Ruby5" endend

As Instance Method As Class Method

Page 23: Decyphering Rails 3

Microkernel Architecture

Page 24: Decyphering Rails 3

A refactor of ActionController in ActionPack

AbstractController

Provides  A  Low  Level  Interface  for  making  Customized  Controller  Base  classes.

Carl Lerche

Page 25: Decyphering Rails 3

Microkernel Design PatternOld ActionController Stack

ActionDispatchLayouts

Logger

Callbacks

Renderer

url_forhttp_auth

helpers

MimeResponds

CookiesSessionRouter

Page 26: Decyphering Rails 3

Microkernel Design PatternOld ActionController Stack

Layouts

Logger

Callbacks

Renderer

url_forhttp_auth

helpers

MimeResponds

CookiesSession

AbstractControllerAssignsCallbacksCollectorHelpersLayoutsLoggerRenderingTranslationViewPaths

Page 27: Decyphering Rails 3

AssignsCallbacksCollectorHelpersLayoutsLoggerRenderingTranslationViewPaths

Microkernel Design Pattern

AbstractController

Page 28: Decyphering Rails 3

rails/actionpack/lib/abstract_controller/base.rb

The MicroKernel

module AbstractController class Base attr_internal :response_body attr_internal :action_name

abstract!

# Calls the action going through the entire action dispatch stack. def process(action, *args)

def controller_path

def action_methods

private

def action_method?(name)

def process_action(method_name, *args)

def _handle_action_missing

def method_for_action(action_name) endend

Page 29: Decyphering Rails 3

AbstractController::Base

The microkernel with the bare minimum needed for dispatching

AssignsCallbacksCollectorHelpersLayoutsLoggerRenderingTranslationViewPaths

Modules that can be included to add functionality on top of base

before_filter :authorizedafter_filter :send_emailaround_filter :benchmark

layout 'custom' layout :symbol layout false layout nil

helper :custom helper_method :current_user

Page 30: Decyphering Rails 3

Microkernel Design Pattern

AbstractController::BaseActionController::Metal <<

contains just enough code to get a valid Rack application from a controller

AbstractController::BaseActionMailer::Base <<

AssignsBaseCallbacksCollectorHelpersLayoutsLoggerRenderingTranslationViewPaths

Page 31: Decyphering Rails 3

Microkernel Design Pattern

AbstractController::BaseActionController::Metal <<

class HelloController < ActionController::Metal def index self.response_body = "Hello World!" endend

match 'hello', :to => HelloController.action(:index)

app/controllers/hello_controller.rb

config/routes.rb

Page 32: Decyphering Rails 3

rails/actionpack/lib/action_controller/metal.rb

Microkernel Design PatternActionController::Base

module ActionController class Metal < AbstractController::Base abstract!

def self.call(env) action(env['...'][:action]).call(env) end

def self.action(name, klass = ActionDispatch::Request) middleware_stack.build do |env| new.dispatch(name, klass.new(env)) end end

def dispatch(name, request) @_request = request @_env = request.env @_env['action_controller.instance'] = self process(name) response ? response.to_a : [status, headers, response_body] end endend

request

Page 33: Decyphering Rails 3

ActionController::Metal

AbstractController::Base

AssignsCallbacksCollectorHelpersLayoutsLoggerRenderingTranslationViewPaths

CookiesExceptionsFlashHelpersRedirectingRenderingResponderUrlFor..... (lots more)

redirect_to post_url(@post)

class PeopleController < ApplicationController respond_to :html, :xml, :json

def index @people = Person.find(:all) respond_with(@people) end end

Page 34: Decyphering Rails 3

Microkernel Design Pattern

class HelloController < ActionController::Metal include ActionController::Rendering append_view_path "#{Rails.root}/app/views"

def index render "hello/index" endend

app/controllers/hello_controller.rb

app/controllers/hello_controller.rbclass HelloController < ActionController::Metal include ActionController::Redirecting include Rails.application.routes.url_helpers

def index redirect_to root_url endend

Page 35: Decyphering Rails 3

rails/actionpack/lib/action_controller/base.rb

Microkernel Design PatternActionController::Base

Cookies, Flash, Verification, RequestForgeryProtection, Streaming, RecordIdentifier, Instrumentation, AbstractController::Callbacks, Rescue ]

MODULES.each do |mod| include mod end

module ActionController class Base < Metal abstract!

MODULES = [ AbstractController::Layouts, AbstractController::Translation,

Helpers, HideActions, UrlFor, Redirecting, Rendering, Renderers::All, ConditionalGet, RackDelegation, SessionManagement, Caching, MimeResponds, PolymorphicRoutes, ImplicitRender,

module ActionController module Helpers

include AbstractController::Helpers ...

Page 36: Decyphering Rails 3

ActionController::Metal

AbstractController::Base

AssignsCallbacksCollectorHelpersLayoutsLoggerRenderingTranslationViewPaths

ActionController::Base

CookiesExceptionsFlashHelpersRedirectingRenderingResponderUrlFor..... (lots more)

abstract

abstract

abstract

AbstractController Namespace

ActionController Namespace

includes

Page 37: Decyphering Rails 3

Microkernel Architecture

Page 38: Decyphering Rails 3

# Basi

c implemen

tations fo

r content_

type=, loc

ation=, an

d headers

are

# prov

ided to re

duce the d

ependency

on the Rac

kDelegatio

n module

# in R

enderer an

d Redirect

or.

def co

ntent_type

=(type)

head

ers["Conte

nt-Type"]

= type.to_

s

end

def co

ntent_type

head

ers["Conte

nt-Type"]

end

def lo

cation

head

ers["Locat

ion"]

end

def lo

cation=(ur

l)

head

ers["Locat

ion"] = ur

l

end

def st

atus

@_st

atus

end

def st

atus=(stat

us)

@_st

atus = Rac

k::Utils.s

tatus_code

(status)

end

response_

body=(val)

val.respo

nd_to?(:ea

ch) ? val

: [val]

Don’t be afraid of readi

ng the source

Page 39: Decyphering Rails 3

let’s test the water

Page 40: Decyphering Rails 3

Both instance and class methods

class Test include PrintMethend

Test.new.print_podcastTest.print_company

Ruby5Envy Labs

def print_podcast puts "Ruby5" end

def self.included(base)

end

module PrintMeth

module ClassMethods def print_company puts "Envy Labs" end endend

? base.extend(ClassMethods) base.class_eval do extend ClassMethods end

Page 41: Decyphering Rails 3

alias_method_chain vs super

Page 42: Decyphering Rails 3

class GenericUser def name "Gregg Pollack" endend

class User < GenericUser

endinclude Mr

=> “Gregg Pollack”output

puts User.new.name

=> “Mr. Gregg Pollack”output wanted

module Mr

end

def name_with_mr "Mr. " + name_without_mr end

end end

def self.included(base) base.class_eval do alias :name_without_mr :name alias :name :name_with_mr

name_without_mr

name

Can’t modify parent class

Page 43: Decyphering Rails 3

module Mr

end

end end

def self.included(base) base.class_eval do alias :name_without_mr :name alias :name :name_with_mr

alias_method_chain :name, :mr

class GenericUser def name "Gregg Pollack" endend

class User < GenericUser

endinclude Mr

=> “Gregg Pollack”output

puts User.new.name

=> “Mr. Gregg Pollack”output wanted

def name_with_mr "Mr. " + name_without_mr end

name_without_mr

name

Can’t modify parent class

Page 44: Decyphering Rails 3

class GenericUser def name "Gregg Pollack" endend

class User < GenericUser

endinclude Mr

=> “Gregg Pollack”output

puts User.new.name

=> “Mr. Gregg Pollack”output wanted

module Mr

end

def name "Mr. " + super end< GenericUser

Page 45: Decyphering Rails 3

A refactor of ActionController in ActionPack

AbstractController

Provides  A  Low  Level  Interface  for  making  Customized  Controller  Base  classes.

Carl Lerche

Page 46: Decyphering Rails 3

rails/actionpack/lib/action_controller/base.rb

Microkernel Design PatternActionController::Base

Cookies, Flash, Verification, RequestForgeryProtection, Streaming, RecordIdentifier, Instrumentation, AbstractController::Callbacks, Rescue ]

MODULES.each do |mod| include mod end

module ActionController class Base < Metal abstract!

MODULES = [ AbstractController::Layouts, AbstractController::Translation,

Helpers, HideActions, UrlFor, Redirecting, Rendering, Renderers::All, ConditionalGet, RackDelegation, SessionManagement, Caching, MimeResponds, PolymorphicRoutes, ImplicitRender,

module ActionController module Helpers

include AbstractController::Helpers ...

Page 47: Decyphering Rails 3

Using Super

module ActionController module Helpers def modules_for_helpers(args) args += all_application_helpers if args.delete(:all) super(args) end ...

rails/actionpack/lib/abstract_controller/helpers.rb

module AbstractController module Helpers # Returns a list of modules, normalized from the acceptable kinds of # helpers with the following behavior: def modules_for_helpers(args) ...

rails/actionpack/lib/action_controller/metal/helpers.rb

helper :all

Page 48: Decyphering Rails 3

alias_method_chain vs super

Page 49: Decyphering Rails 3

ActiveSupport::Concern

Page 50: Decyphering Rails 3

class MyBar cattr_accessor :year include MyCamp def self.unconference puts title + " " + year endend

MyBar.unconference

BarCamp 2010=> nil

module MyCamp

end

module ClassMethods def title "BarCamp" end end

def self.included(klass) klass.class_eval do

end end

self.year = "2010"extend ClassMethods

?

Page 51: Decyphering Rails 3

module MyCamp

end

module ClassMethods def title "BarCamp" end end

module MyCamp

end

module ClassMethods def title "BarCamp" end end

def self.included(klass) klass.class_eval do

end end

self.year = "2010"extend ClassMethods

included do self.year = "2010" end

extend ActiveSupport::Concern

Page 52: Decyphering Rails 3

1. Look for ClassMethods and extend them

When a module with ActiveSupport::Concern gets included into a class, it will:

module MyCamp extend ActiveSupport::Concern module ClassMethods def title "BarCamp" end endend

class MyBar include MyCamp

end}

Page 53: Decyphering Rails 3

2. Look for InstanceMethods and include them

When a module with ActiveSupport::Concern gets included into a class, it will:

module MyCamp extend ActiveSupport::Concern module InstanceMethods def title "BarCamp" end endend

class MyBar include MyCamp

end}

Page 54: Decyphering Rails 3

3. class_eval everything in the include block

When a module with ActiveSupport::Concern gets included into a class, it will:

module MyCamp extend ActiveSupport::Concern included do self.year = "2010" end

end

class MyBar include MyCamp

end}

Page 55: Decyphering Rails 3

4. All included modules get their included hook run on the base class

module MyFoo extend ActiveSupport::Concern

included do self.planet = "Earth" endend

class MyBar include MyCamp

endmodule MyCamp extend ActiveSupport::Concern

include MyFoo

included do self.year = "2010" endend

}

}

class MyBar include MyFoo,MyCamp

end

Previously you had to do

Page 56: Decyphering Rails 3

rails/actionpack/lib/action_controller/base.rb

Microkernel Design PatternActionController::Base

Cookies, Flash, Verification, RequestForgeryProtection, Streaming, RecordIdentifier, Instrumentation, AbstractController::Callbacks, Rescue ]

MODULES.each do |mod| include mod end

module ActionController class Base < Metal abstract!

MODULES = [ AbstractController::Layouts, AbstractController::Translation,

Helpers, HideActions, UrlFor, Redirecting, Rendering, Renderers::All, ConditionalGet, RackDelegation, SessionManagement, Caching, MimeResponds, PolymorphicRoutes, ImplicitRender,

module ActionController module Helpers

include AbstractController::Helpers ...

Page 57: Decyphering Rails 3

module ActionController module Helpers extend ActiveSupport::Concern

include AbstractController::Helpers

included do class_attribute :helpers_path self.helpers_path = [] end ...

rails/actionpack/lib/abstract_controller/helpers.rb

module AbstractController module Helpers extend ActiveSupport::Concern

included do class_attribute :_helpers delegate :_helpers, :to => :'self.class' self._helpers = Module.new end

rails/actionpack/lib/action_controller/metal/helpers.rb

module ActionController class Base < Metal

include Helpers

../action_controller/base.rb

}

}

Page 58: Decyphering Rails 3

ActiveSupport::Concern

1. Look for ClassMethods and extend them

2. Look for InstanceMethods and include them

3. class_eval everything in the include block

4. All included modules get their included hook run on the base class

Page 59: Decyphering Rails 3

Catch/Throw in Bundler

Page 60: Decyphering Rails 3

Dependency Resolution

dependencies

paperclip

sqlite3

mocha rake-compiler

shoulda aws-s3

searchlogic

Depth  First  Search

activerecordConflict

Page 61: Decyphering Rails 3

Control Flow

if  elsif  elseunlessforwhilecase

method  returnmethod  invocation

raise  ..  rescue

catch  ..  throw

Page 62: Decyphering Rails 3

puts "This will not get executed"

catch(:marker) do puts "This will get executed" throw :marker

endputs "This will not get executed"

This will get executed=> nil

catch(:marker) do puts "This will get executed" throw :marker, "hello" puts "This will not get executed"end

This will get executed=> "hello"

begin .. raise ..rescue ..end

Control Flow

Page 63: Decyphering Rails 3

Dependency Resolution

dependencies

paperclip

sqlite3

mocha rake-compiler

shoulda aws-s3

searchlogic

Depth  First  Search

activerecordConflict

Page 64: Decyphering Rails 3

Inside Bundler

retval = catch(requirement.name) do resolve(reqs, activated)end

Bundler/lib/bundler/resolver.rb

when resolving a requirement

parent = current.required_by.last || existing.required_by.last

debug { " -> Jumping to: #{parent.name}" }throw parent.name, existing.required_by.last.name

when activated gem is conflicted to go to initial requirement

parent = current.required_by.last || existing.required_by.last

debug { " -> Jumping to: #{parent.name}" }throw parent.name, existing.required_by.last.name

when gem is not activated, but it’s dependencies conflict

Page 65: Decyphering Rails 3

Catch/Throw in Bundler

Page 66: Decyphering Rails 3

Deciphering Rails 3

Method Compilation

Microkernel Architecture

alias_method_chain vs super

ActiveSupport::Concern

Catch/Throw in Bundler

Page 67: Decyphering Rails 3

# Basi

c implemen

tations fo

r content_

type=, loc

ation=, an

d headers

are

# prov

ided to re

duce the d

ependency

on the Rac

kDelegatio

n module

# in R

enderer an

d Redirect

or.

def co

ntent_type

=(type)

head

ers["Conte

nt-Type"]

= type.to_

s

end

def co

ntent_type

head

ers["Conte

nt-Type"]

end

def lo

cation

head

ers["Locat

ion"]

end

def lo

cation=(ur

l)

head

ers["Locat

ion"] = ur

l

end

def st

atus

@_st

atus

end

def st

atus=(stat

us)

@_st

atus = Rac

k::Utils.s

tatus_code

(status)

end

response_

body=(val)

val.respo

nd_to?(:ea

ch) ? val

: [val]

Don’t be afraid of readi

ng the source

Don’t be afraid of making it your own

Page 68: Decyphering Rails 3

name author URLConstruction Time Again v1ctory_1s_m1ne http://www.flickr.com/photos/v1ctory_1s_m1ne/3416173688/

Microprocesseur Stéfan http://www.flickr.com/photos/st3f4n/2389606236/

chain-of-14-cubes.4 Ardonik http://www.flickr.com/photos/ardonik/3273300715/

(untitled) squacco http://www.flickr.com/photos/squeakywheel/454111821/

up there Paul Mayne http://www.flickr.com/photos/paulm/873641065/

Day 5/365 - Night Terrors Tom Lin :3= http://www.flickr.com/photos/tom_lin/3193080175/

Testing the water The Brit_2 http://www.flickr.com/photos/26686573@N00/2188837324/

High Dive Zhao Hua Xi Shi http://www.flickr.com/photos/elephantonabicycle/4321415975/

Creative Commons

Page 69: Decyphering Rails 3

http://RailsBest.com

Page 70: Decyphering Rails 3

@[email protected]

http://envylabs.com

Slides => http://bit.ly/railstoaster

Спасибо