tdc 2012 - patterns e anti-patterns em ruby

Post on 18-May-2015

2.506 Views

Category:

Technology

5 Downloads

Preview:

Click to see full reader

DESCRIPTION

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.

TRANSCRIPT

Donets BasinMina de carvão-Ucrânia

Genius is the gold in the mine; talent is the miner who works and brings it out.Lady Marguerite Blessington

“”

@akitaonrails

Ruby e Ruby on Rails - 2006

Rails Summit Latin America - 2008

RubyConf Brasil - 2010

Patterns e Anti-Patternsem Ruby

Fabio Akita (@akitaonrails)

Pattern PADRÃO

Pattern PADRÃO

Default

STANDARD

“Pattern”

“Standard”

Christopher Alexander

"...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

ERB

require 'erubis'

def render_erb(filepath) content = File.read(filepath) template = Erubis::Eruby.new(content) context = { title: "Hello World", paragraph: "This is an ERB template." } output = template.evaluate(context)end

puts render_erb("sample.erb")

ERB

<html> <head><title><%= @title %></title></head> <body> <p><%= @paragraph %></p> </body></html>

<html> <head><title>Hello World</title></head> <body> <p>This is an ERB template.</p> </body></html>

Markdown

require 'rdiscount'

def render_markdown(filepath) content = File.read(filepath) template = RDiscount.new(content) output = template.to_htmlend

puts render_markdown("sample.md")

Markdown

# 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>

def render(filepath) case filepath when /erb$/ then render_erb(filepath) when /md$/ then render_markdown(filepath) when /sass$/ then render_sass(filepath) ... endend

puts render("sample.erb")

Strategy

Template Engines supported by Tilt

ENGINE FILE EXTENSIONS REQUIRED LIBRARIES-------------------------- ----------------------- ----------------------------ERB .erb, .rhtml none (included ruby stdlib)Interpolated String .str none (included ruby core)Erubis .erb, .rhtml, .erubis erubisHaml .haml hamlSass .sass haml (< 3.1) or sass (>= 3.1)Scss .scss haml (< 3.1) or sass (>= 3.1)Less CSS .less lessBuilder .builder builderLiquid .liquid liquidRDiscount .markdown, .mkd, .md rdiscountRedcarpet .markdown, .mkd, .md redcarpetBlueCloth .markdown, .mkd, .md blueclothKramdown .markdown, .mkd, .md kramdownMaruku .markdown, .mkd, .md marukuRedCloth .textile redclothRDoc .rdoc rdocRadius .radius radiusMarkaby .mab markabyNokogiri .nokogiri nokogiriCoffeeScript .coffee coffee-script (+ javascript)Creole (Wiki markup) .wiki, .creole creoleWikiCloth (Wiki markup) .wiki, .mediawiki, .mw wikiclothYajl .yajl yajl-ruby

ERB and Markdown through Tilt

require 'tilt'

template = Tilt.new("sample.erb")context = { title: "Hello World", paragraph: "This is an ERB template." }output = template.render(context)

puts output

template = Tilt.new("sample.md")output = template.render

puts output

Same example as previously

require 'tilt'

template = Tilt::ErubisTemplate.new("sample.erb")context = { title: "Hello World", paragraph: "This is an ERB template." }output = template.render(context)

puts output

template = Tilt::RDiscountTemplate.new("sample.md")output = template.render

puts output

tilt.rb

module Tilt ... @preferred_mappings = Hash.new @template_mappings = Hash.new { |h, k| h[k] = [] }

# Hash of template path pattern => template implementation class mappings. def self.mappings @template_mappings end ... # Register a template implementation by file extension. def self.register(template_class, *extensions) if template_class.respond_to?(:to_str) # Support register(ext, template_class) too extensions, template_class = [template_class], extensions[0] end

extensions.each do |ext| ext = normalize(ext) mappings[ext].unshift(template_class).uniq! end end ...

Tilt#register

require 'tilt/string'register StringTemplate, 'str'

require 'tilt/erb'register ERBTemplate, 'erb', 'rhtml'register ErubisTemplate, 'erb', 'rhtml', 'erubis'

require 'tilt/haml'register HamlTemplate, 'haml'

require 'tilt/css'register SassTemplate, 'sass'register ScssTemplate, 'scss'register LessTemplate, 'less'

...

Tilt#[]

> Tilt["erb"]=> Tilt::ErubisTemplate

> Tilt["md"]=> Tilt::RDiscountTemplate

> Tilt["haml"]=> Tilt::HamlTemplate

> Tilt['coffee']=> Tilt::CoffeeScriptTemplate

Tilt overrided #new

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

Factory

Tilt::Template Lint

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 endend

Tilt::RedClothTemplate Implementation

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 endend

Adapter

