Upgrade to Pro — share decks privately, control downloads, hide ads and more …

That's Not Very Ruby of You

Ernie Miller
November 10, 2013

That's Not Very Ruby of You

Keynote on final day of RubyConf 2013.

How Ruby is your Ruby? As we've found our way to Ruby, we've all brought with us habits and idioms from the languages we know and love (or hate). Sometimes, these habits serve us well, and other times, they make our lives more difficult. More often, they make life difficult for our collaborators, who don't expect to see C, Java, or PHP in their Ruby codebase.

Not only that, as many new Rubyists have found their way to Ruby from Rails, they've brought along various Rails-isms, some of which aren't even a good idea in Rails-land (callbacks, anyone?), or dependencies on libraries like ActiveSupport which are overkill for their real needs.

We'll talk about some of the baggage we bring to our Ruby code, and some simple techniques that will help us leave it behind.

Ernie Miller

November 10, 2013
Tweet

More Decks by Ernie Miller

Other Decks in Programming

Transcript

  1. #include <stdio.h> ! int main() { int a = -1;

    int b = -1; int c = -1; ! while (a != 0 || b != 0) { printf("Enter two numbers (0 0 to quit): "); scanf("%d%d", &a, &b); ! c = a + b; ! printf("The sum of the numbers you entered is %d\n", c); } }
  2. def int(*) end ! def main yield end ! int

    main() { int a = -1; int b = -1; int c = -1; ! while (a != 0 || b != 0) printf("Enter two numbers (0 0 to quit): "); a, b = scanf("%d%d"); ! c = a + b; ! printf("The sum of the numbers you entered is %d\n", c); end }
  3. require 'scanf' ! def int(*) end ! def main yield

    end ! int main() { int a = -1; int b = -1; int c = -1; ! while (a != 0 || b != 0) printf("Enter two numbers (0 0 to quit): "); a, b = scanf("%d%d"); ! c = a + b; ! printf("The sum of the numbers you entered is %d\n", c); end } Enter two numbers (0 0 to quit): 2 4 The sum of the numbers you entered is 6 Enter two numbers (0 0 to quit): 1 2 The sum of the numbers you entered is 3 Enter two numbers (0 0 to quit): 0 0 The sum of the numbers you entered is 0
  4. require 'scanf' ! def int(*) end ! def main yield

    end ! int main() { int a = -1; int b = -1; int c = -1; ! while (a != 0 || b != 0) printf("Enter two numbers (0 0 to quit): "); a, b = scanf("%d%d"); ! c = a + b; ! printf("The sum of the numbers you entered is %d\n", c); end } Enter two numbers (0 0 to quit): 2 4 The sum of the numbers you entered is 6 Enter two numbers (0 0 to quit): 1 2 The sum of the numbers you entered is 3 Enter two numbers (0 0 to quit): 0 0 The sum of the numbers you entered is 0
  5. require 'scanf' ! def int(*) end ! def main yield

    end ! int main() { int a = -1; int b = -1; int c = -1; ! while (a != 0 || b != 0) printf("Enter two numbers (0 0 to quit): "); a, b = scanf("%d%d"); ! c = a + b; ! printf("The sum of the numbers you entered is %d\n", c); end } Enter two numbers (0 0 to quit): 2 4 The sum of the numbers you entered is 6 Enter two numbers (0 0 to quit): 1 2 The sum of the numbers you entered is 3 Enter two numbers (0 0 to quit): 0 0 The sum of the numbers you entered is 0
  6. <? $fruits = array('banana', 'apple', 'orange') ?> A sorted list

    of fruits: ! <? sort($fruits) ?> <? foreach ($fruits as $fruit) { ?> * <?= $fruit ?> ! <? } ?> ! <?= $fruits[0] ?> is spelled: <?= implode(' ', str_split($fruits[0])) ?> <? $rubies = array( '1.8' => 'unsupported', '1.9' => 'supported', '2.0' => 'supported' ) ?> ! ! Important note regarding Ruby versions: ! <? foreach ($rubies as $ruby => $status) { ?> * <?= implode(' is ', array($ruby, $status)) ?> ! <? } ?>
  7. PersonalHomePage.new.render(DATA.read) ! __END__ <% $fruits = array('banana', 'apple', 'orange') %>

    A sorted list of fruits: ! <% sort($fruits) %> <% for $fruit in $fruits %> * <%= $fruit %> <% end %> ! <%= $fruits[0] %> is spelled: <%= implode(' ', str_split($fruits[0])) %> <% $rubies = array( '1.8' => 'unsupported', '1.9' => 'supported', '2.0' => 'supported' ) %> Important note regarding Ruby versions: ! <% for ($ruby, $status) in $rubies %> * <%= implode(' is ', array($ruby, $status)) %> <% end %>
  8. require 'erb' ! class PersonalHomePage def array(*args) Hash === args.first

    ? args.first : Array(args) end ! def implode(*args) array_join(*args.reverse) end ! Array.instance_methods.each do |method_name| define_method "array_#{method_name}", ->(array, *args, &block) { array.to_a.send(method_name, *args, &block) } end ! String.instance_methods.each do |method_name| define_method "str_#{method_name}", ->(string, *args, &block) { string.send(method_name, *args, &block) } end ! alias sort array_sort! ! def str_split(string, length = 1) string.scan(/.{1,#{length}}/) end ! def render(template) ERB.new(template, nil, '<>').run(binding) end end ! PersonalHomePage.new.render(DATA.read)
  9. require 'erb' ! class PersonalHomePage def array(*args) Hash === args.first

    ? args.first : Array(args) end ! def implode(*args) array_join(*args.reverse) end ! Array.instance_methods.each do |method_name| define_method "array_#{method_name}", ->(array, *args, &block) { array.to_a.send(method_name, *args, &block) } end ! String.instance_methods.each do |method_name| define_method "str_#{method_name}", ->(string, *args, &block) { string.send(method_name, *args, &block) } end ! alias sort array_sort! ! def str_split(string, length = 1) string.scan(/.{1,#{length}}/) end ! def render(template) ERB.new(template, nil, '<>').run(binding) end end ! PersonalHomePage.new.render(DATA.read)
  10. A sorted list of fruits: ! * apple * banana

    * orange ! apple is spelled: a p p l e ! Important note regarding Ruby versions: ! * 1.8 is unsupported * 1.9 is supported * 2.0 is supported
  11. App = { say: function(string) { console.log(string) }, ifMultipleOf: function(number,

    check, callback) { (check % number) == 0 ? callback(check) : nil; }, ifEven: function(number, callback) { App.ifMultipleOf(2, number, callback); }, proclaimEvenness: function(number) { App.say("It would appear that "+number+" is an even number."); } } App.say("Hello, world!"); App.ifEven(42, App['proclaimEvenness']);
  12. ! ! ! ! ! ! ! App = {

    say: ->(string) { console.log(string) }, ifMultipleOf: ->(number, check, callback) { (check % number) == 0 ? callback.(check) : nil; }, ifEven: ->(number, callback) { App.ifMultipleOf(2, number, callback); }, proclaimEvenness: ->(number) { App.say("It would appear that "+number+" is an even number."); } } App.say("Hello, world!"); App.ifEven(42, App['proclaimEvenness']);
  13. class Hash def method_missing(method_id, *args, &block) has_key?(method_id) ? self[method_id].(*args, &block)

    : super end ! def [](key) fetch(key) { fetch(key.to_sym) } # LOL GC end ! def respond_to_missing?(method_id, include_private = false) has_key?(method_id) || super end end ! class Fixnum alias to_str to_s end ! class Console def log(*args) puts *args end end ! console = Console.new ! Hello, world! It would appear that 42 is an even number.
  14. class Hash def method_missing(method_id, *args, &block) has_key?(method_id) ? self[method_id].(*args, &block)

    : super end ! def [](key) fetch(key) { fetch(key.to_sym) } # LOL GC end ! def respond_to_missing?(method_id, include_private = false) has_key?(method_id) || super end end ! class Fixnum alias to_str to_s end ! class Console def log(*args) puts *args end end ! console = Console.new ! Hello, world! It would appear that 42 is an even number.
  15. class Person: def __init__(self, name, age): self.name = name self.age

    = age ! person = Person('Ernie', 36) ! print person.name print person.age person.nonexistent_attribute = 'Seriously?' print person.nonexistent_attribute
  16. ! ! ! ! class Person def __init__(se1f, name, age)

    se1f.name = name se1f.age = age end end ! person = Person('Ernie', 36) ! print person.name print person.age person.nonexistent_attribute = 'Seriously?' print person.nonexistent_attribute
  17. class Object alias print puts ! def setter_block(name) ->(*args) {

    instance_variable_get(:@__attributes__)[name] = *args } end ! def getter_block(name) ->(*args) { instance_variable_get(:@__attributes__)[name] } end ! def method_missing(method_id, *args, &block) method_name = method_id.to_s if method_name.end_with?('=') && self.instance_variable_defined?(:@__attributes__) attribute_name = method_name.chop define_singleton_method(method_name, setter_block(attribute_name)) define_singleton_method(attribute_name, getter_block(attribute_name)) send(method_name, *args, &block) elsif Object.const_defined?(method_id) obj = Object.const_get(method_id).allocate obj.instance_variable_set(:@__attributes__, {}) obj.__init__(obj, *args, &block) obj else super end rescue NameError => e super end end Ernie 36 Seriously?
  18. def edit_distance length1, length2 = @string1.length, @string2.length # puts "length1:

    #{length1} length2: #{length2}" # row = [nil] * length1 # prev_row = row prev_row = row = (0..length1).to_a (1..length2).each do |y| # puts "prev_row: #{prev_row.inspect}" row = [y] * (length1 + 1) # (1..length1).each do |column| (1..length1).each do |x| # if @string1[x - 1] == @string2[y - 1] # row[column] = prev_row[column - 1] # else row[x] = if @string1[x - 1] == @string2[y - 1] prev_row[x - 1] else # row[column] = find_min(row, prev_row, column) [ row[x - 1] + 1, # delete? prev_row[x] + 1, # insert? prev_row[x - 1] + 1 # substitute? ].min end end # puts "row: #{row.inspect}" prev_row = row end row.last end
  19. def edit_distance length1, length2 = @string1.length, @string2.length # puts "length1:

    #{length1} length2: #{length2}" # row = [nil] * length1 # prev_row = row prev_row = row = (0..length1).to_a (1..length2).each do |y| # puts "prev_row: #{prev_row.inspect}" row = [y] * (length1 + 1) # (1..length1).each do |column| (1..length1).each do |x| # if @string1[x - 1] == @string2[y - 1] # row[column] = prev_row[column - 1] # else row[x] = if @string1[x - 1] == @string2[y - 1] prev_row[x - 1] else # row[column] = find_min(row, prev_row, column) [ row[x - 1] + 1, # delete? prev_row[x] + 1, # insert? prev_row[x - 1] + 1 # substitute? ].min end end # puts "row: #{row.inspect}" prev_row = row end row.last end
  20. class City def initialize @traits = [] end ! def

    trait description @traits << description end ! def space needle "for knitting space socks." end ! def grunge music "smells like teen spirit" end ! def emerald city, usa trait space :needle trait grunge :music end end
  21. class City def initialize @traits = [] end ! def

    trait description @traits << description end ! def space needle "for knitting space socks." end ! def grunge music "smells like teen spirit" end ! def emerald city, usa trait space :needle trait grunge :music end end
  22. class Greeter def initialize ( greeting ) @greeting = greeting

    end def greet ( name, number_of_greetings = 1 ) number_of_greetings.times do puts ("#{ @greeting }, #{ name }!") end end end
  23. class Greeter ! def initialize(greeting) @greeting = greeting end !

    def greet(name, number_of_greetings = 1) number_of_greetings.times do puts "#{@greeting}, #{name}!" end end ! end
  24. class Greeter ! # Initialize with a greeting, x. def

    initialize(x) @x = x end ! # Greet the person/group named x, # i times. def greet(x, i = 1) i.times { puts "#{@x}, #{x}!" } end ! end
  25. class Greeter ! def initialize(the_greeting_or_prefix) @the_greeting_or_prefix = the_greeting_or_prefix end !

    def greet(name_of_person_to_greet, number_of_times_to_greet = 1) number_of_times_to_greet.times do puts “#{@the_greeting_or_prefix}, #{name_of_person_to_greet}!” end end ! end
  26. class Greeter ! def initialize(the_greeting_or_prefix) @the_greeting_or_prefix = the_greeting_or_prefix end !

    def greet(name_of_person_to_greet, number_of_times_to_greet = 1) number_of_times_to_greet.times do puts “#{@the_greeting_or_prefix}, #{name_of_person_to_greet}!” end end ! end
  27. class Greeter ! def initialize(the_greeting_or_prefix) @the_greeting_or_prefix = the_greeting_or_prefix end !

    def greet(name_of_person_to_greet, number_of_times_to_greet = 1) number_of_times_to_greet.times do puts “#{@the_greeting_or_prefix}, #{name_of_person_to_greet}!” end end ! end
  28. class AwesomeGreeter ! def initialize(theSalutation) @theSalutation = theSalutation end !

    def greetPersonOrGroup(nameOfGreetable, numberOfTimesToGreet = 1) numberOfTimesToGreet.times do puts("#{@theSalutation}, #{nameOfGreetable}!") end end ! end
  29. Ruby is not Java. This is what we call in

    the industry a “feature.” Also: http://tinyurl.com/camelCase
  30. class Word ! def similarity_to(other_word) score = normalized_distance(self, other_word) score

    += 100 if synonyms.include?(other_word) return score end ! end
  31. class Iffy ! def iffy if thing_is_true? do_some_stuff if other_thing_is_true?

    do_some_more_stuff if truthiness_is_everywhere do_the_stuffiest_stuff_ever unless just_kidding? end else do_some_different_stuff end else do_a_fallback_thing end end ! end
  32. class Iffy ! def iffy if thing_is_true? do_some_stuff if other_thing_is_true?

    do_some_more_stuff if truthiness_is_everywhere do_the_stuffiest_stuff_ever unless just_kidding? end else do_some_different_stuff end else do_a_fallback_thing end end ! end
  33. class Iffy ! def iffy if thing_is_true? do_some_stuff if other_thing_is_true?

    do_some_more_stuff if truthiness_is_everywhere do_the_stuffiest_stuff_ever unless just_kidding? end else do_some_different_stuff end else do_a_fallback_thing end end ! end
  34. class Iffy ! def iffy if thing_is_true? do_some_stuff if other_thing_is_true?

    do_some_more_stuff if truthiness_is_everywhere do_the_stuffiest_stuff_ever unless just_kidding? end else do_some_different_stuff end else do_a_fallback_thing end end ! end
  35. module Util module_function ! def dashify(string) string.scan(/./).join('-') end ! def

    array_of_strings?(o) o.is_a?(Array) && o.all? { |obj| obj.is_a?(String) } end ! def array_of_arrays?(o) o.is_a?(Array) && o.all? { |obj| obj.is_a?(Array) } end ! def array_of_dates?(o) o.is_a?(Array) && o.all? { |obj| obj.respond_to?(:to_time) } end ! end
  36. YOU CAN NOT HAS BUCKET OF METHODS if Class <

    Module puts 'That goes for you too, Class.' end
  37. class InstallationsController < ActionController::Base ! def schedule desired_date = params[:desired_date]

    if request.xhr? begin if @installation.pending_credit_check? render :json => {:errors => ["Cannot schedule installation while credit check is pending"]}, :status => 400 return end audit_trail_for(current_user) do if @installation.schedule!(desired_date, :installation_type => params[:installation_type], :city => @city) if @installation.scheduled_date date = @installation.scheduled_date.in_time_zone(@installation.city.timezone).to_date render :json => {:errors => nil, :html => schedule_response(@installation, date)} end else render :json => {:errors => [%Q{Could not update installation. #{@installation.errors.full_messages.join(' ')}}] } end end rescue ActiveRecord::RecordInvalid => e render :json => {:errors => [e.message] } rescue ArgumentError => e render :json => {:errors => ["Could not schedule installation. Start by making sure the desired date is on a business day."]} end else if @installation.pending_credit_check? flash[:error] = "Cannot schedule installation while credit check is pending" redirect_to installations_path(:city_id => @installation.city_id, :view => "calendar") and return end begin audit_trail_for(current_user) do if @installation.schedule!(desired_date, :installation_type => params[:installation_type], :city => @city) if @installation.scheduled_date if @installation.customer_provided_equipment? flash[:success] = %Q{Installation scheduled} else flash[:success] = %Q{Installation scheduled! Don't forget to order the equipment also.} end end else flash[:error] = %Q{Could not schedule installation, check the phase of the moon} end end rescue => e flash[:error] = e.message end redirect_to(@installation.customer_provided_equipment? ? customer_provided_installations_path : installations_path(:city_id => @installation.city_id, :view => "calendar")) end end ! end
  38. class InstallationsController < ActionController::Base ! def schedule ScheduleInstallation.new(schedule_responder).call end !

    private ! def schedule_responder if request.xhr? AJAXResponder.new(self) else HTMLResponder.new(self) end end ! end
  39. “ for the same reason that a drawing should have

    no unnecessary lines and a machine no unnecessary parts.
  40. “ This requires not that the writer make all sentences

    short or avoid all detail and treat subjects only in outline, but that every word tell.
  41. class Array ! def second self[1] end ! def third

    self[2] end ! def fourth self[3] end ! def fifth self[4] end ! def forty_two self[41] end ! end
  42. class Array ! def second self[1] end ! def third

    self[2] end ! def fourth self[3] end ! def fifth self[4] end ! def forty_two self[41] end ! end array = %w(first second third fourth fifth) ! array.second # => "second" array.fourth # => "fourth"
  43. class Array ! def second self[1] end ! def third

    self[2] end ! def fourth self[3] end ! def fifth self[4] end ! def forty_two self[41] end ! end array = %w(first second third fourth fifth) ! array.second # => "second" array.fourth # => "fourth" class Array ! def penultimate # EXPRESSIVE self[-2] end ! def antepenultimate # ZOMG SO EXPRESSIVE self[-3] end ! end
  44. class Array ! def second self[1] end ! def third

    self[2] end ! def fourth self[3] end ! def fifth self[4] end ! def forty_two self[41] end ! end array = %w(first second third fourth fifth) ! array.second # => "second" array.fourth # => "fourth" class Array ! def penultimate # EXPRESSIVE self[-2] end ! def antepenultimate # ZOMG SO EXPRESSIVE self[-3] end ! end array.penultimate # => "fourth" array.antepenultimate # => "third"
  45. class Array ! def second self[1] end ! def third

    self[2] end ! def fourth self[3] end ! def fifth self[4] end ! def forty_two self[41] end ! end array = %w(first second third fourth fifth) ! array.second # => "second" array.fourth # => "fourth" class Array ! def penultimate # EXPRESSIVE self[-2] end ! def antepenultimate # ZOMG SO EXPRESSIVE self[-3] end ! end array.penultimate # => "fourth" array.antepenultimate # => "third"
  46. class Object ! def in?(container) container.include? self end ! end

    array = %w(first second third fourth fifth) ! 'second'.in? array # => true
  47. has_many :posts, -> { order(:created_at => :desc) }, :inverse_of =>

    :user, :class_name => 'Article', :foreign_key => 'person_id', :validate => false, :autosave => true, :dependent => :destroy do def method_in_an_anonymous_module_because_we_certainly_wont_regret_this puts 'LOL LSP!' end end
  48. has_many :posts, -> { order(:created_at => :desc) }, :inverse_of =>

    :user, :class_name => 'Article', :foreign_key => 'person_id', :validate => false, :autosave => true, :dependent => :destroy do def method_in_an_anonymous_module_because_we_certainly_wont_regret_this puts 'LOL LSP!' end end
  49. def delegate(*methods) options = methods.pop unless options.is_a?(Hash) && to =

    options[:to] raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).' end ! prefix, allow_nil = options.values_at(:prefix, :allow_nil) ! if prefix == true && to =~ /^[^a-z_]/ raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.' end ! method_prefix = \ if prefix "#{prefix == true ? to : prefix}_" else '' end ! file, line = caller.first.split(':', 2) line = line.to_i ! to = to.to_s to = 'self.class' if to == 'class' ! methods.each do |method| definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block' ! if allow_nil module_eval(<<-EOS, file, line - 3) def #{method_prefix}#{method}(#{definition}) _ = #{to} if !_.nil? || nil.respond_to?(:#{method}) _.#{method}(#{definition}) end end EOS else exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") ! module_eval(<<-EOS, file, line - 2) def #{method_prefix}#{method}(#{definition}) _ = #{to} _.#{method}(#{definition}) rescue NoMethodError if _.nil? #{exception} else raise end end EOS end end end
  50. def delegate(*methods) options = methods.pop unless options.is_a?(Hash) && to =

    options[:to] raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).' end ! prefix, allow_nil = options.values_at(:prefix, :allow_nil) ! if prefix == true && to =~ /^[^a-z_]/ raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.' end ! method_prefix = \ if prefix "#{prefix == true ? to : prefix}_" else '' end ! file, line = caller.first.split(':', 2) line = line.to_i ! to = to.to_s to = 'self.class' if to == 'class' ! methods.each do |method| definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block' ! if allow_nil module_eval(<<-EOS, file, line - 3) def #{method_prefix}#{method}(#{definition}) _ = #{to} if !_.nil? || nil.respond_to?(:#{method}) _.#{method}(#{definition}) end end EOS else exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") ! module_eval(<<-EOS, file, line - 2) def #{method_prefix}#{method}(#{definition}) _ = #{to} _.#{method}(#{definition}) rescue NoMethodError if _.nil? #{exception} else raise end end EOS end end end
  51. class Module private ! def delegate(*args) dest, prefix = _extract_valid_delegation_options(args.pop)

    _define_delegators(caller_locations.first, prefix, dest, args) end ! def delegate_maybe(*args) dest, prefix = _extract_valid_delegation_options(args.pop) _define_delegators( caller_locations.first, prefix, "(_ = #{dest}; _.nil? ? NilDelegate.new : _)", args ) end ! def _define_delegators(from, prefix, accessor, args) args.each do |arg| method_name = [prefix, arg].compact.join('_') module_eval( _delegator(accessor, arg, method_name), from.path, from.lineno - 2 ) end end ! def _delegator(accessor, destination_method, local_method) %{ def #{local_method}(*args, &block) #{accessor}.__send__(:#{destination_method}, *args, &block) end } end ! def _extract_valid_delegation_options(opts) if Hash === opts && opts.has_key?(:to) [opts[:to].to_s.sub(/^([a-z])/, 'self.\1'), opts[:prefix]] else raise ArgumentError, 'Invalid delegation options. Delegate with :to.' end end end
  52. class Module private ! def delegate(*args) dest, prefix = _extract_valid_delegation_options(args.pop)

    _define_delegators(caller_locations.first, prefix, dest, args) end ! def delegate_maybe(*args) dest, prefix = _extract_valid_delegation_options(args.pop) _define_delegators( caller_locations.first, prefix, "(_ = #{dest}; _.nil? ? NilDelegate.new : _)", args ) end ! def _define_delegators(from, prefix, accessor, args) args.each do |arg| method_name = [prefix, arg].compact.join('_') module_eval( _delegator(accessor, arg, method_name), from.path, from.lineno - 2 ) end end ! def _delegator(accessor, destination_method, local_method) %{ def #{local_method}(*args, &block) #{accessor}.__send__(:#{destination_method}, *args, &block) end } end ! def _extract_valid_delegation_options(opts) if Hash === opts && opts.has_key?(:to) [opts[:to].to_s.sub(/^([a-z])/, 'self.\1'), opts[:prefix]] else raise ArgumentError, 'Invalid delegation options. Delegate with :to.' end end end
  53. class NilDelegate < BasicObject delegate *(nil.methods - [:__send__, :object_id]), :to

    => :__nil__ ! private ! def __nil__ nil end ! def method_missing(method_id, *args, &block) nil end ! def respond_to_missing?(method_id, include_private = false) true end end
  54. Rules for Class Macros What, not how! Idempotent! Order independent!

    Straightforward! Free of branching 1 2 3 4 5
  55. Rules for Class Macros What, not how! Idempotent! Order independent!

    Straightforward! Free of branching 1 2 3 4 5
  56. Rules for Class Macros What, not how! Idempotent! Order independent!

    Straightforward! Free of branching 1 2 3 4 5
  57. You have to know the rules in order to break

    them. Otherwise, it’s no fun.
  58. require 'active_support/callbacks' ! class Base ! include ActiveSupport::Callbacks ! define_callbacks

    :auto_arming ! set_callback :auto_arming, :before do @weapons_armed = true end ! def fire_lasers run_callbacks :auto_arming do puts 'PEW PEW PEW!' if @weapons_armed end end ! end ! base = Base.new base.fire_lasers # => PEW PEW PEW!
  59. What, not how! Idempotent! Order independent! Straightforward! Free of branching

    1 2 3 4 5 Rules Callbacks Not consistently. No. Nope. LOL!! Honorary tree.
  60. class Base ! include ActiveSupport::Callbacks ! define_callbacks :auto_arming ! set_callback

    :auto_arming, :before, :arm_weapons, :if => :not_armed?, :unless => :training_mode? ! # ... ! def fire_lasers run_callbacks :auto_arming do puts 'PEW PEW PEW!' if @weapons_armed end end ! end
  61. class Base ! include ActiveSupport::Callbacks ! define_callbacks :auto_arming ! set_callback

    :auto_arming, :before, :arm_weapons, :if => :not_armed?, :unless => :training_mode? ! # ... ! def fire_lasers run_callbacks :auto_arming do puts 'PEW PEW PEW!' if @weapons_armed end end ! end
  62. class SecretBase < Base ! # This base shouldn't go

    making loud PEW PEW PEW noises # unless it really, really needs to. skip_callback :auto_arming, :before, :arm_weapons, :unless => :under_attack? ! # ... ! end
  63. module AutoArming ! def fire_lasers @weapons_armed = true super end

    ! end ! class Base ! def fire_lasers puts 'PEW PEW PEW!' if @weapons_armed end ! end ! base = Base.new base.extend AutoArming base.fire_lasers # => PEW PEW PEW!
  64. module AutoArming ! def fire_lasers @weapons_armed = true super end

    ! end ! class Base ! def fire_lasers puts 'PEW PEW PEW!' if @weapons_armed end ! end ! base = Base.new base.extend AutoArming base.fire_lasers # => PEW PEW PEW!
  65. module AutoArming ! def fire_lasers @weapons_armed = true super end

    ! end ! class Base ! prepend AutoArming ! def fire_lasers puts 'PEW PEW PEW!' if @weapons_armed end ! end ! base = Base.new base.fire_lasers # => PEW PEW PEW!
  66. module AutoArming ! def fire_lasers @weapons_armed = true super end

    ! end ! class Base ! prepend AutoArming ! def fire_lasers puts 'PEW PEW PEW!' if @weapons_armed end ! end ! base = Base.new base.fire_lasers # => PEW PEW PEW!
  67. module AutoArming ! def fire_lasers @weapons_armed = true if should_autoarm?

    super end ! def should_autoarm? true end ! end class SecretBase < Base ! def should_autoarm? under_attack? end ! # ... ! end class Base ! prepend AutoArming ! def fire_lasers puts 'PEW PEW PEW!' if @weapons_armed end ! end
  68. What, not how! Idempotent! Order independent! Straightforward! Free of branching

    1 2 3 4 5 Rules extend/prepend Yes! It depends. Yep!
  69. What, not how! Idempotent! Order independent! Straightforward! Free of branching

    1 2 3 4 5 Rules extend/prepend Yes! It depends. Yep! Eeeeyup!
  70. What, not how! Idempotent! Order independent! Straightforward! Free of branching

    1 2 3 4 5 Rules extend/prepend Yes! It depends. Yep! Eeeeyup! Free as the wind (through a branchless tree)
  71. base.singleton_class.ancestors # => [AutoArming, Base, Object, Kernel, BasicObject] base.is_a?(AutoArming) #

    => true base._fire_lasers_callbacks # => [#<ActiveSupport::Callbacks::Callback:0x007...>] Callbacks: extend/prepend: