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

Advanced Ruby

Advanced Ruby

My afternoon training session at LSRC 2013

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

Anthony Lewis

July 18, 2013
Tweet

More Decks by Anthony Lewis

Other Decks in Programming

Transcript

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

    View full-size slide

  2. About Me
    • Senior Engineer at Mass Relevance
    • @anthonylewis
    [email protected]
    • http://anthonylewis.com
    Thursday, July 18, 13

    View full-size slide

  3. Session Overview
    • Installation
    • Ruby Object Model
    • More About Classes
    • Modules / Mixins
    • Metaprogramming
    • Real-World Examples
    Thursday, July 18, 13

    View full-size slide

  4. Installation
    Thursday, July 18, 13

    View full-size slide

  5. Windows
    • Planning to use Rails?
    • Use Rails Installer
    • http://railsinstaller.org
    • Use Ruby Installer
    • http://rubyinstaller.org
    Thursday, July 18, 13

    View full-size slide

  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

    View full-size slide

  7. Linux
    • Check your package system
    • Does it have Ruby 1.9 or later?
    • Enjoy building from source?
    • Try RVM
    Thursday, July 18, 13

    View full-size slide

  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

    View full-size slide

  9. Ruby Object Model
    Thursday, July 18, 13

    View full-size slide

  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

    View full-size slide

  11. A Simple Class
    p = Person.new("Tony")
    p.class
    => Person
    Person.class
    => Class
    Thursday, July 18, 13

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  15. More About Classes
    Thursday, July 18, 13

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  20. Class Instance Variables
    class User < Record
    before_save :saving
    def saving
    puts "Saving..."
    end
    end
    Thursday, July 18, 13

    View full-size slide

  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

    View full-size slide

  22. Class Instance Variables
    class Record
    def self.before_save(*methods)
    @callbacks ||= []
    @callbacks += methods
    end
    def self.callbacks
    @callbacks
    end
    Thursday, July 18, 13

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  27. Modules / Mixins
    Thursday, July 18, 13

    View full-size slide

  28. Include
    module Speaker
    def speak(word = "Hello")
    word
    end
    end
    Thursday, July 18, 13

    View full-size slide

  29. • Add module methods as instance methods
    Include
    class Person
    include Speaker
    end
    p.speak
    => "Hello"
    Thursday, July 18, 13

    View full-size slide

  30. Extend
    module Learner
    def can_learn?
    true
    end
    end
    Thursday, July 18, 13

    View full-size slide

  31. • Add module methods as class methods
    Extend
    class Person
    extend Learner
    end
    Person.can_learn?
    => true
    Thursday, July 18, 13

    View full-size slide

  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

    View full-size slide

  33. Included
    def code(lines = 3)
    "Ruby" * lines
    end
    module ClassMethods
    def can_code?
    true
    end
    end
    end
    Thursday, July 18, 13

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  38. Metaprogramming
    Thursday, July 18, 13

    View full-size slide

  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

    View full-size slide

  40. class_eval
    p.name
    NoMethodError: undefined method
    'name' for ...
    Thursday, July 18, 13

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  43. Note: Ruby provides this functionality
    out of the box with attr_reader,
    attr_writer, and attr_accessor.
    Thursday, July 18, 13

    View full-size slide

  44. • Create a method at runtime
    define_method
    class Person
    define_method :punch do |arg|
    "Ouch!" * arg
    end
    end
    Thursday, July 18, 13

    View full-size slide

  45. define_method
    p.punch(3)
    => "Ouch!Ouch!Ouch!"
    Thursday, July 18, 13

    View full-size slide

  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

    View full-size slide

  47. method_missing
    p.what
    => "What's a what?"
    Thursday, July 18, 13

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  50. Rails Attributes
    p = Post.new
    p.title = "Hello, World"
    p.title
    => "Hello, World"
    Thursday, July 18, 13

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  53. Rails Dynamic Finders
    • Find a record using an attribute’s value
    p = Post.find_by_title("My Cat")
    Thursday, July 18, 13

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  56. Rails Dynamic Finders
    def respond_to?(meth)
    if meth.to_s =~ /^find_by_(.+)$/
    true
    else
    super
    end
    end
    Thursday, July 18, 13

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  61. Method Decorators
    class Account
    extend TrackMethods
    # deposit is not changed
    track :deposit
    end
    Thursday, July 18, 13

    View full-size slide

  62. Method Decorators
    a = Account.new(10)
    =>
    a.deposit 5
    => "Calling deposit with 5"
    => "Balance is now 15"
    Thursday, July 18, 13

    View full-size slide

  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

    View full-size slide

  64. require 'set'
    class User
    def initialize
    @features = Set.new
    end
    # continued...
    User Features
    Thursday, July 18, 13

    View full-size slide

  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

    View full-size slide

  66. User Features
    u = User.new
    =>
    u.can_code
    =>
    puts u.can_code?
    => true
    Thursday, July 18, 13

    View full-size slide

  67. Resources
    Thursday, July 18, 13

    View full-size slide

  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

    View full-size slide

  69. Go Ruby Crazy
    • why’s (poignant) Guide to Ruby
    • by why the lucky stiff
    Thursday, July 18, 13

    View full-size slide

  70. Learn Onscreen
    • Try Ruby
    • http://tryruby.org
    • Ruby Koans
    • http://rubykoans.com
    • Ruby has damaged your karma.
    Thursday, July 18, 13

    View full-size slide

  71. Thursday, July 18, 13

    View full-size slide