Simple Web

Mongrel

Thin

Webrick

Rails

Sinatra

Ramaze

CGI

Simple Web

Mongrel

Thin

Webrick

Rails

Sinatra

Ramaze

CGI

Simple Web

Mongrel

Thin

Webrick

Rails

Sinatra

Ramaze

CGIRack

Minimal Rack Compliant

class HelloWorld def call(env) [200, {"Content-Type" => "text/plain"}, ["Hello world!"]] endend

hello_world = ->(env) { [200, {"Content-Type" => "text/plain"}, ["Hello world!"]] }

rackup app.ru

hello_world = ->(env) { [200, {"Content-Type" => "text/html"}, ["<h1>Hello world!</h1>"]] }

use Rack::ContentType, "text/html"use Rack::ShowExceptionsuse Rack::Auth::Basic, "Rack Demo" do |username, password| 'secret' == passwordend

# Setup Rackrun Rack::URLMap.new( { "/hello" => hello_world, "/" => Rack::File.new( "index.html" )} )

rackup app.ru

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 endend

rake middleware (Rails)

use ActionDispatch::Staticuse Rack::Lock...use Rack::Runtimeuse Rack::MethodOverrideuse ActionDispatch::RequestIduse Rails::Rack::Loggeruse ActionDispatch::ShowExceptionsuse ActionDispatch::DebugExceptionsuse ActionDispatch::RemoteIpuse ActionDispatch::Reloaderuse ActionDispatch::Callbacksuse ActiveRecord::ConnectionAdapters::ConnectionManagementuse ActiveRecord::QueryCacheuse ActionDispatch::Cookiesuse ActionDispatch::Session::CookieStoreuse ActionDispatch::Flashuse ActionDispatch::ParamsParseruse ActionDispatch::Headuse Rack::ConditionalGetuse Rack::ETaguse ActionDispatch::BestStandardsSupportuse Warden::Manageruse OmniAuth::Strategies::Twitteruse OmniAuth::Strategies::Facebookrun Rubyconf2012::Application.routes

Chain of Responsibility

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"; endend

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"; endend

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"; endend

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"; endend

State

class SaleOrder attr_accessor :items, :value, :checkout_date def initialize(*args) @items, @value, @checkout_date = args endend

sale = SaleOrder.new( ['Biscuit', 'Cheese'], 15.0, "2012-07-07")

require 'money'require 'time'class SaleOrder attr_reader :value, :checkout_date def initialize(options = {}) @items, @value, @checkout_date = [], Money.new(0.0, "USD"), nil self.items = options[:items] || [] self.value = options[:value] || 0.0 self.checkout_date = options[:checkout_date] end def items=(items); @items += items.dup; end def items(index); @items[index]; end def value=(value); @value = Money.new(value.to_f, "USD"); end def checkout_date=(date) @checkout_date = Date.parse(date) if date endend

sale = SaleOrder.new(items: ['Biscuit', 'Cheese'], value: 15.0, checkout_date: "2012-07-07")

Primitive Obsession

http://u.akita.ws/avdi-null

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 nilend

ActiveSupport - Try

class Object def try(*a, &b) if a.empty? && block_given? yield self else public_send(*a, &b) end endend

class NilClass def try(*args) nil endend

http://u.akita.ws/avdi-null

require 'active_support/core_ext/object/try'

def slug(title) title.try(:strip).try(:downcase). try(:tr_s, '^[a-z0-9]', '-')end

<%= title ? title.downcase : "" %>

<%= title.try(:downcase) %>

http://u.akita.ws/avdi-null

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 tap; self; end def to_value; nil; endend

def Maybe(value) value.nil? ? NullObject.new : valueend

class Object def to_value self endend

http://u.akita.ws/avdi-null

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

git clone git://github.com/akitaonrails/null.gitcd nullgem build null.gemspecgem install null-0.1.gem

> require 'null'> Maybe(someobj).foo.bar.something#=> null

> (100.0 / (NULL * 15.5) - 150)#=> null

object = Maybe(someobj)if object.truthy? # something usefulend

Null Object

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" )

> 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>"

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" )

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)

> 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"]

# originalorder = SalesOrder.new(["Bacon", "Cheese"], 10.0, "John Doe" )decorated_order = SalesOrderDecorator.new(order)

# newclass 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

Decorator/Presenter

Strategy

Factory

Adapter (Proxy, Bridge)

Chain of Responsibility

State

Primitive Obsession

Null Object

Decorator (Presenter)

contact@codeminer42.com.br

www.codeminer42.com.br+55 11 3729 14 22

slideshare.net/akitaonrails

contact@codeminer42.com.br

www.codeminer42.com.br+55 11 3729 14 22

slideshare.net/akitaonrails

MUITO OBRIGADO

top related