Slide 1

Slide 1 text

RUBY & RAILS ANTIPATTERNS Tom Crinson - @mrjaba - IPRUG 10th Jan 2012 Wednesday, 11 January 12

Slide 2

Slide 2 text

WHAT IS AN ANTIPATTERN? Wednesday, 11 January 12

Slide 3

Slide 3 text

“In software engineering, an anti-pattern (or antipattern) is a pattern that may be commonly used but is ineffective and/or counterproductive in practice” (Wikipedia) God Object Sequential Coupling Object Orgy Wednesday, 11 January 12

Slide 4

Slide 4 text

RUBY IS NOT EXEMPT Wednesday, 11 January 12

Slide 5

Slide 5 text

THESE ARE NOT LAWS nor are they definitive, most are simply opinion - have your pinches of salt* at the ready *flaming pitchforks must wait until the end Wednesday, 11 January 12

Slide 6

Slide 6 text

RUBY ANTIPATTERNS Wednesday, 11 January 12

Slide 7

Slide 7 text

UNLESS WITH ELSE def horrible_unless_else unless i_am_a_robot? go_eat_food else go_bend_things end end Wednesday, 11 January 12

Slide 8

Slide 8 text

UNLESS WITH ELSE def horrible_unless_else unless i_am_a_robot? go_eat_food else go_bend_things end end def normal_if_else if i_am_a_robot? go_bend_things else go_eat_food end end Wednesday, 11 January 12

Slide 9

Slide 9 text

RESCUE NIL def horrible_rescue_nil tweets = find_all_my_tweets rescue nil tweets.detect{|tweet| tweet.from ~= /"@dhh"/} if tweets end Wednesday, 11 January 12

Slide 10

Slide 10 text

RESCUE NIL def handle_exceptions_properly tweets = find_all_my_tweets find_tweets_from_ddh(tweets) rescue Timeout::Error => e handle_timeout rescue Exception => e logger.error(e) raise end Wednesday, 11 January 12

Slide 11

Slide 11 text

CATCHALL EXCEPTIONS def handle_exceptions_properly tweets = find_all_my_tweets find_tweets_from_ddh(tweets) rescue Exception => e logger.error(e) raise end def handle_exceptions_properly tweets = find_all_my_tweets find_tweets_from_ddh(tweets) rescue Timeout::Error => e retry rescue Net::HTTPServerError => e wait and retry rescue Exception => e logger.error(e) raise end Wednesday, 11 January 12

Slide 12

Slide 12 text

IF TRUE ELSE FALSE def horrible_if_else if self.has_cake? && self.eats_it? return true else return false end end Wednesday, 11 January 12

Slide 13

Slide 13 text

IF TRUE ELSE FALSE def i_like_cake self.has_cake? && self.eats_it? end Wednesday, 11 January 12

Slide 14

Slide 14 text

IF TRUE ELSE FALSE irb(main):001:0> has_cake = "yes" => "yes" irb(main):002:0> eats_it = "hell yes" => "hell yes" irb(main):003:0> has_cake && eats_it => "hell yes" irb(main):004:0> !!(has_cake && eats_it) => true but i need a boolean! Wednesday, 11 January 12

Slide 15

Slide 15 text

IF TRUE ELSE FALSE def i_like_cake self.has_cake? && self.eats_it? end Wednesday, 11 January 12

Slide 16

Slide 16 text

OVERRIDING METHOD_MISSING but not respond_to? class Cake def eat puts "mm eating cake" end def method_missing(method, *args, &block) if method.to_s =~ /^cake/ puts "mmm cake" else super end end end puts Cake.new.respond_to? :eat # true puts Cake.new.respond_to? :cake # false Wednesday, 11 January 12

Slide 17

Slide 17 text

OVERRIDING METHOD_MISSING but not respond_to? class Cake def respond_to?(method) return true if method =~ /^cake/ super end def method_missing(method, *args, &block) if method.to_s =~ /^cake/ puts "mmm cake" else super end end end puts Cake.new.respond_to? :cake # false Wednesday, 11 January 12

Slide 18

Slide 18 text

NOT KNOWING YOUR TOOLS Aka: Use your language features def find_all_the_cakes(bakery_produce) cakes = [] bakery_produce.each do |potential_cake| cakes << potential_cake if is_cake?(potential_cake) end cakes end def find_a_cake(bakery_produce) bakery_produce.each do |potential_cake| return potential_cake if is_cake?(potential_cake) end end Wednesday, 11 January 12

Slide 19

Slide 19 text

NOT KNOWING YOUR TOOLS Aka: Use your language features def find_all_the_cakes(bakery_produce) bakery_produce.select{|potential_cake| is_cake?(potential_cake)} end array[0] vs array.first def find_a_cake(bakery_produce) bakery_produce.detect{|potential_cake| is_cake?(potential_cake)} end Wednesday, 11 January 12

Slide 20

Slide 20 text

VIOLATING DEMETER if cake.layers.first.ingredients.include?(:vanilla_essence) self.tummy.state = "rumbling" end Aka: Train Wreck code Wednesday, 11 January 12

Slide 21

