---
# System prepended metadata

title: Astro課程 0710 - Ruby (Day2)
tags: [' ruby', astro]

---

---
title: Astro課程 0710 - Ruby (Day2)
tags: astro, ruby
---

# 0710 Ruby (Day2)


# 練習
印出偶數且大於3的總和


方法一：用`do end`迴圈
```
list = [1, 4, 2, 4, 6, 7, 8, 2, 3, 2, 1, 6]

sum = 0

list.each do |item|
  (item.even? && item > 3) ? (sum+= item) : sum

p sum

```

方法二：用`.select`方法

```
list = [1, 4, 2, 4, 6, 7, 8, 2, 3, 2, 1, 6]

p list.select { |x| x.even?  && x > 3 }.sum
```


javascript也有跟select相似的方法 

```javascript
list.filter ( (x) => x > 3 && x % 2 == 0 )
```
`( (x) => x > 3 && x % 2 == 0 )` 
中間這一段為 callback function

## Callback

"做完之後再打給我"

回呼函式（callback function）是指能藉由參數（argument）通往另一個函式的函式。[Ref](https://developer.mozilla.org/zh-TW/docs/Glossary/Callback_function)
 

 
# Block

Block是一段不會被主動執行的程式碼
在Ruby/ Rails裡大量使用Block

這裡的`Rails`是`常數`

```ruby
Rails.application.routes.draw do
  resources :restaurants
  root 'restaurants#index'
  get 'pages/about'
  get "/contact_us", to "pages#contact"
end
```

Block有兩種寫法：`do... end`和  `{}`

## Block無法單獨存活

以下會造成語法錯誤

```ruby
{
  puts "我是大括號型的Block"
  #syntax error, unexpected '}', expecting end-of-input
}
```

```ruby
do
  puts "我是do end型的Block"
end
  #block.rb:21: syntax error, unexpected keyword_do_block, expecting end-of-input
```

## Block的執行方式

以下block, 不會被執行
- 因為block像寄生蟲一樣，只能依附在方法後面
- block會不會執行，要看宿主的臉色，會不會想讓block執行


```ruby
def say_hello
  puts "Hi, "
end

say_hello {
  puts "here! "
}
puts "there!"
```

 
Outputs

```bash
Hi, 
there!
```

## 使用yield執行

```ruby

def say_hello
  puts "Hi, "
  yield
  puts "大家好"
end

say_hello {
  puts "here! "
}
puts "there!"
```

Output:
```ruby
Hi, 
here! 
大家好
there!
```

## 轉讓的時候，可以攜帶參數

```ruby
def say_hello
  yield 3
end

#say_hello

say_hello { |n|
  puts "Hi! "*n
}
```

Outputs
```
Hi! Hi! Hi! 
```


如果yield後面沒寫參數=> 等於參數是`nil`

```ruby
def say_hello
  yield nil
end

say_hello do
  p "n"
  p "m"
end
```

output

```
"n"
"m"
```

```ruby
def say_hello
  yield 3, 4
end

say_hello do |n , m, l|
    p n
    p m
    p l
end
```
output

```
3
4
nil
```


## 讓出控制權來有什麼好處？

把資料做過濾及篩選

Eg.
```
list = [* 1..100]
p list.select { |x| x > 50 }
```


## Block完成的時候也可以帶東西回來

```
def test_two
  if yield(3)
    puts "yes, it is 2"
  else
    puts "no, it is not 2"
  end
end

test_two {|n|
  puts "aaa"
  n == 2
}
```

output

block回傳最後一行， 3 == 2 為`false`
```
no, it is not 2
```

## 沒有block但是使用yield就會出錯

`LocalJumpError`

```
def hello
  yield  
end
hello
```

output
```
Traceback (most recent call last):
        1: from yield.rb:44:in `<main>'
yield.rb:42:in `hello': no block given (yield) (LocalJumpError)
```


實體變數和全域變數有預設值 `nil`
（區域變數沒有）

```
2.5.2 :002 > $123
 => nil 
2.5.2 :003 > @aaa
 => nil 
 
2.5.2 :006 > $ruby.is_a?(Object)
 => true 
2.5.2 :007 > $php.is_a?(Object)
 => true 
 
 2.5.2 :008 > $ruby.is_a?(Object) {|oriented| language }
 => true 
```

區域變數沒有預設值

```
2.5.2 :011 > y
出錯
Traceback (most recent call last):
        2: from /Users/tingtinghsu/.rvm/rubies/ruby-2.5.2/bin/irb:11:in `<main>'
        1: from (irb):13
NameError (undefined local variable or method `y' for main:Object)

2.5.2 :011 > y = y
 => nil 
2.5.2 :012 > y
 => nil 
```

## 變數提升 Variable Hoisting
面試常考！ javascript有變數提升的現象

在執行任何程式碼前，JavaScript 會把函式宣告放進記憶體裡面，這樣做的優點是：可以在程式碼宣告該函式之前使用它。
[Ref](https://developer.mozilla.org/zh-TW/docs/Glossary/Hoisting)

```
console.log (x); 
var x = 1;
=> 1

# creation phase
=> x = undefined

# execution phase
# => undefined
x = 1
```

但是在ruby會出錯

Javascript的`setTimeOut 3000` 和`Event Loop`也常考


# 練習:刻出自己的my_select方法


```
def my_select(list)
 //實作內容
end

my_select([1, 2, 3, 4, 5]) { |i| i.odd? }
```

Eg.
```
def my_select(list)
  result = []
  list.each do |x|
    if yield (x)
      result << x
    end
  end
  return result
end

p my_select([1, 2, 3, 4, 5]) { |i| i.odd? }
```

# 練習:刻出自己的my_map方法

```
# p [1, 2, 3, 4, 5].map { |x| x * 2 }

def my_map(list)
  # 實作內容
end

p my_map([1, 2, 3, 4, 5]) { |x| x * 2 }

```

Eg.
```

def my_map(list)
  # 實作內容
  result = []
  list.each do |element|
    result << yield(element)
  end
  return result  
end

p my_map([1, 2, 3, 4, 5]) { |x| x * 2 }
```


## `.select` & `.reject` 方法

```
list = [1, 2, 3, 4, 5]
p list.select { |x| x.odd? } # [1, 3, 5]
p list.reject { |x| x.odd? } # [2, 4]
```

# Block兩種寫法的不同

`大括號`的內容會先執行
`do end` 較弱


## 重要：javascript執行的先後順序

javascript
```
1 < 10 < 100
true

100 > 10 > 1
false

100 > 10
true

10 > 1
true

true > 1
false

1 + "2" + 3
"123"

1 + 2 + "3"
"33"
```

# Proc: 物件化的block

一般的方法只能被呼叫
```
def add_two()
    2 + n
end

add_two(3)
```

Proc物件可以被當作參數丟來丟去
```
add_two = Proc.new { |x| x*2 }
add_two.call(3)

```

練習
```
list = [1, 2, 3, 4, 5]

double = Proc.new { |x| x*2 }
p list.map { |element| double.call(element) }

# => [2, 4, 6, 8, 10]
```

練習: lambda當參數傳進去 (龍哥說把lambda寫成一個小元件)

```
list1 = [1, 2, 3, 4, 5]
list2 = ["a", "b", "c"]
double = -> (x) { x * 2 }

p list1.map(&double)

```

output
```
[2, 4, 6, 8, 10]
[2, 4, 6, 8, 10]
["aa", "bb", "cc"]
["aa", "bb", "cc"]
```

練習

```
p list1.select { |x| x.odd? }
p list1.select(&:odd?)
```

output

```
[1, 3, 5]
[1, 3, 5]
```

P.S. javascript 高階函數(higher order function)
一般的函數就可以被當作參數丟來丟去

但是ruby一般的方法沒有此特性，必須先寫成Proc

# lambda

Proc的另一種寫法
```ruby
add_two = Proc.new { |n| n + 2 }
add_two = lambda { |n| n + 2 }
add_two = -> (n) { n +2 }
```

```ruby
add_two = 2

其實是
add_two = -> (x) { x * 2 } # 匿名函數
add_two.call(100)

def    add_two  # 具名函數

end
```

## lambda 在 Rails的應用

Eg. 在rails定義一個叫做cheap的scope

```ruby
class Book < ApplicationRecord
  scope :cheap, -> { where ("price <= 100") }
end
```

`-> { where ("price <= 100") }`
大括號包成lambda, 變成可以使用的物件
（一段可以執行的程式碼，但是是被動的，必須要有人呼叫它）



scope有兩個參數 `:cheap`, `-> { where ("price <= 100") }`

## High Order Function
ruby `scope(:cheap, -> {...})`
javascript `list.map(double)`
將函數當作物件傳給其他函數


# 物件導向程式語言

## 為什麼要使用OOP?

將記憶體的位置`擬人化`的過程

物件 = 狀態（名詞）+ 行為（動詞）

Eg. 人
狀態：膚色
行為：吃飯、走路


類別：烤盤
實體：雞蛋糕

## 類別命名：必須是常數（第一個字大寫）
```ruby
class Cat
  def eat(food)
    puts "#{food} 好吃！"
  end
end
```
大寫一定是常數，但不一定是類別

* 實體：使用`類別`來產生

```ruby
kitty = Cat.new
kitty.eat("八方雲集")
nancy = Cat.new
nancy.eat("海南雞飯")

```

output
```
八方雲集 好吃！
海南雞飯 好吃！
```

# 繼承
- 與其說是繼承，不如說是分類
- 把`共同的特徵`放在`同一個分類`
```ruby
class Animal
  def walk(place)
    puts "走去 #{place}！"
  end
  def eat(food)
    puts "#{food} 好吃！"
  end
end

class Cat < Animal
end

class Dog < Animal
end

kitty = Cat.new
kitty.eat("八方雲集")
nancy = Cat.new
nancy.eat("海南雞飯")

kitty.walk("公園")
nancy.walk("車站")

```

output
```
八方雲集 好吃！
海南雞飯 好吃！
走去 公園！
走去 車站！
```

# 初始化 `initialize`

`initialize`是一個特別的method，一出生就會做的第一個動作

```ruby
class Cat
  def initialize
    puts "hello 你好"
  end
end

kitty = Cat.new # => hello 你好
# 什麼都不用做，一出生就會打招呼！
```

# 實體方法

作用在實體上的方法

`say_hello`: 作用在實體上面

```ruby
class Cat
  def say_hello
    puts "hi, 你好"
  end
end

kitty = Cat.new #kitty實體
kitty.say_hello #say_hello方法，作用在kitty實體上
```

# 類別方法

必須在實體方法前面加上`.self`

- 類別之內`self.all`
- 這樣才能順利呼叫類別方法`Cat.all`

```ruby
class Cat
  def say_hello
    puts "hi, 你好"
  end
  
  def self.all
    puts "全部的貓!"
  end
end

kitty = Cat.new 
kitty.say_hello # => hi, 你好 (實體方法)

Cat.all # => 全部的貓! (類別方法)
```

# singleton method 單體（單例）方法 

可以在任意物件上定義任何方法

```
def kitty.fjdlfjdslfd
  puts "hi"
end

kitty = Cat.new
kitty.fjdlfjdslfd

```

`all`類別方法其實是從`單體方法`來的：
```
class Cat
  def Cat.all
    puts "hi"
  end
end

# singleton method 單體（例）方法
Cat.all

```

如果以後要改成`Dog`?
直接改成`self`

```

class Cat
  def self.all
    puts "hi"
  end
end

Cat.all
```

javascript裡的`this` 類似`self`
(你在哪裡？我在`這裡`) 
那`這裡`到底是哪裡？

[請看Kuro的講解](https://kuro.tw/posts/2017/10/12/What-is-THIS-in-JavaScript-%E4%B8%8A/)

# 為什麼要有類別方法？

直接請類別幫忙做事情

```
kitty = Cat.new
kitty.xxx

Cat.xxx
```

# 開放類別 Open Class
又稱為monkey patching

```
# rails
3.days.ago   # 可以用

# ruby
3.days.ago   # 會出錯 X
```

來改一下ruby讓`3.days.ago`跑得動
```
class Integer
  def days
  end

  def ago
  end
end

puts 3.days.ago 


Traceback (most recent call last):
hello.rb:9:in `<main>': undefined method `ago' for nil:NilClass (NoMethodError)

# => 會出錯，因為 days 沒有回傳值

```

讓days回傳自己
```
class Integer
  def days
    self
  end

  def ago
    "#{self} days ago!!"
  end
end

puts 3.days.ago
puts 5.days.ago
```

開放類別可能會在背後偷偷做某些事情（但是我們不知道）...

```
# open class
# monkey patching
class Integer
  alias :old_plus :+

  def +(n)
    puts "hey hey hey"
    old_plus(n)
  end
end

puts 1 + 2
puts 3 + 2
```

# Ruby 的存取控制

## public

只要沒有特別說明，預設是`public`
通常習慣把public method集中放在上面，
private method集中放在下面

## private

private 不能有明確的訊息接收者(receiver)

* 呼叫方法的時候不能有`.`小數點 （puts就是一種private方法）

```ruby
class Cat
  def say_hello #public
      puts "hello!"
  end

  private #在private範圍內設定的方法，不能隨意取用
  def gossip  
  end
end

kitty = Cat.new
kitty.say_hello #hello!

kitty.gossip # private method `gossip' called for #<Cat:0x00007fcd4692c730> (NoMethodError)
```

Smalltalk程式語言
`2 + 3`
對物件2傳送+訊息，並把3傳進來

Ruby傳訊息的方式受到Smalltalk的影響
在Ruby裡是對Kitty物件送了say_hello()訊息

    * kitty: 接收者
    * say_hello(): 訊息
    
    
來查查看private方法有哪些：`self.class.private_methods.sort`

發現`private`和`public`都是private方法
```
2.5.2 :001 > self.class.private_methods.sort
 => [:Array, :Complex, :DelegateClass, :Float, :Hash, :Integer, :Rational, :String, :URI, :__callee__, :__dir__, :__method__, :`, :abort, :at_exit, :binding, :block_given?, :caller, :caller_locations, :catch, :eval, :exec, :exit, :exit!, :extended, :fail, :fork, :format, :gem, :gem_original_require, :gets, :global_variables, :included, :inherited, :initialize, :initialize_clone, :initialize_copy, :initialize_dup, :irb_binding, :iterator?, :lambda, :load, :local_variables, :loop, :method_added, :method_missing, :method_removed, :method_undefined, :open, :p, :prepended, :print, :printf, :private, :proc, :protected, :public, :putc, :puts, :raise, :rand, :readline, :readlines, :remove_const, :require, :require_relative, :respond_to_missing?, :select, :set_trace_func, :singleton_method_added, :singleton_method_removed, :singleton_method_undefined, :sleep, :spawn, :sprintf, :srand, :syscall, :system, :test, :throw, :trace_var, :trap, :untrace_var, :using, :warn] 
```

如果寫了一個private method，再查詢
```
private
  def hello
end

p self.class.private_methods.sort
```

會多了`:hello`的private method

```
[...  :gets, :global_variables, :hello, ]
```

## 其實private也不是這麼private

使用`.send(:gossip)`
但是這樣會破壞封裝的原則

```
class Cat
  private
  def gossip
    puts "我跟你說，你不能跟別人說"
  end
end

kitty = Cat.new
kitty.send(:gossip)  # 成功！
```

可以一層一層的往裡面傳

```
kitty = Cat.new
kitty.send(:gossip)  # 成功！
kitty.send(:send, :gossip) 
kitty.send(:send, :send, :gossip) 
kitty.send(:send, :send, :send, :send, 
:send, :send, :send, :send, :send, :send, 
:gossip) 
```

```
我跟你說，你不能跟別人說
我跟你說，你不能跟別人說
我跟你說，你不能跟別人說
我跟你說，你不能跟別人說
```

## protect

如果是protect, `self.hello`, `hello`兩種寫法都可以
但是private會出錯

```
class Smalltalk

  def h
    self.hello
    hello
  end

  protected
  def hello
  end
end

```

# Superclass

```
class Animal

end

class Cat < Animal
  
end

kitty = Cat.new

p kitty.class
p kitty.class.superclass
p kitty.class.superclass.superclass
p kitty.class.superclass.superclass.superclass
p kitty.class.superclass.superclass.superclass.superclass
p kitty.class.superclass.superclass.superclass.class
```

output

```
Cat 
Animal
Object
BasicObject 
nil          # BasicOject是最上層，再上去就是nil了
Class
```