Slide 1

Slide 1 text

Advanced Ruby Anthony Lewis Thursday, July 18, 13

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Installation Thursday, July 18, 13

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Mac OS X • Planning to use Rails? • Use Rails Installer • http://railsinstaller.org • Use Homebrew? • brew install ruby • Try RVM Thursday, July 18, 13

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

Ruby Object Model Thursday, July 18, 13

Slide 10

Slide 10 text

A Simple Class class Person def initialize(name) @name = name end def greet puts "Hi, I'm #{@name}" end end Thursday, July 18, 13

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Kernel Object BasicObject Person p Thursday, July 18, 13

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

More About Classes Thursday, July 18, 13

Slide 16

Slide 16 text

A Simple Class class Person def initialize(name) @name = name end def greet puts "Hi, I'm #{@name}" end end Thursday, July 18, 13

Slide 17

Slide 17 text

• 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

Slide 18

Slide 18 text

• 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

Slide 19

Slide 19 text

• 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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

• 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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

• 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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

• 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

Slide 27

Slide 27 text

Modules / Mixins Thursday, July 18, 13

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

• 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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

• 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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

Metaprogramming Thursday, July 18, 13

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

• 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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Real-World Examples Thursday, July 18, 13

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

Resources Thursday, July 18, 13

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

Thursday, July 18, 13