Palestra apresentada no The Developers Conference 2012 em São Paulo. Explicação sobre Patterns e Anti-Patterns em Ruby para quem está iniciando a aprender a linguagem.
Unleash Your Potential - Namagunga Girls Coding Club
Genius is the gold in the mine
1. “ Genius is the gold in the mine; talent is
the miner who works and brings it out. ”
Lady Marguerite Blessington
Donets Basin
Mina de carvão-Ucrânia
2.
3. @akitaonrails
Ruby e Ruby on Rails - 2006
Rails Summit Latin America - 2008
RubyConf Brasil - 2010
4.
5.
6.
7. Patterns e
Anti-Patterns
em Ruby
Fabio Akita (@akitaonrails)
16. "...each pattern represents our current best
guess ... to solve the problem presented. ..., the
patterns are still hypotheses, all ... of them -and
are therefore all tentative, all free to evolve under
the impact of new experience and observation"
—Christopher Alexander, A Pattern Language, p. xv
20. # Hello World
This is a Markdown example
* [Markdown](http://daringfireball.net/projects/markdown/
syntax/)
<h1>Hello World</h1>
<p>This is a Markdown example</p>
<ul>
<li><a href="http://daringfireball.net/projects/markdown/
syntax/">Markdown</a></li>
</ul>
Markdown
21. def render(filepath)
case filepath
when /erb$/ then render_erb(filepath)
when /md$/ then render_markdown(filepath)
when /sass$/ then render_sass(filepath)
...
end
end
puts render("sample.erb")
30. module Tilt
...
# Create a new template for the given
# file using the file's extension
# to determine the the template mapping.
def self.new(file, line=nil, options={}, &block)
if template_class = self[file]
template_class.new(file, line, options, &block)
else
fail "No template engine registered for
#{File.basename(file)}"
end
end
Tilt overrided #new
32. module Tilt
# Base class for template implementations.
# Subclasses must implement the #prepare method and one
# of the #evaluate or #precompiled_template methods.
class Template
...
def render(scope=Object.new, locals={}, &block)
evaluate scope, locals || {}, &block
end
protected
def prepare; ... end
def evaluate(scope, locals, &block); ... end
def precompiled(locals); ... end
def precompiled_template(locals); ... end
def precompiled_preamble(locals); ... end
def precompiled_postamble(locals); ''; end
def compiled_method(locals_keys); ... end
end
end
Tilt::Template Lint
33. module Tilt
# RedCloth implementation. See:
# http://redcloth.org/
class RedClothTemplate < Template
def self.engine_initialized?
defined? ::RedCloth
end
def initialize_engine
require_template_library 'redcloth'
end
def prepare
@engine = RedCloth.new(data)
@output = nil
end
def evaluate(scope, locals, &block)
@output ||= @engine.to_html
end
end
end
Tilt::RedClothTemplate Implementation
38. class HelloWorld
def call(env)
[200,
{"Content-Type" => "text/plain"},
["Hello world!"]]
end
end
hello_world = ->(env) {
[200,
{"Content-Type" => "text/plain"},
["Hello world!"]]
}
Minimal Rack Compliant
39. hello_world = ->(env) {
[200,
{"Content-Type" => "text/html"},
["<h1>Hello world!</h1>"]]
}
use Rack::ContentType, "text/html"
use Rack::ShowExceptions
use Rack::Auth::Basic, "Rack Demo" do |username, password|
'secret' == password
end
# Setup Rack
run Rack::URLMap.new( {
"/hello" => hello_world,
"/" => Rack::File.new( "index.html" )
} )
rackup app.ru
40. module Rack
class ContentType
include Rack::Utils
def initialize(app, content_type = "text/html")
@app, @content_type = app, content_type
end
def call(env)
status, headers, body = @app.call(env)
headers = Utils::HeaderHash.new(headers)
unless STATUS_WITH_NO_ENTITY_BODY.include?(status)
headers['Content-Type'] ||= @content_type
end
[status, headers, body]
end
end
end
rackup app.ru
41. use ActionDispatch::Static
use Rack::Lock
...
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use ActionDispatch::Head
use Rack::ConditionalGet
use Rack::ETag
use ActionDispatch::BestStandardsSupport
use Warden::Manager
use OmniAuth::Strategies::Twitter
use OmniAuth::Strategies::Facebook
run Rubyconf2012::Application.routes
rake middleware (Rails)
43. class Relationship
attr_accessor :state
def initialize
@state = :dating
end
def get_married
make_vows
@state = :married
eat_wedding_cake
end
def get_divorced
@state = :divorced
end
def make_vows; "I do"; end
def eat_wedding_cake; "Yummy"; end
end
44. class Relationship
attr_accessor :state
def initialize
@state = :dating
end
def get_married
raise "Must date before marry" unless @state == :dating
make_vows
@state = :married
eat_wedding_cake
end
def get_divorced
raise "Must be married before divorce" unless @state == :married
@state = :divorced
end
def make_vows; "I do"; end
def eat_wedding_cake; "Yummy"; end
end
45.
46. class Relationship
include AASM
aasm do
state :dating, initial: true
state :married
state :divorced
event :get_married,
:before => :make_vows,
:after => :eat_wedding_cake do
transitions from: [:dating], to: :married
end
event :get_divorced do
transitions from: [:married], to: :divorced
end
end
def make_vows; "I do"; end
def eat_wedding_cake; "Yummy"; end
end
47. class Relationship
attr_accessor :dating, :married, :divorced
def initialize
@dating, @married, @divorced = true, false, false
end
def get_married
raise "Must date before marry" unless dating
make_vows
@dating, @married = false, true
eat_wedding_cake
end
def get_divorced
raise "Must be married before divorce" unless married
@married, @divorced = false, true
end
def make_vows; "I do"; end
def eat_wedding_cake; "Yummy"; end
end
52. def slug(title)
# 1
if title.nil?
title.strip.downcase.tr_s('^[a-z0-9]', '-')
end
# 2
title.strip.downcase.tr_s('^[a-z0-9]', '-') if title
# 3
(title || "").strip.downcase.tr_s('^[a-z0-9]', '-')
# 4
title.strip.downcase.tr_s('^[a-z0-9]', '-') rescue nil
end
http:/
/u.akita.ws/avdi-null
53. class Object
def try(*a, &b)
if a.empty? && block_given?
yield self
else
public_send(*a, &b)
end
end
end
class NilClass
def try(*args)
nil
end
end
ActiveSupport - Try
55. class NullObject
def method_missing(*args, &block)
self
end
def to_a; []; end
def to_s; ""; end
def to_f; 0.0; end
def to_i; 0; end
def Maybe(value)
value.nil? ? NullObject.new : value
def tap; self; end
end
def to_value; nil; end
end
class Object
def to_value
self
end
end
http:/
/u.akita.ws/avdi-null
56. def slug(title)
Maybe(title).strip.downcase.tr_s('^[a-z0-9]', '-')
end
def slug(title)
title = Maybe(title)
if title.to_value
# do something useful
end
title.strip.downcase.tr_s('^[a-z0-9]', '-')
end
http:/
/u.akita.ws/avdi-null
59. class SalesOrder < Struct.new(:products, :total, :buyer_name)
def html_receipt
html_items = products.inject("") do |html, item|
html += "<li>#{item}</li>"
end
html = %{<h1>Thanks for the Purchase #{buyer_name}!</h1>
<p>You purchased:</p>
<ul>
#{html_items}
</ul>
<p>Total: $#{total}</p>}
end
end
order = SalesOrder.new(["Bacon", "Cheese"], 10.0, "John Doe" )
60. > order.html_receipt
=> "<h1>Thanks for the Purchase John Doe!</h1>
<p>You purchased:</p>
<ul>
<li>Bacon</li><li>Cheese</li>
</ul>
<p>Total: $10.0</p>"
61. class SalesOrder < Struct.new(:products, :total, :buyer_name)
def html_receipt
html_items = products.inject("") do |html, item|
html += "<li>#{item}</li>"
end
html = %{<h1>Thanks for the Purchase #{buyer_name}!</h1>
<p>You purchased:</p>
<ul>
#{html_items}
</ul>
<p>Total: $#{total}</p>}
end
end
order = SalesOrder.new(["Bacon", "Cheese"], 10.0, "John Doe" )
62. class SalesOrder < Struct.new(:products, :total, :buyer_name)
end
class SalesOrderDecorator < SimpleDelegator
def html_receipt
html_items = products.inject("") do |html, item|
html += "<li>#{item}</li>"
end
html = %{<h1>Thanks for the Purchase #{buyer_name}!</h1>
<p>You purchased:</p>
<ul>
#{html_items}
</ul>
<p>Total: $#{total}</p>}
end
end
order = SalesOrder.new(["Bacon", "Cheese"], 10.0, "John Doe" )
decorated_order = SalesOrderDecorator.new(order)
63. > decorated_order.html_receipt
=> "<h1>Thanks for the Purchase John Doe!</h1>
<p>You purchased:</p>
<ul>
<li>Bacon</li><li>Cheese</li>
</ul>
<p>Total: $10.0</p>"
> decorated_order.total
=> 10.0
> decorated_order.products
=> ["Bacon", "Cheese"]
64. # original
order = SalesOrder.new(["Bacon", "Cheese"], 10.0, "John Doe" )
decorated_order = SalesOrderDecorator.new(order)
# new
class SalesOrderDecorator < SimpleDelegator
def initialize(*args)
if args.first.is_a?(SalesOrder)
super(args.first)
else
order = SalesOrder.new(*args)
super(order)
end
end
...
end
decorated_order = SalesOrderDecorator.new(["Bacon", "Cheese"], 10.0,
"John Doe" )
decorated_order.html_receipt