decyphering rails 3
DESCRIPTION
Код ядра Rails был существенно улучшен с выпуском Rails 3, в основном из-за использования эффективных паттернов проектирования. Мы разберем некоторые из ключевых изменений, которые привели к улучшению качества кода, и на их примере научимся применять такие техники к своему собственному коду.Вот некоторые из таких техник:Компилирование методов vs method_missingМикроядерная архитектураalias_method_chain vs superActiveSupport::ConcernCatch/Throw в BundlerСлушатели намного улучшат свои знания о некоторых сложных паттернах проектирования в Ruby и станут лучше разбираться во внутренностях Rails 3.TRANSCRIPT
Deciphering Rails 3with Gregg Pollack
# 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
Deciphering Rails 3
Method Compilation
Microkernel Architecture
alias_method_chain vs super
ActiveSupport::Concern
Catch/Throw in Bundler
let’s test the water
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
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'
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
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
Method Compilation
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
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
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]
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]
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!
ruby-profUsing Method Missing
Using Method Compliation
Yehuda’s first commit
rails/actionpack/lib/abstract_controller/metal/mime_responds.rb
rails/actionpack/lib/abstract_controller/collector.rb
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
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?
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
Method Compilation
let’s test the water
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
Microkernel Architecture
A refactor of ActionController in ActionPack
AbstractController
Provides A Low Level Interface for making Customized Controller Base classes.
Carl Lerche
Microkernel Design PatternOld ActionController Stack
ActionDispatchLayouts
Logger
Callbacks
Renderer
url_forhttp_auth
helpers
MimeResponds
CookiesSessionRouter
Microkernel Design PatternOld ActionController Stack
Layouts
Logger
Callbacks
Renderer
url_forhttp_auth
helpers
MimeResponds
CookiesSession
AbstractControllerAssignsCallbacksCollectorHelpersLayoutsLoggerRenderingTranslationViewPaths
AssignsCallbacksCollectorHelpersLayoutsLoggerRenderingTranslationViewPaths
Microkernel Design Pattern
AbstractController
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
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
Microkernel Design Pattern
AbstractController::BaseActionController::Metal <<
contains just enough code to get a valid Rack application from a controller
AbstractController::BaseActionMailer::Base <<
AssignsBaseCallbacksCollectorHelpersLayoutsLoggerRenderingTranslationViewPaths
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
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
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
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
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 ...
ActionController::Metal
AbstractController::Base
AssignsCallbacksCollectorHelpersLayoutsLoggerRenderingTranslationViewPaths
ActionController::Base
CookiesExceptionsFlashHelpersRedirectingRenderingResponderUrlFor..... (lots more)
abstract
abstract
abstract
AbstractController Namespace
ActionController Namespace
includes
Microkernel Architecture
# 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
let’s test the water
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
alias_method_chain vs super
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
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
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
A refactor of ActionController in ActionPack
AbstractController
Provides A Low Level Interface for making Customized Controller Base classes.
Carl Lerche
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 ...
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
alias_method_chain vs super
ActiveSupport::Concern
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
?
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
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}
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}
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}
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
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 ...
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
}
}
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
Catch/Throw in Bundler
Dependency Resolution
dependencies
paperclip
sqlite3
mocha rake-compiler
shoulda aws-s3
searchlogic
Depth First Search
activerecordConflict
Control Flow
if elsif elseunlessforwhilecase
method returnmethod invocation
raise .. rescue
catch .. throw
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
Dependency Resolution
dependencies
paperclip
sqlite3
mocha rake-compiler
shoulda aws-s3
searchlogic
Depth First Search
activerecordConflict
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
Catch/Throw in Bundler
Deciphering Rails 3
Method Compilation
Microkernel Architecture
alias_method_chain vs super
ActiveSupport::Concern
Catch/Throw in Bundler
# 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
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
http://RailsBest.com