Advanced Ruby

Advanced Ruby

My afternoon training session at LSRC 2013

Code samples are online at https://gist.github.com/anthonylewis

3311968fbb383cc2111af84f36508d93?s=128

Anthony Lewis

July 18, 2013
Tweet

Transcript

  1. Advanced Ruby Anthony Lewis Thursday, July 18, 13

  2. About Me • Senior Engineer at Mass Relevance • @anthonylewis

    • me@anthonylewis.com • http://anthonylewis.com Thursday, July 18, 13
  3. Session Overview • Installation • Ruby Object Model • More

    About Classes • Modules / Mixins • Metaprogramming • Real-World Examples Thursday, July 18, 13
  4. Installation Thursday, July 18, 13

  5. Windows • Planning to use Rails? • Use Rails Installer

    • http://railsinstaller.org • Use Ruby Installer • http://rubyinstaller.org Thursday, July 18, 13
  6. Mac OS X • Planning to use Rails? • Use

    Rails Installer • http://railsinstaller.org • Use Homebrew? • brew install ruby • Try RVM Thursday, July 18, 13
  7. Linux • Check your package system • Does it have

    Ruby 1.9 or later? • Enjoy building from source? • Try RVM Thursday, July 18, 13
  8. Ruby Version Manager • For Mac OS X and Linux

    • Install multiple versions of Ruby • Builds from source • Dev Tools Required • http://rvm.io Thursday, July 18, 13
  9. Ruby Object Model Thursday, July 18, 13

  10. A Simple Class class Person def initialize(name) @name = name

    end def greet puts "Hi, I'm #{@name}" end end Thursday, July 18, 13
  11. A Simple Class p = Person.new("Tony") p.class => Person Person.class

    => Class Thursday, July 18, 13
  12. Ancestors Person.ancestors => [Person, Object, Kernel, BasicObject] Thursday, July 18,

    13
  13. Kernel Object BasicObject Person p Thursday, July 18, 13

  14. Documentation • http://www.ruby-doc.org/core-2.0/ • /BasicObject.html • /Kernel.html • /Object.html Thursday,

    July 18, 13
  15. More About Classes Thursday, July 18, 13

  16. A Simple Class class Person def initialize(name) @name = name

    end def greet puts "Hi, I'm #{@name}" end end Thursday, July 18, 13
  17. • Calling a method is also sending a message send

    p.greet => "Hi, I'm Tony" p.send :greet => "Hi, I'm Tony" Thursday, July 18, 13
  18. • You’ve used class variables and instance variables, but have

    you ever used class instance variables? • Class instance variables look like regular instance variables used in a class method • Class instance variables are not shared between subclasses Class Instance Variables Thursday, July 18, 13
  19. • Here’s a question I love to ask when interviewing

    a Ruby developer: • “How would you implement your own Record class with a before_save callback like the one in ActiveRecord?” Class Instance Variables Thursday, July 18, 13
  20. Class Instance Variables class User < Record before_save :saving def

    saving puts "Saving..." end end Thursday, July 18, 13
  21. • Since before_save is a class method, your first instinct

    is to use a class variable to store a list of methods to call. • Since class variables are shared among subclasses, all subclasses will share the same list of before_save callbacks. Class Instance Variables Thursday, July 18, 13
  22. Class Instance Variables class Record def self.before_save(*methods) @callbacks ||= []

    @callbacks += methods end def self.callbacks @callbacks end Thursday, July 18, 13
  23. Class Instance Variables def save self.class.callbacks.each do |c| send c

    if respond_to? c end puts "Saved" end end Thursday, July 18, 13
  24. • Also known as an Eigenclass • An anonymous class

    for holding singleton methods • A singleton method is a method defined on an instance Singleton Class Thursday, July 18, 13
  25. Singleton Class superman = Person.new("Clark") => superman.greet => "Hi, I'm

    Clark" def superman.fly puts "Up, up and away!" end Thursday, July 18, 13
  26. • The method fly is store in superman’s eigenclass •

    It is not available to any other instance of the class Person Singleton Class superman.fly => "Up, up and away!" Thursday, July 18, 13
  27. Modules / Mixins Thursday, July 18, 13

  28. Include module Speaker def speak(word = "Hello") word end end

    Thursday, July 18, 13
  29. • Add module methods as instance methods Include class Person

    include Speaker end p.speak => "Hello" Thursday, July 18, 13
  30. Extend module Learner def can_learn? true end end Thursday, July

    18, 13
  31. • Add module methods as class methods Extend class Person

    extend Learner end Person.can_learn? => true Thursday, July 18, 13
  32. • Called when a module is included in a class

    Included module Coder def self.included(base) base.extend ClassMethods end # continued... Thursday, July 18, 13
  33. Included def code(lines = 3) "Ruby" * lines end module

    ClassMethods def can_code? true end end end Thursday, July 18, 13
  34. Included class Person include Coder end Person.can_code? => true p.code

    => "RubyRubyRuby" Thursday, July 18, 13
  35. Prepend • Include module methods before methods defined in the

    class class Fibonacci def calc(n) return n if n < 2 return calc(n - 1) + calc(n - 2) end end Thursday, July 18, 13
  36. • Calling super will call the method defined in the

    class Prepend module Memoize def calc(n) @@memo ||= {} @@memo[n] ||= super end end Thursday, July 18, 13
  37. Prepend class Fibonacci prepend Memoize def calc(n) return n if

    n < 2 return calc(n - 1) + calc(n - 2) end end Thursday, July 18, 13
  38. Metaprogramming Thursday, July 18, 13

  39. class_eval • Evaluate a code string as if it were

    typed directly into the class definition. • class_eval creates instance methods Thursday, July 18, 13
  40. class_eval p.name NoMethodError: undefined method 'name' for ... Thursday, July

    18, 13
  41. class_eval class Class def get_attr(attr) self.class_eval " def #{attr} @#{attr}

    end " end end Thursday, July 18, 13
  42. class_eval class Person get_attr "name" end p.name => "Tony" Thursday,

    July 18, 13
  43. Note: Ruby provides this functionality out of the box with

    attr_reader, attr_writer, and attr_accessor. Thursday, July 18, 13
  44. • Create a method at runtime define_method class Person define_method

    :punch do |arg| "Ouch!" * arg end end Thursday, July 18, 13
  45. define_method p.punch(3) => "Ouch!Ouch!Ouch!" Thursday, July 18, 13

  46. • Called when a method is not found method_missing class

    Person def method_missing method, *args "What's a #{method}?" end end Thursday, July 18, 13
  47. method_missing p.what => "What's a what?" Thursday, July 18, 13

  48. Real-World Examples Thursday, July 18, 13

  49. Rails Attributes • Every ActiveRecord object stores the values for

    its attributes in a private variable called @attributes • ActiveRecord also adds methods for accessing each attribute such as title, title=, title?, etc. Thursday, July 18, 13
  50. Rails Attributes p = Post.new p.title = "Hello, World" p.title

    => "Hello, World" Thursday, July 18, 13
  51. Rails Attributes def define_method_attribute(attr) class_eval " def #{attr} read_attribute(#{attr}) end

    " end Thursday, July 18, 13
  52. Rails Attributes def read_attribute(attr) @attributes.fetch(attr) end Thursday, July 18, 13

  53. Rails Dynamic Finders • Find a record using an attribute’s

    value p = Post.find_by_title("My Cat") Thursday, July 18, 13
  54. Rails Dynamic Finders def method_missing(meth, *args) if meth.to_s =~ /^find_by_(.+)$/

    attr = $1.split('_and_') find_by_attributes(attr, *args) else super end end Thursday, July 18, 13
  55. Rails Dynamic Finders def find_by_attributes(attr, *args) conditions = Hash[attr.map {

    |a| [a, args[attr.index(a)]] }] where(conditions).first end Thursday, July 18, 13
  56. Rails Dynamic Finders def respond_to?(meth) if meth.to_s =~ /^find_by_(.+)$/ true

    else super end end Thursday, July 18, 13
  57. Method Decorators • We’re working on software for a bank

    • They want to track every time the deposit method is called in their Account class • But, of course, we are not allowed to change code inside the deposit method Thursday, July 18, 13
  58. Method Decorators class Account def initialize(balance) @balance = balance end

    def deposit(n) @balance += n puts "Balance is #{@balance}" end end Thursday, July 18, 13
  59. Method Decorators • We decide to track method calls by

    adding a module with a method decorator • Our decorator is a class method that takes an instance method parameter and prints a message every time it is called Thursday, July 18, 13
  60. module TrackMethods def track(meth) self.class_eval do alias_method "old_#{meth}", meth define_method

    meth do |*args| puts "Calling #{meth} " + "with #{args.join(', ')}" self.send "old_#{meth}", *args end end end end Thursday, July 18, 13
  61. Method Decorators class Account extend TrackMethods # deposit is not

    changed track :deposit end Thursday, July 18, 13
  62. Method Decorators a = Account.new(10) => a.deposit 5 => "Calling

    deposit with 5" => "Balance is now 15" Thursday, July 18, 13
  63. User Features • Our boss would like the ability to

    define features that different users can use • He wants to be able to assign features and check them easily • In a real application, we would store these features in a database table • Instead we will store then in a Ruby Set Thursday, July 18, 13
  64. require 'set' class User def initialize @features = Set.new end

    # continued... User Features Thursday, July 18, 13
  65. def method_missing(meth, *args) if meth.to_s =~ /^can_(.*)\?$/ @features.include? $1.to_s elsif

    meth.to_s =~ /^can_(.*)$/ @features << $1.to_s self else super end end end Thursday, July 18, 13
  66. User Features u = User.new => u.can_code => puts u.can_code?

    => true Thursday, July 18, 13
  67. Resources Thursday, July 18, 13

  68. Learn More Ruby • Programming Ruby 1.9 & 2.0 •

    The PickAxe Book • by Dave Thomas, with Chad Fowler and Andy Hunt • Metaprogramming Ruby • by Paolo Perrotta Thursday, July 18, 13
  69. Go Ruby Crazy • why’s (poignant) Guide to Ruby •

    by why the lucky stiff Thursday, July 18, 13
  70. Learn Onscreen • Try Ruby • http://tryruby.org • Ruby Koans

    • http://rubykoans.com • Ruby has damaged your karma. Thursday, July 18, 13
  71. Thursday, July 18, 13