Код ядра Rails был существенно улучшен с выпуском Rails 3, в основном из-за использования эффективных паттернов проектирования. Мы разберем некоторые из ключевых изменений, которые привели к улучшению качества кода, и на их примере научимся применять такие техники к своему собственному коду.
Вот некоторые из таких техник:
Компилирование методов vs method_missing
Микроядерная архитектура
alias_method_chain vs super
ActiveSupport::Concern
Catch/Throw в Bundler
Слушатели намного улучшат свои знания о некоторых сложных паттернах проектирования в Ruby и станут лучше разбираться во внутренностях Rails 3.
2. asi
c to
r edi
# B ded an d R _s
r ovi er er e .to
# p end pe) = typ
R ty
in e =( e "]
#
t_t
yp Typ
ten te nt-
con [" Con
def d ers
hea
end "]
ype ype
t_t t-T
on ten on ten
f c [" C
source
de rs
ade
ng the
he
end
raid of readi
ion ion
"]
be af
at at
loc Loc
Don’t def ["
d ers
hea
rl
e nd (u rl) = u
on= n"]
a ti ca tio
loc "Lo
def s[
der
Don’t be afraid of making it your own
hea
end
us tus
)
s tat sta
def ta tus co de(
@_s at us_
tu s) ls .st
end sta Uti
=( :: l]
a tus R ack [va
st = :
def t a tus ? val
@_s ) ch)
val ea
end od y =( to ?(:
ns e_b po nd_
e spo . res
3. Deciphering Rails 3
Method Compilation
Microkernel Architecture
alias_method_chain vs super
ActiveSupport::Concern
Catch/Throw in Bundler
5. Basics def city
return @city
end
class SimplePrint
attr_accessor :city
def city=(val)
def initialize(val) @city = val
self.city = val end
end
def method_missing(meth)
s = SimplePrint.new("moscow")
puts "#{meth} #{city}"
end
end
s.toaster
> s = SimplePrint.new("moscow")
=> #<SimplePrint:0x000001020013d0 @city="moscow">
> s.toaster
toaster moscow
=> nil
7. instan ce_eval
class Test t = Test.new
def print t.print
puts "Hello" Hello
end
end
Test.new.instance_eval do
t = Test.new print
t.instance_eval do end
def print2
Hello
puts "Hello2"
b6 0>
end 0 0124
t :0 x1
end r# <Tes
Test.new.print2 rin t 2’ fo
eth od ‘p
t.print2 d m
u nd efine
r:
dErro
Hello2 M etho
No
8. c lass_eval
class Test class Test
end end
class Test Test.class_eval do
def print def print
puts "Ruby5" puts "Ruby5"
end end
end end
Test.new.print Test.new.print
Ruby5 Ruby5
10. s = SimplePrint.new [:push_it, :pop_it, :top_it]
output
s.push_it
s.pop_it => “called push_it”
s.top_it => “called pop_it”
=> “called top_it”
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
end
end
11. s = SimplePrint.new [:push_it, :pop_it, :top_it]
30000.times do
s.push_it
s.pop_it
s.top_it
end
Using Method Missing 5 Seconds
12. s = SimplePrint.new [:push_it, :pop_it, :top_it]
class SimplePrint class BetterPrint
attr_accessor :actions
def initialize(arr)
def initialize(arr) arr.each do |meth|
self.actions = arr
end instance_eval <<-RUBY
def #{meth}
def print(meth) print("#{meth}")
puts "called #{meth}" end
end RUBY
def method_missing(meth) end
print(meth) if actions.include? meth end
end
end def print(meth)
puts "called #{meth}"
end
end
13. [:push_it, :pop_it, :top_it]
class BetterPrint
def initialize(arr) def push_it
arr.each do |meth| print("push_it")
end
instance_eval <<-RUBY
def #{meth}
print("#{meth}")
end def pop_it
RUBY print("pop_it")
end
end
end
def print(meth) def top_it
puts "called #{meth}" print("top_it")
end end
end
14. s = SimplePrint.new [:push_it, :pop_it, :top_it]
30000.times do
s.push_it
s.pop_it
s.top_it
end
Using Method Missing 5 Seconds
Using Instance Eval 3.5 Seconds
30 % Faster!
22. includin g modules
module PrintMeth
def print
puts "Ruby5"
end
end
As Instance Method As Class Method
class Test class Test
include PrintMeth extend PrintMeth
end end
Test.new.print Test.print
Ruby5 Ruby5
24. AbstractController
A refactor of ActionController in ActionPack
Provides
A
Low
Level
Interface
for
making
Customized
Controller
Base
classes.
Carl Lerche
25. Old ActionController Stack Microkernel Design Pattern
Cookies ActionDispatch
S
Routeression
Layout
s
Logger
MimeRes
ponds
s
Cal lback
Rendere
r
url_for
http_
auth
helpers
26. Old ActionController Stack Microkernel Design Pattern
Cookies
Assigns AbstractController
Sessio
Layout
Callbacks
n
s
Collector
Logger
MimeRes
Helpers
ponds
Layouts
Logger ks
ac
Callb
Renderinge
R nderer
Translation
url_for
ViewPaths
http_
auth
helpers
28. The MicroKernel
rails/actionpack/lib/abstract_controller/base.rb
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)
end
end
29. AbstractController::Base
The microkernel with the bare minimum needed for dispatching
Assigns
before_filter :authorized
Callbacks after_filter :send_email
Collector around_filter :benchmark
helper :custom
Helpers helper_method :current_user
layout 'custom'
Layouts layout :symbol
Logger layout false
layout nil
Rendering
Translation
ViewPaths
Modules that can be included to add functionality on top of base
30. Microkernel Design Pattern
ActionController::Metal << AbstractController::Base
Assigns
Base
Callbacks
contains just enough code to get a valid
Collector
Rack application from a controller
Helpers
Layouts
Logger
Rendering
Translation
ViewPaths
ActionMailer::Base << AbstractController::Base
31. Microkernel Design Pattern
ActionController::Metal << AbstractController::Base
app/controllers/hello_controller.rb
class HelloController < ActionController::Metal
def index
self.response_body = "Hello World!"
end
end
config/routes.rb
match 'hello', :to => HelloController.action(:index)
32. ActionController::Base Microkernel Design Pattern
rails/actionpack/lib/action_controller/metal.rb
module ActionController
class Metal < AbstractController::Base
abstract!
request 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
end
end
33. Cookies
Exceptions
Flash
ActionController::Metal Helpers
Redirecting
Rendering
redirect_to post_url(@post) Responder
UrlFor
class PeopleController < ApplicationController
..... (lots more)
respond_to :html, :xml, :json
def index
@people = Person.find(:all)
Assigns
respond_with(@people)
end Callbacks
end Collector
Helpers
AbstractController::Base Layouts
Logger
Rendering
Translation
ViewPaths
34. Microkernel Design Pattern
app/controllers/hello_controller.rb
class HelloController < ActionController::Metal
include ActionController::Rendering
append_view_path "#{Rails.root}/app/views"
def index
render "hello/index"
end
end
app/controllers/hello_controller.rb
class HelloController < ActionController::Metal
include ActionController::Redirecting
include Rails.application.routes.url_helpers
def index
redirect_to root_url
end
end
35. ActionController::Base Microkernel Design Pattern
rails/actionpack/lib/action_controller/base.rb
module ActionController
class Base < Metal
abstract!
MODULES = [
AbstractController::Layouts,
AbstractController::Translation,
module ActionController Cookies,
Helpers, Flash,
module Helpers
HideActions, Verification,
UrlFor, RequestForgeryProtection,
include AbstractController::Helpers
Streaming,
Redirecting,
... RecordIdentifier,
Rendering,
Renderers::All, Instrumentation,
ConditionalGet, AbstractController::Callbacks,
RackDelegation, Rescue
SessionManagement, ]
Caching,
MimeResponds, MODULES.each do |mod|
PolymorphicRoutes, include mod
ImplicitRender, end
38. asi
c to
r edi
# B ded an d R _s
r ovi er er e .to
# p end pe) = typ
R ty
in e =( e "]
#
t_t
yp Typ
ten te nt-
con [" Con
def d ers
hea
end "]
ype ype
t_t t-T
on ten on ten
de f c rs [" C
he ade
end "]
ion ion
source
at at
loc Loc
ing the
def ["
ers
f read
d
hea
fraid o
rl
rl) = u
t be a
e nd (u n"]
Don’ ti on= tio
loc
a ca
s[ "Lo
def der
hea
end
us tus
)
s tat sta
def ta tus co de(
@_s at us_
tu s) ls .st
end sta Uti
=( :: l]
a tus R ack [va
st = :
def t a tus ? val
@_s ) ch)
val ea
end od y =( to ?(:
ns e_b po nd_
e spo . res
40. Both instance and class methods
module PrintMeth class Test
def self.included(base) include PrintMeth
end
?
base.class_eval do
base.extend(ClassMethods)
extend ClassMethods
end Test.new.print_podcast
end Test.print_company
def print_podcast Ruby5
puts "Ruby5" Envy Labs
end
module ClassMethods
def print_company
puts "Envy Labs"
end
end
end
42. Can’t modify parent class
class GenericUser mr
def name name_ without_
"Gregg Pollack"
end
end module Mr
def name_with_mr na
me
"Mr. " + name_without_mr
end
class User < GenericUser
include Mr def self.included(base)
end base.class_eval do
alias :name_without_mr :name
puts User.new.name alias :name :name_with_mr
end
end
output
=> “Gregg Pollack” end
output wanted
=> “Mr. Gregg Pollack”
43. Can’t modify parent class
class GenericUser mr
def name name_ without_
"Gregg Pollack"
end
end module Mr
def name_with_mr na
me
"Mr. " + name_without_mr
end
class User < GenericUser
include Mr def self.included(base)
end base.class_eval do
alias :name_without_mr :name
puts User.new.name alias :name :name_with_mr
end
end
output
=> “Gregg Pollack” end
output wanted alias_method_chain :name, :mr
=> “Mr. Gregg Pollack”
44. class GenericUser
def name
"Gregg Pollack"
end
end module Mr
def name
"Mr. " + super
class User < GenericUser end
include Mr
end end
puts User.new.name
output
=> “Gregg Pollack”
output wanted
=> “Mr. Gregg Pollack”
45. AbstractController
A refactor of ActionController in ActionPack
Provides
A
Low
Level
Interface
for
making
Customized
Controller
Base
classes.
Carl Lerche
46. ActionController::Base Microkernel Design Pattern
rails/actionpack/lib/action_controller/base.rb
module ActionController
class Base < Metal
abstract!
MODULES = [
AbstractController::Layouts,
AbstractController::Translation,
module ActionController Cookies,
Helpers, Flash,
module Helpers
HideActions, Verification,
UrlFor, RequestForgeryProtection,
include AbstractController::Helpers
Streaming,
Redirecting,
... RecordIdentifier,
Rendering,
Renderers::All, Instrumentation,
ConditionalGet, AbstractController::Callbacks,
RackDelegation, Rescue
SessionManagement, ]
Caching,
MimeResponds, MODULES.each do |mod|
PolymorphicRoutes, include mod
ImplicitRender, end
47. Using Super
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
module ActionController
helper :
module Helpers all
def modules_for_helpers(args)
args += all_application_helpers if args.delete(:all)
super(args)
end
...
50. class MyBar module MyCamp
cattr_accessor :year
def self.included(klass)
include MyCamp klass.class_eval do
self.year = "2010"
def self.unconference extend ClassMethods
?
puts title + " " + year end
end end
end
module ClassMethods
MyBar.unconference def title
"BarCamp"
end
BarCamp 2010
end
=> nil
end
51. module MyCamp module MyCamp
extend ActiveSupport::Concern
def self.included(klass)
klass.class_eval do included do
self.year = "2010" self.year = "2010"
extend ClassMethods end
end
module ClassMethods
end
def title
"BarCamp"
module ClassMethods
end
def title
end
"BarCamp"
end
end
end
end
52. When a module with ActiveSupport::Concern
gets included into a class, it will:
1. Look for ClassMethods and extend them
module MyCamp
extend ActiveSupport::Concern
class MyBar
include MyCamp
module ClassMethods
}
def title
end
"BarCamp"
end
end
end
53. When a module with ActiveSupport::Concern
gets included into a class, it will:
2. Look for InstanceMethods and include them
module MyCamp
extend ActiveSupport::Concern
class MyBar
include MyCamp
module InstanceMethods
}
def title
end
"BarCamp"
end
end
end
54. When a module with ActiveSupport::Concern
gets included into a class, it will:
3. class_eval everything in the include block
module MyCamp
extend ActiveSupport::Concern class MyBar
include MyCamp
}
included do
self.year = "2010" end
end
end
55. 4. All included modules get their included hook
run on the base class
module MyFoo
extend ActiveSupport::Concern
}
included do
self.planet = "Earth"
end class MyBar
end
include MyCamp
end
module MyCamp
extend ActiveSupport::Concern
include MyFoo
Previously you had to do
}
class MyBar
included do
self.year = "2010" include MyFoo,MyCamp
end
end end
56. ActionController::Base Microkernel Design Pattern
rails/actionpack/lib/action_controller/base.rb
module ActionController
class Base < Metal
abstract!
MODULES = [
AbstractController::Layouts,
AbstractController::Translation,
module ActionController Cookies,
Helpers, Flash,
module Helpers
HideActions, Verification,
UrlFor, RequestForgeryProtection,
include AbstractController::Helpers
Streaming,
Redirecting,
... RecordIdentifier,
Rendering,
Renderers::All, Instrumentation,
ConditionalGet, AbstractController::Callbacks,
RackDelegation, Rescue
SessionManagement, ]
Caching,
MimeResponds, MODULES.each do |mod|
PolymorphicRoutes, include mod
ImplicitRender, end
57. 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 ../action_controller/base.rb
end
module ActionController
class Base < Metal
rails/actionpack/lib/action_controller/metal/helpers.rb
include Helpers
module ActionController
module Helpers
extend ActiveSupport::Concern
include AbstractController::Helpers
}
included do
class_attribute :helpers_path
self.helpers_path = []
end
...
58. upport::C oncern
ActiveS
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
61. Control Flow
if
method
return
elsif
method
invocation
else
unless
for raise
..
rescue
while
case catch
..
throw
62. Control Flow
catch(:marker) do
puts "This will get executed"
throw :marker
puts "This will not get executed"
end begin
..
This will get executed
=> nil raise
..
catch(:marker) do rescue
puts "This will get executed" ..
throw :marker, "hello" end
puts "This will not get executed"
end
This will get executed
=> "hello"
64. Inside Bundler
Bundler/lib/bundler/resolver.rb
when resolving a requirement
retval = catch(requirement.name) do
resolve(reqs, activated)
end
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
parent = current.required_by.last || existing.required_by.last
debug { " -> Jumping to: #{parent.name}" }
throw parent.name, existing.required_by.last.name
66. Deciphering Rails 3
Method Compilation
Microkernel Architecture
alias_method_chain vs super
ActiveSupport::Concern
Catch/Throw in Bundler
67. asi
c to
r edi
# B ded an d R _s
r ovi er er e .to
# p end pe) = typ
R ty
in e =( e "]
#
t_t
yp Typ
ten te nt-
con [" Con
def d ers
hea
end "]
ype ype
t_t t-T
on ten on ten
f c [" C
source
de rs
ade
ng the
he
end
raid of readi
ion ion
"]
be af
at at
loc Loc
Don’t def ["
d ers
hea
rl
e nd (u rl) = u
on= n"]
a ti ca tio
loc "Lo
def s[
der
Don’t be afraid of making it your own
hea
end
us tus
)
s tat sta
def ta tus co de(
@_s at us_
tu s) ls .st
end sta Uti
=( :: l]
a tus R ack [va
st = :
def t a tus ? val
@_s ) ch)
val ea
end od y =( to ?(:
ns e_b po nd_
e spo . res
68. Creative Commons
name author URL
Construction 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/