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