Learning Ruby Again

Basic

Variables

  • Ruby has four kinds of variables
    • Local variables: begins with an underscore _ or lower case letter.
    • Global variables: begins with $.
    • Instance variables: begins with @.
    • Class variables: begins with @@.

Constants

  • Constants begin with a capital letter. Ruby has beforehand defined several constants(known as predefined-constants), for example: RUBY_VERSION, RUBY_PlATFORM, ARGV, and so on.

Assign

  • Ruby can simultaneously assign values to several variables.
1
2
a, b, c = 1, 2, 3
pp(a, b, c) #=> 1, 2, 3
  • Redundant values will be ignored.
1
2
a, b = 1, 2, 3, 4, 5
pp(a, b) #=> 1, 2
  • The lacking values will be filled with nil.
1
2
a, b, c = 1, 2
pp(a, b, c) #=> 1, 2, nil
  • Prefixes one of these variables with * for capturing all redundant values with array format.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
*a, b, c = 1, 2, 3, 4
pp(a, b, c) #=> [1, 2], b, c

a, *b, c = 1, 2, 3, 4
pp(a, b, c) #=> 1, [2, 3], 4

a, b, *c = 1, 2, 3, 4
pp(a, b, c) #=> 1, 2, [3, 4]

a, *b, c = 1, 2
pp(a, b, c) #=> 1, [], 2

a, *b, c = 1
pp(a, b, c) #=> 1, [], nil

a, *b, c = nil
pp(a, b, c) #=> nil, [], nil
  • Assigning an array to several variables has the equivalence of assigning several values unwrapped from the arr to these variables.
1
2
a, b, c = [1, 2, 3] # the same as `a, b, c = 1, 2, 3`
pp(a, b, c) #=> 1, 2, 3

Conditions

  • Ruby has two groups of logical operators: &&, ||, ! and and, or, not. But the former has higher priority.

  • If the scope of the condition has only one line of code, Usually, we write them on one line.

1
2
3
4
5
6
7
8
9
age = 20

# Not good.
if age > 18
puts 'You are an adult.'
end

# Good.
puts 'You are an adult.' if age > 18

Loop

  • Ruby has six kinds of loops, times, while, each, for, until, and loop.
  • The for is just a syntactic sugar of the each.
  • Ruby has tree loop-condition symbols:
    • break: Jumps out of the loop
    • next: Jumps to next cycle.
    • redo: Repeats current cycle.

Functions

Invoking Functions

  • In Ruby, all function calls must have a receiver. You can think of invoking a method as sending a message to a receiver.
1
print(100) #=> 100

The print function has an implicit receiver: self.

  • In Ruby, many operators are actually functions. For example: +, -, [], etc.
1
2
3
4
5
6
7
8
pp(100 + 200)  #=> 300
pp(100.+(200)) #=> 300
pp(100.+ 200) #=> 300

storage = {name: 'csl', age: 100}
pp(storage[:name]) #=> "csl"
pp(storage.[](:name)) #=> "csl"
pp(storage.[] :name) #=> "csl"
  • Ruby has three kinds of methods:
    • instance method
    1
    2
    str = 'csl'
    puts str.length #=> 3
    • class method
    1
    2
    file = File.open('file_path')
    file = File::open('file_path')
    As you can see, we can use . or :: to invoke a class method. But we recommend to use ..
    • functional method
    1
    puts 'Hello, world.'
    Superficially, the function has no receivers. We don’t need to specify a receiver for the function when calling it. The invoking result doesn’t changed with difference receivers.

Function Definition

  • Ruby functions implicitly return the last expression’s value when not using the return keyword.
1
2
3
4
5
6
7
8
def method1
1 + 1
end
pp(method1) #=> 2

def execute2
end
pp(method2) #=> nil
  • Ruby methods can define default values for arguments. But you must specify default values beginning with the right of the argument list.
1
2
3
4
5
def method(a, b=200, c=300)
puts a, b, c
end

method(100) #=> 100, 200, 300
  • Ruby methods with variadic arguments. Just like assign values to multiple variables.
1
2
3
4
def method(a, *b, c)
pp(a, b, c)
end
method 1, 2, 3, 4 #=> 1, [2, 3], 4
  • Ruby methods with named arguments.
    • Named arguments can setup default values with format name: default.
    • Cannot pass undefined named arguments.
1
2
3
4
5
def method(a:, b:1, c:2)
pp(a, b, c)
end

method b: 200 #=> nil, 200, 2
  • Ruby methods with combination of named arguments and normal arguments.
    • The method must firstly define normal arguments.
1
2
3
4
5
6
7
8
9
def method(a, b:0)
pp(a, b)
end
method(10, b: 20) #=> 10, 20

