Module Magic

259f23c3b129f07b0c496b9f0495f07e?s=47 jeg2
August 28, 2009

Module Magic

A deep dive into how Ruby's modules really fit into the language.

259f23c3b129f07b0c496b9f0495f07e?s=128

jeg2

August 28, 2009
Tweet

Transcript

  1. MODULE MAGIC and my trip to RubyKaigi2009

  2. JAMES EDWARD GRAY II

  3. JAMES EDWARD GRAY II Created the Ruby Quiz and wrote

    that book
  4. JAMES EDWARD GRAY II Created the Ruby Quiz and wrote

    that book Built FasterCSV (now CSV), HighLine (with Greg), Elif, and some other scarier experiments
  5. JAMES EDWARD GRAY II Created the Ruby Quiz and wrote

    that book Built FasterCSV (now CSV), HighLine (with Greg), Elif, and some other scarier experiments Documented some of Ruby
  6. JAMES EDWARD GRAY II Created the Ruby Quiz and wrote

    that book Built FasterCSV (now CSV), HighLine (with Greg), Elif, and some other scarier experiments Documented some of Ruby http://blog.grayproductions.net/
  7. JAMES EDWARD GRAY II Created the Ruby Quiz and wrote

    that book Built FasterCSV (now CSV), HighLine (with Greg), Elif, and some other scarier experiments Documented some of Ruby http://blog.grayproductions.net/ http://twitter.com/JEG2
  8. LSRC SPEECHES TV shows geeks should know!

  9. LSRC SPEECHES TV shows geeks should know!

  10. RUBYKAIGI2009

  11. RUBYKAIGI2009

  12. RUBYKAIGI2009 See how the Japanese do conferences

  13. RUBYKAIGI2009 See how the Japanese do conferences The translation time

    allows you to think more
  14. RUBYKAIGI2009 See how the Japanese do conferences The translation time

    allows you to think more Meet nice Rubyists from Japan and other places
  15. RUBYKAIGI2009 See how the Japanese do conferences The translation time

    allows you to think more Meet nice Rubyists from Japan and other places See Japan!
  16. None
  17. None
  18. None
  19. None
  20. None
  21. None
  22. None
  23. None
  24. None
  25. None
  26. None
  27. None
  28. MIXIN MODULES

  29. A TRIVIAL MIXIN I’m sure most of us know this

  30. A TRIVIAL MIXIN I’m sure most of us know this

    module Mixin def shared_method puts "Called!" end end ! class Whatever include Mixin end ! Whatever.new.shared_method
  31. A TRIVIAL MIXIN I’m sure most of us know this

    module Mixin def shared_method puts "Called!" end end ! class Whatever include Mixin end ! Whatever.new.shared_method Called!
  32. None
  33. None
  34. None
  35. None
  36. None
  37. class A def call puts "A" end end ! %w[B

    C D].each do |name| eval <<-END_RUBY module #{name} def call puts "#{name}" super end end END_RUBY end ! class E < A include B include C include D def call puts "E" super end end ! E.new.call
  38. E D C B A class A def call puts

    "A" end end ! %w[B C D].each do |name| eval <<-END_RUBY module #{name} def call puts "#{name}" super end end END_RUBY end ! class E < A include B include C include D def call puts "E" super end end ! E.new.call
  39. INHERITANCE p E.ancestors

  40. INHERITANCE p E.ancestors [E, D, C, B, A, Object, Kernel,

    BasicObject]
  41. None
  42. None
  43. None
  44. None
  45. None
  46. class A def call puts "A" end end ! %w[B

    C D].each do |name| eval <<-END_RUBY module #{name} def call puts "#{name}" super end end END_RUBY end ! a = A.new a.extend(B) a.extend(C) a.extend(D) a.call
  47. class A def call puts "A" end end ! %w[B

    C D].each do |name| eval <<-END_RUBY module #{name} def call puts "#{name}" super end end END_RUBY end ! a = A.new a.extend(B) a.extend(C) a.extend(D) a.call D C B A
  48. INVISIBLE CLASS class << a; p ancestors end

  49. INVISIBLE CLASS class << a; p ancestors end [D, C,

    B, A, Object, Kernel, BasicObject]
  50. INVISIBLE CLASS class << a; p ancestors end [D, C,

    B, A, Object, Kernel, BasicObject] The (invisible) “singleton class” is here
  51. None
  52. class A def call puts "A" end end ! %w[B

    C D].each do |name| eval <<-END_RUBY module #{name} def call puts "#{name}" super end end END_RUBY end ! a = A.new a.extend(B) a.extend(C) a.extend(D) class << a def call puts "Invisible" super end end a.call
  53. class A def call puts "A" end end ! %w[B

    C D].each do |name| eval <<-END_RUBY module #{name} def call puts "#{name}" super end end END_RUBY end ! a = A.new a.extend(B) a.extend(C) a.extend(D) class << a def call puts "Invisible" super end end a.call Invisible D C B A
  54. JUST A SHORTCUT We now know what extend() really is

  55. JUST A SHORTCUT We now know what extend() really is

    obj.extend(Mod)
  56. JUST A SHORTCUT We now know what extend() really is

    obj.extend(Mod) class << obj include Mod end
  57. NAMESPACE MODULES

  58. GROUP CONSTANTS Group constants/classes and even mix them in

  59. GROUP CONSTANTS Group constants/classes and even mix them in class

    Logger # ... # Logging severity. module Severity DEBUG = 0 INFO = 1 WARN = 2 ERROR = 3 FATAL = 4 UNKNOWN = 5 end include Severity # ... end
  60. GROUP CONSTANTS Group constants/classes and even mix them in class

    Logger # ... # Logging severity. module Severity DEBUG = 0 INFO = 1 WARN = 2 ERROR = 3 FATAL = 4 UNKNOWN = 5 end include Severity # ... end module JSON class Array # ... end class Object # ... end # ... end
  61. None
  62. None
  63. None
  64. None
  65. DUAL INTERFACE Some modules are both a namespace and a

    mixin
  66. DUAL INTERFACE Some modules are both a namespace and a

    mixin module MoreMath def self.dist(x1, y1, x2, y2) Math.sqrt( (x2 - x1) ** 2 + (y2 - y1) ** 2 ) end end
  67. DUAL INTERFACE Some modules are both a namespace and a

    mixin module MoreMath def self.dist(x1, y1, x2, y2) Math.sqrt( (x2 - x1) ** 2 + (y2 - y1) ** 2 ) end end module MoreMath extend Math def self.dist( x1, y1, x2, y2 ) sqrt( (x2 - x1) ** 2 + (y2 - y1) ** 2 ) end end
  68. MIXIN YOURSELF Better than Ruby’s module_function()

  69. MIXIN YOURSELF Better than Ruby’s module_function() module MiniLogger extend self

    def logger $stdout end def log(message) logger.puts "%s: %s" % [ Time.now.strftime("%D %H:%M:%S"), message ] end end ! if __FILE__ == $PROGRAM_NAME MiniLogger.log "Called as a module method and " + "written to $stdout." end
  70. MIXIN YOURSELF Better than Ruby’s module_function() require "mini_logger" ! class

    Whatever include MiniLogger def logger @logger ||= open("whatever.log", "w") end def initialize log "Called as an " + "instance method " + "and written to " + "a file." end end ! Whatever.new module MiniLogger extend self def logger $stdout end def log(message) logger.puts "%s: %s" % [ Time.now.strftime("%D %H:%M:%S"), message ] end end ! if __FILE__ == $PROGRAM_NAME MiniLogger.log "Called as a module method and " + "written to $stdout." end
  71. None
  72. None
  73. None
  74. None
  75. LIMITED MAGIC Summon new error types as needed

  76. LIMITED MAGIC Summon new error types as needed module Errors

    class BaseError < RuntimeError; end def self.const_missing(error_name) if error_name.to_s =~ /\wError\z/ const_set(error_name, Class.new(BaseError)) else super end end end ! p Errors::SaveError
  77. SOME EXAMPLES Bridging the theory to implementation gap

  78. SOME EXAMPLES Bridging the theory to implementation gap

  79. A BIT TOO CLEVER If RDoc can’t read it, it’s

    probably too clever
  80. A BIT TOO CLEVER If RDoc can’t read it, it’s

    probably too clever require "ostruct" ! class << Config = OpenStruct.new def update_from_config_file(path = config_file) eval <<-END_UPDATE config = self #{File.read(path)} config END_UPDATE end # ... end ! Config.config_file = "config.rb" Config.update_from_config_file
  81. A BIT TOO CLEVER If RDoc can’t read it, it’s

    probably too clever require "ostruct" ! class << Config = OpenStruct.new def update_from_config_file(path = config_file) eval <<-END_UPDATE config = self #{File.read(path)} config END_UPDATE end # ... end ! Config.config_file = "config.rb" Config.update_from_config_file config.command = "ls" config.retries = 42 # ...
  82. LESS MAGIC RDoc and I can both read it now

  83. LESS MAGIC RDoc and I can both read it now

    require "ostruct" ! # These extra methods are mixed into the OpenStruct stored in Config. module Configured # This method loads configuration settings from a plain Ruby file. def update_from_config_file(path = config_file) eval <<-END_UPDATE config = self #{File.read(path)} config END_UPDATE end # ... end # This constant holds all global configuration, see Configured for details. Config = OpenStruct.new.extend(Configured)
  84. None
  85. None
  86. None
  87. None
  88. WITH CLASS METHODS A classic pattern made popular by Rails

  89. WITH CLASS METHODS A classic pattern made popular by Rails

    module DoubleMixin module ClassMethods # ... end module InstanceMethods # ... end def self.included(receiver) receiver.extend(ClassMethods) receiver.send(:include, InstanceMethods) end end
  90. None
  91. None
  92. None
  93. None
  94. LABELING OBJECTS You can use a do-nothing module as a

    type
  95. LABELING OBJECTS You can use a do-nothing module as a

    type module DRb # ... module DRbUndumped def _dump(dummy) # :nodoc: raise TypeError, 'can\'t dump' end end # ... class DRbMessage # ... def dump(obj, error=false) # :nodoc: obj = make_proxy(obj, error) if obj.kind_of? DRbUndumped # ... end # ... end # ... end
  96. OBJECT EDITING Using hooks to edit objects

  97. OBJECT EDITING Using hooks to edit objects p "ruby".strip!. capitalize!

    ! # NoMethodError: # undefined method # `capitalize!' for # nil:NilClass
  98. OBJECT EDITING Using hooks to edit objects module SafelyChainable def

    self.extended(singleton) singleton.methods.grep(/\w!\z/). each do |bang| singleton.instance_eval <<-END_RUBY def #{bang} super self end END_RUBY end end end ! p "ruby".extend(SafelyChainable). strip!.capitalize! p "ruby".strip!. capitalize! ! # NoMethodError: # undefined method # `capitalize!' for # nil:NilClass
  99. SUMMARY

  100. SUMMARY

  101. SUMMARY Master Ruby’s method lookup; it’s worth the effort

  102. SUMMARY Master Ruby’s method lookup; it’s worth the effort Modules

    are a terrific at limiting the scope of magic
  103. SUMMARY Master Ruby’s method lookup; it’s worth the effort Modules

    are a terrific at limiting the scope of magic Remember, modules can modify individual objects
  104. SUMMARY Master Ruby’s method lookup; it’s worth the effort Modules

    are a terrific at limiting the scope of magic Remember, modules can modify individual objects Try replacing some inheritance with extend()
  105. QUESTIONS?