Ruby Além dos Trilhos

Ruby Além dos Trilhos

O Rails ao longo dos últimos anos se tornou uma plataforma bastante sólida e respeitada. E boa parte desse sucesso é atribuido a uma comunidade inovadora, vibrante e bem "opinionada". Como consequência, hoje temos um vasto leque de bibliotecas, padrões de projeto, convenções e boas práticas à nossa mão.

Porém, quando não somos criteriosos, todos esses recursos podem facilmente trazer uma grande dor-de-cabeça: o inferno da manutenção. Complexidade desnecessária, alto acoplamento, indireção, todos esses fatores acabam nos atrapalhando quando estamos evoluindo nosso software. Nesta palestra visitaremos alguns exemplos reais desses problemas e aprenderemos como enxergar além para evitá-los, trazendo de volta a tranquilidade ao nosso dia-a-dia de desenvolvimento.

Por João Britto, para o RS on Rails 2013.

7c12adb8b5521c060ab4630360a4fa27?s=128

Plataformatec

October 19, 2013
Tweet

Transcript

  1. github.com/britto João Britto twitter.com/noteu

  2. None
  3. RS on Rails 2013 Ruby Além dos Trilhos

  4. None
  5. A dynamic, open source programming language with a focus on

    simplicity and productivity. It has an elegant syntax that is natural to read and easy to write.
  6. A dynamic, open source programming language with a focus on

    simplicity and productivity. It has an elegant syntax that is natural to read and easy to write.
  7. "RS on Rails? bah, que tri!".upcase #=> RS ON RAILS?

    BAH, QUE TRI!
  8. class Integer # Check whether the integer is evenly divisible

    by the argument. # # 0.multiple_of?(0) #=> true # 6.multiple_of?(5) #=> false # 10.multiple_of?(2) #=> true def multiple_of?(number) number != 0 ? self % number == 0 : zero? end end
  9. Monkey Patch

  10. Freedom Patch

  11. module I18NExtensions def t(options = {}) I18n.t self, options end

    end Symbol.send :include, I18NExtensions
  12. # Sem monkey patch t :save # Com monkey patch

    :save.t
  13. None
  14. class Author < ActiveRecord::Base has_many :authorships has_many :books, :through =>

    :authorships end class Authorship < ActiveRecord::Base belongs_to :author belongs_to :book end @author = Author.first # sem has_many through @author.authorships.collect { |a| a.book } # com has_many through @author.books
  15. class Author < ActiveRecord::Base has_many :authorships has_many :books, :through =>

    :authorships end class Authorship < ActiveRecord::Base belongs_to :author belongs_to :book end @author = Author.first # sem has_many through @author.authorships.collect { |a| a.book } # com has_many through @author.books
  16. class Author < ActiveRecord::Base has_many :authorships has_many :books, :through =>

    :authorships end class Authorship < ActiveRecord::Base belongs_to :author belongs_to :book end @author = Author.first # sem has_many through @author.authorships.collect { |a| a.book } # com has_many through @author.books
  17. class Author < ActiveRecord::Base has_many :authorships has_many :books, :through =>

    :authorships end class Authorship < ActiveRecord::Base belongs_to :author belongs_to :book end @author = Author.first # sem has_many through @author.authorships.collect { |a| a.book } # com has_many through @author.books
  18. class Author < ActiveRecord::Base has_many :authorships has_many :books, :through =>

    :authorships end class Authorship < ActiveRecord::Base belongs_to :author belongs_to :book end @author = Author.first # sem has_many through @author.authorships.collect { |a| a.book } # com has_many through @author.books
  19. class User < ActiveRecord::Base belongs_to :avatar end class Profile <

    ActiveRecord::Base belongs_to :user belongs_to :avatar, :through => :user end @user = User.first # sem through @profile.user.avatar # com through @profile.avatar
  20. class User < ActiveRecord::Base belongs_to :avatar end class Profile <

    ActiveRecord::Base belongs_to :user belongs_to :avatar, :through => :user end @user = User.first # sem through @profile.user.avatar # com through @profile.avatar
  21. class User < ActiveRecord::Base belongs_to :avatar end class Profile <

    ActiveRecord::Base belongs_to :user belongs_to :avatar, :through => :user end @user = User.first # sem through @profile.user.avatar # com through @profile.avatar
  22. Para o resgate “Metaprogramaçãozinha”

  23. class User < ActiveRecord::Base belongs_to :avatar end class Profile <

    ActiveRecord::Base include ActiveRecordExtensions belongs_to :user belongs_to :avatar, :through => :user end @user = User.first # sem through @profile.user.avatar # com through @profile.avatar
  24. class User < ActiveRecord::Base belongs_to :avatar end class Profile <

    ActiveRecord::Base include ActiveRecordExtensions belongs_to :user belongs_to :avatar, :through => :user end @user = User.first # sem through @profile.user.avatar # com through @profile.avatar
  25. module ActiveRecordExtensions def self.included(base) base.extend ClassMethods end module ClassMethods def

    self.extended(base) class << base alias_method_chain :belongs_to, :through_option end end def belongs_to_with_through_option(association, options = {}) if options[:through] if self.reflect_on_all_associations.find { |a| a.macro == :belongs_to && a.name == options[:through] } define_method association do |*args| through_assoc = self.send(options[:through], *args) through_assoc && through_assoc.send(options[:source] || association, *args) end else raise ':through option in :belongs_to macro must be another valid :belongs_to association' end else belongs_to_without_through_option association, options end end end end
  26. module ActiveRecordExtensions def self.included(base) base.extend ClassMethods end module ClassMethods def

    self.extended(base) class << base alias_method_chain :belongs_to, :through_option end end def belongs_to_with_through_option(association, options = {}) if options[:through] if self.reflect_on_all_associations.find { |a| a.macro == :belongs_to && a.name == options[:through] } define_method association do |*args| through_assoc = self.send(options[:through], *args) through_assoc && through_assoc.send(options[:source] || association, *args) end else raise ':through option in :belongs_to macro must be another valid :belongs_to association' end else belongs_to_without_through_option association, options end end end end
  27. alias_method_chain :belongs_to, :through_option

  28. module ActiveRecordExtensions def self.included(base) base.extend ClassMethods end module ClassMethods def

    self.extended(base) class << base alias_method_chain :belongs_to, :through_option end end def belongs_to_with_through_option(association, options = {}) if options[:through] if self.reflect_on_all_associations.find { |a| a.macro == :belongs_to && a.name == options[:through] } define_method association do |*args| through_assoc = self.send(options[:through], *args) through_assoc && through_assoc.send(options[:source] || association, *args) end else raise ':through option in :belongs_to macro must be another valid :belongs_to association' end else belongs_to_without_through_option association, options end end end end
  29. module ActiveRecordExtensions def self.included(base) base.extend ClassMethods end module ClassMethods def

    self.extended(base) class << base alias_method_chain :belongs_to, :through_option end end def belongs_to_with_through_option(association, options = {}) if options[:through] if self.reflect_on_all_associations.find { |a| a.macro == :belongs_to && a.name == options[:through] } define_method association do |*args| through_assoc = self.send(options[:through], *args) through_assoc && through_assoc.send(options[:source] || association, *args) end else raise ':through option in :belongs_to macro must be another valid :belongs_to association' end else belongs_to_without_through_option association, options end end end end
  30. def avatar user && user.avatar end

  31. class User < ActiveRecord::Base belongs_to :avatar end class Profile <

    ActiveRecord::Base include ActiveRecordExtensions belongs_to :user belongs_to :avatar, :through => :user end @user = User.first # sem through @profile.user.avatar # com through @profile.avatar
  32. class User < ActiveRecord::Base belongs_to :avatar end class Profile <

    ActiveRecord::Base include ActiveRecordExtensions belongs_to :user belongs_to :avatar, :through => :user end @user = User.first # sem through @profile.user.avatar # com through @profile.avatar
  33. module ActiveRecordExtensions def self.included(base) base.extend ClassMethods end module ClassMethods def

    self.extended(base) class << base alias_method_chain :belongs_to, :through_option end end def belongs_to_with_through_option(association, options = {}) if options[:through] if self.reflect_on_all_associations.find { |a| a.macro == :belongs_to && a.name == options[:through] } define_method association do |*args| through_assoc = self.send(options[:through], *args) through_assoc && through_assoc.send(options[:source] || association, *args) end else raise ':through option in :belongs_to macro must be another valid :belongs_to association' end else belongs_to_without_through_option association, options end end end end
  34. None
  35. Antes de fazer Monkey Patch

  36. Tenho alternativa?

  37. class User < ActiveRecord::Base belongs_to :avatar end class Profile <

    ActiveRecord::Base belongs_to :user delegate :avatar, :to => :user, :allow_nil => true end @user = User.first # sem delegate @profile.user.avatar # com delegate @profile.avatar
  38. class User < ActiveRecord::Base belongs_to :avatar end class Profile <

    ActiveRecord::Base belongs_to :user delegate :avatar, :to => :user, :allow_nil => true end @user = User.first # sem delegate @profile.user.avatar # com delegate @profile.avatar
  39. class User < ActiveRecord::Base belongs_to :avatar end class Profile <

    ActiveRecord::Base belongs_to :user delegate :avatar, :to => :user, :allow_nil => true end @user = User.first # sem delegate @profile.user.avatar # com delegate @profile.avatar
  40. Eu preciso mesmo dele?

  41. # Sem monkey patch t :save # Com monkey patch

    :save.t
  42. require 'will_paginate' module WillPaginate module ViewHelpers pagination_options[:previous_label] = :prev_label.t(:scope =>

    :mislav_will_paginate) pagination_options[:next_label] = :next_label.t(:scope => :mislav_will_paginate) def page_entries_info(collection, options = {}) entry_name = options[:entry_name] || :record.t(:scope => :mislav_will_paginate) entries_name = options[:entries_name] || :records.t(:scope => :mislav_will_paginate) if collection.total_pages < 2 case collection.size when 0 :no_records.t( :entry_name => entry_name, :scope => [:mislav_will_paginate, :messages] ) when 1 :showing_1.t( :entry_name => entry_name, :scope => [:mislav_will_paginate, :messages] ) else :showing_all.t( :size => collection.size, :entries_name => entries_name, :scope => [:mislav_will_paginate, :messages] ) end else :showing.t( :entries_name => entries_name, :from => collection.offset + 1, :to => collection.offset + collection.length, :total => collection.total_entries, :scope => [:mislav_will_paginate, :messages] ) end end end end
  43. Outras Bibliotecas

  44. http://en.wikipedia.org/wiki/Adapter_pattern Adapter Pattern

  45. Por que eu devo evitar?

  46. Esconde comportamento

  47. Modifica globalmente

  48. Dificulta atualização

  49. Complica os testes

  50. Testes dão confiança para mudar o que for necessário

  51. None
  52. não faz tudo para você Rails

  53. None
  54. routes.rb

  55. $ wc -l config/routes.rb 358 config/routes.rb

  56. Controllers

  57. None
  58. http://weblog.jamisbuck.org/2006/10/18/skinny- controller-fat-model Skinny Controller, Fat Model

  59. class PeopleController < ActionController::Base def index @people = Person.find( :conditions

    => ["added_at > ? and deleted = ?", Time.now.utc, false], :order => "last_name, first_name") @people = @people.reject { |p| p.address.nil? } end end
  60. class Person < ActiveRecord::Base def self.find_recent people = find( :conditions

    => ["added_at > ? and deleted = ?", Time.now.utc, false], :order => "last_name, first_name") people.reject { |p| p.address.nil? } end # ... end class PeopleController < ActionController::Base def index @people = Person.find_recent end end
  61. class Person < ActiveRecord::Base def self.find_recent people = find( :conditions

    => ["added_at > ? and deleted = ?", Time.now.utc, false], :order => "last_name, first_name") people.reject { |p| p.address.nil? } end # ... end class PeopleController < ActionController::Base def index @people = Person.find_recent end end
  62. Models

  63. Fat Models também são problemáticos

  64. Defina as responsabilidades

  65. class Account < ActiveRecord::Base validates_numericality_of :amount validates_inclusion_of :currency, :in =>

    %w(us_dollar real) def balance_in_us_dollar if currency == :us_dollar amount elsif currency == :real amount * conversion_rate(:us_dollar, :real) end end def balance_in_real if currency == :real amount elsif currency == :us_dollar amount * conversion_rate(:real, :us_dollar) end end def conversion_rate(from, to) SomeWebservice.get(from, to) end end
  66. class Account < ActiveRecord::Base validates_numericality_of :amount validates_inclusion_of :currency, :in =>

    %w(us_dollar real) def balance_in_us_dollar if currency == :us_dollar amount elsif currency == :real amount * conversion_rate(:us_dollar, :real) end end def balance_in_real if currency == :real amount elsif currency == :us_dollar amount * conversion_rate(:real, :us_dollar) end end def conversion_rate(from, to) SomeWebservice.get(from, to) end end
  67. class Account < ActiveRecord::Base validates_numericality_of :amount validates_inclusion_of :currency, :in =>

    %w(us_dollar real) def balance_in_us_dollar if currency == :us_dollar amount elsif currency == :real amount * conversion_rate(:us_dollar, :real) end end def balance_in_real if currency == :real amount elsif currency == :us_dollar amount * conversion_rate(:real, :us_dollar) end end def conversion_rate(from, to) SomeWebservice.get(from, to) end end
  68. class Account < ActiveRecord::Base validates_numericality_of :amount validates_inclusion_of :currency, :in =>

    %w(us_dollar real) def balance_in_us_dollar if currency == :us_dollar amount elsif currency == :real amount * conversion_rate(:us_dollar, :real) end end def balance_in_real if currency == :real amount elsif currency == :us_dollar amount * conversion_rate(:real, :us_dollar) end end def conversion_rate(from, to) SomeWebservice.get(from, to) end end
  69. class Money def initialize(amount, currency) @amount, @currency = amount, currency

    end def convert_to_us_dollar if currency == :us_dollar @amount elsif currency == :real @amount * ConversionRate.rate(:real, :us_dollar) end end def convert_to_real if currency == :real @amount elsif @currency == :dollar @amount * ConversionRate.rate(:us_dollar, :real) end end end
  70. class Money def initialize(amount, currency) @amount, @currency = amount, currency

    end def convert_to_us_dollar if currency == :us_dollar @amount elsif currency == :real @amount * ConversionRate.rate(:real, :us_dollar) end end def convert_to_real if currency == :real @amount elsif @currency == :dollar @amount * ConversionRate.rate(:us_dollar, :real) end end end
  71. class Account < ActiveRecord::Base validates_numericality_of :amount validates_inclusion_of :currency, :in =>

    %w(us_dollar real) composed_of :balance, :class_name => Money, :mapping => %w(amount currency) end
  72. class Account < ActiveRecord::Base validates_numericality_of :amount validates_inclusion_of :currency, :in =>

    %w(us_dollar real) def balance @balance ||= Money.new(amount, currency) end def balance=(balance) self.amount = balance.amount self.currency = balance.currency end end http://blog.plataformatec.com.br/2012/06/about- the-composed_of-removal/
  73. Evite callbacks e observers

  74. class User < ActiveRecord::Base before_save :get_avatar after_save :create_repository end

  75. class UserRegistration def register(user) get_avatar_for(user) user.save! create_repository_for(user) end end

  76. $ find app/models -name '*.rb' | xargs wc -l |

    sort -r | head -4 7567 total 608 app/models/channel.rb 563 app/models/article.rb 477 app/models/video.rb
  77. http://blog.codeclimate.com/blog/2012/10/17/7- ways-to-decompose-fat-activerecord-models/ Decomponha models obesos

  78. Views

  79. <% people = Person.find( :conditions => ["added_at > ? and

    deleted = ?", Time.now.utc, false], :order => "last_name, first_name") %> <% people.reject { |p| p.address.nil? }.each do |person| %> <div id="person-<%= person.new_record? ? "new" : person.id %>"> <span class="name"> <%= person.last_name %>, <%= person.first_name %> </span> <span class="age"> <%= (Date.today - person.birthdate) / 365 %> </span> </div> <% end %>
  80. class Person < ActiveRecord::Base # ... def name "#{last_name}, #{first_name}"

    end def age (Date.today - birthdate) / 365 end def pseudo_id new_record? ? "new" : id end end
  81. <% @people.each do |person| %> <div id="person-<%= person.pseudo_id %>"> <span

    class="name"><%= person.name %></span> <span class="age"><%= person.age %></span> </div> <% end %>
  82. Evite duplicação

  83. render :partial => 'something_duplicated'

  84. <%= some_helper %>

  85. Muitos locals

  86. render :partial => template_name, :locals => { :my_object => my_object,

    :options => options[:options].join(" "), :use_jquery => options[:use_jquery], :use_css => options[:use_css], :thumb => options[:thumb], :full => options[:full], :width => options[:width], :height => options[:height], :thumb_width => options[:thumb_width], :thumb_height => options[:thumb_height] }
  87. Helpers que renderizam partials

  88. def comment_form(comment, options = {}) # ... render :partial =>

    '_comment_form', :locals => a_huge_amount_of_locals end
  89. None
  90. Tudo no ApplicationHelper

  91. Boas práticas

  92. SOLID http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod

  93. http://amzn.to/Ea92g

  94. http://amzn.to/dnn9Lt

  95. http://amzn.to/1nga3P

  96. O consumo excessivo de Patterns pode trazer danos à saúde.

    Use com moderação.
  97. Conheça suas ferramentas

  98. None
  99. github.com/britto Obrigado! twitter.com/noteu

  100. None
  101. • http://www.guygray.org/2013/08/08/the-explanatory-power-of-monkeys-who-cheat/ • http://wall.alphacoders.com/by_sub_category.php?id=199692 • http://upload.wikimedia.org/wikipedia/commons/4/40/Railroad-Gyula-b.jpg • http://yorkdailypicture.blogspot.com.br/2012/01/full-steam-ahead.html • http://flic.kr/p/LJF71

    • http://www.trainsim.com/vbts/showthread.php?299612-Free-Steam-Locomotive- Drawings • http://theredthread.org/2012/03/08/our-burmese-days/ • http://flic.kr/p/Hjt7G • http://tanfield-railway.blogspot.com.br/2011/09/loco-inspection-prep.html • http://www.ppconstructionsafety.com/newsdesk/2013/04/22/rail-maintenance-firms- err-on-worker-safety/ Créditos das imagens