def method1(a, *b, c, d:0)
pp(a, b, c, d)
end
method(10, 20, 30, 40, d:50) #=> 10, [20, 30], 40, 50
  • Using hash values to pass named parameters.
    • The hash value can’t contain redundant keys.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    def method(a:, b:)
    pp(a, b)
    end

    dic = {a: 100, b: 200}
    method(dic) #=> 100, 200

    dic = {a: 100, b: 200, c: 300}
    method(dic) #=> Error
    • When a method takes a single hash argument or its last argument receives a hash value, you can ignore the hash value’s curly braces.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    def method(arg)
    puts arg
    end
    method(name: 'csl', age: 100) #=> {name: 'csl', age: 100}

    def method(param, arg)
    puts param, arg
    end
    method(100, name: 'csl', age: 100) #=> 100, {name: 'csl', age: 100}

Classes

Class Definition

Common classes inheritance chain.

  • BasicObject

    • Object
      • Array
      • String
      • Hash
      • Regexp
      • IO
        • File
      • Dir
      • Numeric
        • Integer
          • Fixnum
          • Bignum
        • Float
        • Complex
        • Rational
      • Exception
      • Time
  • Defines a class.

1
2
3
4
5
6
7
8
class Person
def initialize(name, age:100)
@name = name
@age = age
end
end

me = Person('csl', age: 18)
  • Accessing instance variables by three ways.
    • Defines accessing methods: age, age=
    • Defines attr_reader and attr_writer
    • Defines attr_accessor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Person
# attr_accessor.
attr_accessor :name

# reader & writer.
attr_reader :weight
attr_writer :weight

# manually create accessing methods.
def age
@age
end

def age=(value)
@age = age
end

def initialize(name, age:100, weight:)
@name = name
@age = age
@weight = weight
end
end

me = Person('csl', age: 18, weight: 85.5)
  • Special variable self
    • The self is a predefined-variable by Swift.
    • The self within instance methods of a class represents the instance itself on run-time.
1
2
3
4
5
6
7
8
9
10
11
class Person
def initialize
end

def dump
puts self
end
end

me = Person.new
me.dump #=> #<Person:0x000000014500f550>

Class Methods

  • Ruby classes have four ways to define a class method.
    • class << class-name ~ end
    1
    2
    3
    4
    5
    6
    class << Person
    def hello(name)
    puts "#{name} said hello."
    end
    end
    Person.hello 'csl' #=> csl said hello.
    • defines class << self ~ end in the definition body of the class.
    1
    2
    3
    4
    5
    6
    7
    8
    class Person
    class << self
    def hello(name)
    puts "#{name} said hello."
    end
    end
    end
    Person.hello 'csl' #=> csl said hello.
    • def class-name.method-name ~ end
    1
    2
    3
    4
    def Person.hello(name)
    puts "#{name} said hello."
    end
    Person.hello 'csl' #=> csl said hello.
    • defines def self.method-name ~ end in the definition body of the class.
    1
    2
    3
    4
    5
    6
    class Person
    def self.hello(name)
    puts "#{name} said hello."
    end
    end
    Person.hello 'csl' #=> csl said hello.

The class defined by class << class-name ~ end is known as singleton class, the methods defined within the class are known as singleton method.

Class Constants

  • Defines constants for classes. As mentioned above, constants begins with a capital letter or all letters are capital.
1
2
3
4
5
class Computer
VERSION = '1.0.0'
end

puts Computer::VERSION #=> 1.0.0

Class Variables

  • Defines class variables
    • Begins with @@
    • All instances can access class variables of its class.
    • The same as instance variables, class variables also need declare writers and readers.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person
@@amount = 0

def initialize
@@amount += 1
end

def self.amount
@@amount
end

def self.amount=(value)
@@amount = value
end
end

10.times { Person.new }
puts Person.amount #=> 10

Access Levels

  • Ruby has three access levels:
    • public
      Exposes this method in the format of instance method
    • private
      You cannot invoke this method with a specified receiver. (You can only invoke this method with the default receiver, so this method cannot be accessed outside its instance).
    • protected
      The methods of protected access level can be accessed as instance methods by its class and all subclasses.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person
def say
puts 'Hello, World.'
end

public :say

def eat
puts 'Eating food.'
end

private :eat

end

me = Person.new

me.say #=> Hello, World.
me.eat #=> Error.
  • Two ways to specify access levels in batches.

    • Specifies multiple methods at the same time.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Person

    private :method1, :method2

    def method1; end

    def method2; end

    end
    • Sets up an access level without any arguments, all the methods defined below the statement will be the access level.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Person

    public # The methods defined below this line will be set up with `public` access level.

    def public1; end
    def public2; end

    private

    def private1; end
    def private2; end
    end

    The default access level is public when you do not explicitly specify any access level. There is an exception that the initialized method is defaulted to private.

Class Extensions

  • We can redeclare an already defined class to extend methods for the class.
1
2
3
4
5
6
7
8
9
class String
def count_word
arr = self.split(/\s+/)

return arr.size
end
end

p "My name is csl".count_word #=> 4

Inheritance

  • Defines a new class inherited from another class in the format of class class-name < super-class-name
    • If you define a new class without specifying a superclass, the default inherited class is Object.
    • If you override a method inherited from its superclass, you can use super to reinvoke superclass’s method in the definition scope of the method.