Slide 21 text

VIOLATING DEMETER if cake.ingredients_include?(:vanilla_essence) self.hungry! end Aka: Train Wreck code • You can play on your own. • You can play with your own toys (but you can’t take them apart). • You can play with toys that were given to you. • And you can play with toys you’ve made yourself. Wednesday, 11 January 12

Slide 22

Slide 22 text

LONG METHODS def bake_a_cake mixing_bowl = [] mixing_bowl << :eggs mixing_bowl << :flour mixing_bowl << :milk 100.times do mixing_bowl.shuffle end mixing_bowl = mixing_bowl.join("") cake_tin = [] cake_tin << :grease_proof_paper cake_tin << :grease cake_tin << mixing_bowl.drop(mixing_bowl.length) oven = Oven.new oven.open oven.temperature = 180.degrees oven.insert cake_tin sleep(90.minutes) ... end Wednesday, 11 January 12

Slide 23

Slide 23 text

LONG METHODS def bake_a_cake mix_indgredients prepare_cake_tin prepare_oven put_cake_in_oven eat_cake end def mix_ingredients @mixing_bowl = MixingBowl.new(:eggs,:flour,:milk) @mixing_bowl.mix end def prepare_cake_tin @cake_tin = CakeTin.new @cake_tin.line! @cake_tin.grease! end Wednesday, 11 January 12

Slide 24

Slide 24 text

RAILS ANTIPATTERNS Wednesday, 11 January 12

Slide 25

Slide 25 text

LOGIC IN VIEWS <%= if @cake && @cake.is_a?( VictoriaSponge ) %>

Mmm Spongey

<% else %>

Hmm Not so Spongey

<% end %> Wednesday, 11 January 12

Slide 26

Slide 26 text

LOGIC IN VIEWS <%= spongeyness_level %> def spongeyness(cake) if cake && cake.spongeyness_level > 4 content_tag(:h4, "Mmm Spongey") else content_tag(:h4, "Hmm not so Spongey") end end cake.html.erb cake_helper.rb Bonus Points: Presenters (e.g. draper) Wednesday, 11 January 12

Slide 27

Slide 27 text

“HELPER” SIDE EFFECTS def cake_deliciousness(cake) if cake && cake.eat =~ /omnomnom/ content_tag(:h4, "Mmm Delicious") else content_tag(:h4, "Average") end end def cake_servings(cake) if cake.slices? content_tag(:h4, cake.slices ) else content_tag(:h4, "The cake has been eated!" ) end end <%= cake_deliciousness(@cake) %> =>

Mmm Delicious

<%= cake_servings(@cake) %> =>

The cake has been eated!

cake.html.erb cake_helper.rb a cake can only be tested by eating it! Wednesday, 11 January 12

Slide 28

Slide 28 text

COMPLEX CONTROLLERS class SearchController < ApplicationController def search if params[:advanced] context = params[:advanced][:context] search_term = params[:advanced][:term] results = AdvancedSearchService.search(context, search_term) @normalized_results = normalize_results(results) respond_with(@normalized_results) else search_term = params[:simple][:term] @results = SimpleSearchService.search(search_term) respond_with(@results) end end end Wednesday, 11 January 12

Slide 29

Slide 29 text

COMPLEX CONTROLLERS class SimpleSearchController < ApplicationController def create @search = Search.new(params) respond_with @search end end Keep it simple Break out complex logic into models One controller per resource Wednesday, 11 January 12

Slide 30

Slide 30 text

FAAAT MODELS class Cake < ActiveRecord::Base #Eating Methods def eat_with_fork(person) person.munch(self) until self.empty? end #Distribution methods def slices(degrees_per_slice) 360/degrees_per_slice end #Representation Methods def to_xml ... end def to_html ... end def as_json ... end end Wednesday, 11 January 12

Slide 31

Slide 31 text

FAAAT MODELS class CakePresenter def initialize(cake) @cake = cake end def to_xml ... end def to_html ... end def as_json ... end end def CakeSlicer def initialize(cake) @cake = cake end def slices(degrees_per_slice) 360/degrees_per_slice end end Wednesday, 11 January 12

Slide 32

Slide 32 text

MANY MANY MORE Referring to ActiveRecord classes in migrations [1] Data Migrations [2] Fixtures - especial with associations [3] Script like Rake task [4] No Database indexes [5] Wednesday, 11 January 12

Slide 33

Slide 33 text

CAKE! THANK YOU! Wednesday, 11 January 12

Slide 34

Slide 34 text

RESOURCES Inspired by: ruby rogues: http://rubyrogues.com/ (episode 32) dan manges: http://www.dan-manges.com/blog/28 Further Reading: [1,2] http://complicated-simplicity.com/2010/05/using-models-in-rails-migrations/ [3] http://jakescruggs.blogspot.com/2007/06/why-i-hate-using-rails-fixtures-in.html [4] http://blog.jayfields.com/2006/11/ruby-testing-rake-tasks.html [5] http://rails-bestpractices.com/posts/21-always-add-db-index Image: www.flickr.com/photos/fairtradefairy/3708326594/ Wednesday, 11 January 12