Authors: Alexandre ZANNI

# Modules, mixins and standard classes

# Modules and mixins

Including a module in a class is called a mixin.

# Include vs extend
# an example from  John Nunemaker @ http://www.railstips.org/blog/archives/2009/05/15/include-vs-extend-in-ruby/
module Foo
  def foo
    puts 'heyyyyoooo!'
  end
end

class Bar
  include Foo
end

Bar.new.foo # heyyyyoooo!
Bar.foo # NoMethodError: undefined method ‘foo’ for Bar:Class

class Baz
  extend Foo
end

Baz.foo # heyyyyoooo!
Baz.new.foo # NoMethodError: undefined method ‘foo’ for #<Baz:0x1e708>

# Module mixins example
module PiMath
    def pisqr
        Math::PI**2
    end
end

class CosWrapper
    def self.cos
        # accessible only for classes which define number method
        # number alone is ambiguous because it can refer to self.number or self.class.number
        Math::cos(self.class.number)
    end
end

class PiPowa < CosWrapper
    extend PiMath
    def self.number
        self.pisqr
    end
end

class EPowa < CosWrapper
    def self.esqr
        Math::E**2
    end
    def self.number
        self.esqr
    end
end

class Somenumber
    attr_reader :mynumber
    def initialize(x)
        @mynumber = self.pisqr * x
    end
    protected
    include PiMath
end

puts PiPowa.pisqr # output: 9.869604401089358
puts PiPowa.number # output: 9.869604401089358
puts PiPowa.cos # output: -0.9026853619330714
puts EPowa.esqr # output: 7.3890560989306495
puts EPowa.number # output: 7.3890560989306495
puts EPowa.cos # output: 0.44835624181873357
s = Somenumber.new(7)
puts s.mynumber # output: 69.0872308076255
puts CosWrapper.cos # output: NameError: undefined local variable or method `number' for CosWrapper:Class

# Auto extend when included
# an example from  John Nunemaker @ http://www.railstips.org/blog/archives/2009/05/15/include-vs-extend-in-ruby/
module Foo
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def bar
      puts 'class method'
    end
  end

  def foo
    puts 'instance method'
  end
end

class Baz
  include Foo
end

Baz.bar # class method
Baz.new.foo # instance method
Baz.foo # NoMethodError: undefined method ‘foo’ for Baz:Class
Baz.new.bar # NoMethodError: undefined method ‘bar’ for #<Baz:0x1e3d4>

# Example of a mixin interacting with a class
class Character
    attr_accessor :name
    include Comparable
    def initialize(name)
        @name = name
    end
    def <=>(other)
        self.name <=> other.name
    end
end

c1 = Character.new("Toto")
c2 = Character.new("Tata")

puts c1 > c2 # ouput: true

# Namespacing

# Group classes
module Pets
    class Dog
        def bark
            puts "Woof!"
        end
    end
    class Cat
        def meow
            puts "Meow!"
        end
    end
end

d = Pets::Dog.new
c = Pets::Cat.new

d.bark
c.meow

# Group methods
module MoreMath
    def self.recadd(n)
        (n * (n + 1)) / 2
    end
    def self.factorial(n)
        (1..n).reduce(:*) || 1
    end
end

MoreMath.recadd(7) # output: 28

It's possible to use self for a module method to avoid it being used as a class instance.

Code from Sarah Mei @ stackoverflow

module Foo
  def a
    puts "a: I am a #{self.class.name}"
  end

  def Foo.b
    puts "b: I am a #{self.class.name}"
  end

  def self.c
    puts "c: I am a #{self.class.name}"
  end
end

class Bar
  include Foo

  def try_it
    a
    Foo.b # Bar.b undefined
    Foo.c # Bar.c undefined
  end
end

Bar.new.try_it
#>> a: I am a Bar
#>> b: I am a Module
#>> c: I am a Module

# Structs

Short way to build class with attribute accessors.

# Simple struct
Point = Struct.new(:x,:y)
a = Point.new(1,5)
b = Point.new(7,-2)
puts a.y # output: 5
a.x = 2

OpenStruct (OStruct) is similar to Struct but doesn't require to define attributes.

The drawback is that OpenStruct is slower than Struct.

# Simple OpenStruct
require 'ostruct'

employee = OpenStruct.new
employee.name = "Antony"
employee.job = "Engineer"
employee.salary = 2100

puts employee.job # output: Engineer

# Initialize an OpenStruct with a hash
require 'ostruct'

employee = OpenStruct.new(name: "Antony", job: "Engineer", salary: 2100)

puts employee.job # output: Engineer

# Math and Time

# square root
puts Math.sqrt(16)

# pi constant
puts Math::PI

# trigonometry
puts Math::tan(0)

# current time
t = Time.now
puts t

# year, month, day
puts t.year
puts t.month
puts t.day

# custom date
t = Time.new(1994,04,22)

# day of the week (0-6)
puts t.wday

# day of the year
puts t.yday

# Procs

Procs are object so they can be passed into methods.

# Simple proc
hello = Proc.new do |someone|
    puts "Hi #{someone}"
end

hello.call "Tristan"
hello.call("David")

# Procs passed into methods
goodbye = Proc.new do |someone|
    puts "Bye #{someone}"
end

def say(arr, proc)
    arr.each {|person| proc.call person}
end

people = ["Tristan", "David", "Ophelia"]
say(people, hello)
say(people,goodbye)

# Proc example
def calc_time(proc, n)
    start = Time.now
    proc.call n
    duration = Time.now - start
end

myProc = Proc.new do |n|
    (1..n).reduce(:*) || 1
end

puts calc_time(myProc, 99999) # output: 3.382078767

# Lambdas

# Lambda / anonymous function
l = lambda {puts "stuff"}
l2 = ->() {puts "stuff2"}
l.call
l2.call

# Lambda vs proc
# with proc, arguments are not mandatory
my_lambda = lambda {|arg| puts "Hi #{arg}"}
my_proc = Proc.new {|arg| puts "Hi #{arg}"}
my_lambda.call # output: ArgumentError: wrong number of arguments (given 0, expected 1)
my_proc.call # output: Hi

# Lambda vs proc
# When return is encountered, proc quite the enclosing method, lambda doesn't
def say(arr)
    p = goodbye = Proc.new do |someone|
        puts "Bye #{someone}"
        return "proc return"
    end
    p.call arr.join(", ")
    puts "not displayed"
end
people = ["Tristan", "David", "Ophelia"]
say(people)
# output: Bye Tristan, David, Ophelia
# => "proc return"
def say2(arr)
    l = lambda do |someone|
        puts "Bye #{someone}"
        return "lambda return"
    end
    l_ret = l.call arr.join(", ")
    puts "this is displayed: #{l_ret}"
end
say2(people)
# output: Bye Tristan, David, Ophelia
# output: this is displayed: lambda return
# => nil