1
2
3
4
5
6
class RingArray < Array
def [](i)
idx = i % size
super(idx) #=> Utilizes superclass implementation.
end
end

alias & undef

  • In Ruby, we can use alias to provide a new meaning name for an already defined method.
    Alternatively, before redefining a method from the super class, you can alias the old method to save its functionality for using later.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person
def say
puts "I'm a person."
end
end

class Student < Person

alias old_say say

def say
puts "I'm a student."
end
end

me = Student.new

me.say #=> I'm a student.
me.old_say #=> I'm a person.
  • undef can be used to remove methods definitions. You can use it to remove methods definitions of super classes.
1
2
3
4
5
6
7
8
9
10
11
12
13
class Person
def say
puts "I'm a person."
end
end

class Student < Person
undef say
end

me = Student.new

me.say #=> undefined method `say`

Singleton Class

Every class we defined in Ruby are instances of BaseObject. We can use class << Student to extend class methods for the class Student.

Modules

If we say classes include data and behaviors, the Modules only include behaviors.

  • Modules can’t have instances.
  • Modules can’t be inherited.

Module Methods

Sometimes, some methods or variables are about a special area, like math calculation, we can collect them into an isolated module for easily using.

1
2
3
4
5
6
7
8
9
10
11
12
module Api
BASE_URL = 'http://csl.cool/api'

def get(path) {
# http request
}

module_function :get
end


Api.get('/user/123') # Request user info.
  • Using module_function can mark the method get as a module method, then we can access it directly by the Api type.
  • No marked functions can only be used in the module inner or be included by other classes.

Mix-in

A class can include a module to inheritance all instance methods defined in the module.

1
2
3
4
5
6
7
8
9
10
11
12
module Animal
def run
p "I'm running"
end
end

class Person
include Animal
end

me = Person.new
me.run # I'm running

Invoking the Person.ancestors method to look up all its ancestors, we can find the Animal module.

1
p Person.ancestors # [Person, Animal, Object, Kernel, BasicObject]

Ruby utilize the class inheritance to implement Mix-in naturally. But the module isn’t the real super class, it’s virtual. Accessing the Person.superclass, you can find that its superclass is still the object class.

Extend an instance

We have learned how to manually extend an instance’s singleton class, now we can use obj.extend(ModuleA) to extend it in bulk.

1
2
3
4
5
6
7
8
9
10
11
12
module Codable
def encode
end

def decode
end
end

path = '/list'
path.extend(Codable)

path.encode

Operators

In Ruby, almost all operators are instance methods, so we can easily override or define new operators for our types.

Binary Operators

Ruby disables overriding these operators:
::, &&, ||, .., ..., ?:, not, =, and, or

Unary Operators

We can only define these four kinds of unary operators: +, -, ~, !. They are defined separately by functions of names +@, -@, ~@ and !@.

Subscript Operators

Using two functions def [](index) and def []=(index, value) to define subscript for your types.

Block

  • Execute a block
    Using the syntax yield to call the block passed in the function.
1
2
3
4
5
6
7
8
def execute
yield
end

execute do
puts 'test'
end
# test
  • Creating a block instance
1
2
3
4
5
hello = Proc.new do |name|
puts "Hello, #{name}"
end

hello.call("Ruby")

If a method’s last argument is prefixed with &, the block passed into the method when invoking will be assigned to the last argument.

1
2
3
4
5
6
7
8
9
10
def execute(value, &block) do
if block
block.call(value)
end
end

execute 100 do |value|
puts value
end
# 100

Tips

  • Using Class.new can create a new class dynamically.
1
2
3
4
5
6
7
8
9
class Person
def say(words)
p "Say: #{words}"
end
end

Student = Class.new(Person)
me = Student.new
me.say 'Hello' #=> Say: Hello
  • Using %w(words) can generate an array from a literal string.
1
2
arr = %w(How dare you)
p arr #=> ["How", "dare", "you"]
  • Proc#[] is equivalent to Proc#call
1
2
3
4
5
6
say = proc do |*words|
p words
end

say.call(1, 2, 3) #=> 1, 2, 3
say.[1, 2, 3] #=> 1, 2, 3
  • objc.method_name vs class << objc
    Both two notations we can use to define singleton methods. But there are some differences between them two.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
objc = Object.new

CONSTANT_VALUE = 'Outer'

class << objc
CONSTANT_VALUE = 'Inner'
end

def objc.print
p CONSTANT_VALUE
end

class << objc
def display
p CONSTANT_VALUE
end
end

objc.print
objc.display

#=> "Outer"
#=> "Inner"

As you can see, the objc.method_name will access the outer constant, its context is external. The class << object will enter the inner of the objc’s singleton class, so we can access its inner constants.

Concepts

  • You can think that the notation class is used to open the context of classes, of course, it would create a new class by the way if the class hasn’t been created yet.

  • Instance variables in Ruby have no relation with its class. That means two objects of a class can have absolutely different instance variables. You can think of instance variables in Ruby as the key-value storage.

References

  • 《Ruby 基础教程第 5 版》
  • 《Ruby Meta Programming》
  • 《Ruby 原理剖析》