【Ruby】blockとprocとlambda 入門

Ruby の block と proc と lambda の違いをまとめました.

block とは

do ~ end または {} に囲われた部分.
method に処理を渡すために使われる.

# do-end block
10.times do |i|
  p i
end

# {} block
[:sun, :mon, :tue].map{ |w| p w.to_s.upcase }

# call block
def hoge
  if block_given?
    yield
  else
    p 'No block'
  end
end

hoge { p 'hogeeee!' }
#=> "hogeeee!"
hoge
#=> "No block"

method内でyieldを使うと,
渡されたblock(処理)を実行できる.
methodにblockが渡されたかどうかは,
block_given?で boolean が返ってくる.

Proc とは

Procは処理をinstance化して,
objectとして扱うためのもの.
Proc は Ruby の class クラスの object.
なので new method で instance化できる.
その際に処理をblockで渡す.
そのため proc のobjectをmethodに渡す事で,
blockと同様 method に処理を渡すことも出来る.
proc objectに対して call methodを呼ぶと,
proc objectの処理をよびだせる.

# instantiate Proc
# pass block to Proc#new
a = Proc.new { p 'hogeee!' }
b = Proc.new do |txt|
  p txt
end

# call proc
a.call #=> "hogeee!"
# call proc with args
b.call(:tarooo) #=> "tarooo"

method内でblock を procとして受け取る

method内でyield を使うと,
渡されたblockの処理を実行できました.
渡されたblockをmethod内でproc objectとして,
受け取る方法が存在します.
methodの引数定義の際に, 引数の名前の前に&をつけます.
&をつける引数は必ず, 「引数定義の最後の引数」です.

def hoge(&proc)
  p proc.class
  p proc.call
  'method end'
end

hoge { 'string in block' }
# Proc                      (p proc.class)
# "string in block"   (p proc.call)
#=> "method end"  (return value)

method内でproc を blockとして受け取る

さっきの逆で引数として渡したproc objectを,
blockとして受け取る.
procを引数に渡す際に &をつける事で可能.

def hoge
  yield # block の実行 proc objectの実行(= call methodの呼び出し)ではない
end

p = Proc.new { p 'string in proc' }
hoge(&p) # &をつける. 
# hoge methodは引数を定義してないのに
# proc objectを引数 **みたい** に(blockとして)渡せる.
#=> "string in proc"

様々なproc objectの生成方法

様々な方法でProc クラスのobjectを生成できる.

# symbolに対して to_proc method
p1 = :hoge.to_proc
p.class #=> Proc

# [method] method
# 引数にsymbolを渡すと, 
# receiverのobject(省略時はself)に対して
# 引数のsymbol名のmethodをproc化して返す.
def hoge
  p 'hogeee!!'
end

p2 = method(:hoge)
p2.class #=> Proc
p2.call #=> "hogeee!!"

lambdaとは

lamndaはKernel moduleの **method** である.

p Kernel.methods.select { |m| m.to_s.include?('lambda') }
#=> [:lambda]

そしてlambda methodの返り値は Proc クラスの インスタンス!!
Proc new された proc object と
lambdaで作られた proc objectには
引数, returnの扱いで差がある.
その違いについては今回は割愛.

s = lambda { p 'hogeee!' }
s.class #=> Proc
s.call #=> "hogeee!"

# alias of lambda 
s = -> { p :hoge }
s.class #=> Proc
s.call #=> "hoge"

おまけ

実はここまでの記事をちゃんと読み内容を理解していても
rails 開発で頻繁に出てくる次のコードを理解できない.
「method内でproc を blockとして受け取る」で紹介した,
procをmethodに引数みたいに渡す時に付けた& .
紹介した通りこいつはprocの頭につけるものだが,
symbolの頭につけるとそのsymbolをprocとして扱ってくれる.
(symbolにto_proc methodを呼び出したかのように)

['sun', 'mon', 'tue'].map(&:upcase)
#=> ["SUN", "MON", "TUE"]

# ['sun', 'mon', 'tue'].map(&(:upcase.to_proc))
# と同じ意味

Rails4でSTIのモデルのform_forで詰まった話

STIとは

STI(Single table inheritance)は,
DBのtable上で, オブジェクト指向における
継承関係にあるクラスをうまく扱うための考え方です.

STI on rails

railsSTIをサクッと実装する方法を提供してくれているので,
type columnに子供のクラス名を指定するだけです.

# app/model/product.rb
class Product < ApplicationRecord
end

# app/model/product/smart_phone.rb
class Product::SmartPhone < Product
end

# app/model/product/pc.rb
class Product::Pc < Product
end

# app/model/product/tablet.rb
class Product::Tablet < Product
end

p = Product.new(type: 'Product::SmartPhone')
p.class.name #=> Product::SmartPhone

問題

# config/routes.rb
resources :products

# app/views/products/_form.html.slim
# @product = Product.where(type: 'Product::SmartPhone').first
# @product.class.name #=> Product::SmartPhone
= form_for @product do |f|
....

STIの親クラス名でroutingを定義している時に,
edit画面で, 子クラスのobjectをform_forの引数に渡すと..

rails に product_smart_phone_path を探しに行かれてundefinedになって死ぬ.

最終解決策

= form_for @product.becomes(Product) do |f|

ちなみにrails 5だと

= form_for @product, as: :product do |f|

で解決するそうな..
version upは大切ですな...

結論への紆余曲折

最初は form_for 内で url_for が呼ばれてるんだろどうせと思い,
railsgithub repositoryで url_for を検索して code 読みまくった.
(ここでrailsアーキテクチャのわかってなさを痛感...)
そしてurl_forにobject渡した時は
ActiveModel::Nameのインスタンスが帰ってきて
そのインスタンスに対してmethod呼び出しをして
呼び出すpathが決まる事を知った.
そこで

class Product::SmartPhone << Product
  def self.model_name
    superclass.model_name
  end
end

とやって無理やり子クラスのurl_forの値を親クラスのものに
していた.

が正しい事をやってるかんが全くない &&
これだけのために大掛かりなことをしすぎてる感

があって結局 becomesに

DraperとActiveDecoratorの比較

前置き

シンプルなRailsMVCの3層です.
Fat Modelをさけるために, Decorator層を作成し,
表示に関するロジックをDecorator層に閉じ込めます.
これはシステム規模が大きくなるにつれ, Fat modelを
避けるためによく使われる方法の一つです.

Decorator層を実現するために使われるgemとして

の2つがあります.

結論

Draper

  • decorator以外にもview_objectなども使って, viewロジックを管理する場合.
  • 中規模以上のシステム.

の場合採用

ActiveDecorator

  • viewロジックをdecoratorだけで管理する場合.
  • 小規模システム.

の場合採用

比較

decorator の実装

Draper
class PenDecorator < ApplicationDecorator
  delegate_all

  def created_at
    "Hello at #{object.created_at}"
  end
end

Decoratorクラスを実装する.
オリジナルクラスのインスタンス
objectで取得可能.

ActiveDecorator
module PenDecorator
  def created_at_str
    "Hi! at #{created_at}"
  end
end

moduleを実装する.
元クラスにincludeされてるかのように振る舞うので,
オリジナルのメソッドをそのまま使える.
(ざっくりコード読んだ感じ元クラスにincludeされてはなさげ. 要確認.)
オリジナルと同名メソッドを定義して,
その中でオリジナルのメソッドを呼ぶと無限ループで死ぬ.

decoration方法

Draper
class HogeController
  def show
    @hoge = Hoge.first.decorate
    p @hoge.class #=> HogeDecorator
  end
end

Hogeクラスのインスタンスに対してdecorateメソッドを呼び出す.
decorateメソッドがHogeDecoratorクラスを返すので, Decoratorの
メソッドが使用可能になる.
Decoratorのメソッドを使いたい時にdecorateすればどこでも使える.

ActiveDecorator

以下の状況で自動でDecoratorのメソッドが使えるようになる.

英語原文: https://github.com/amatsuda/active_decorator


なのでcontroller内でデフォルトではDecoratorのメソッドは使えない.
以下のようにdecorate化すると使えるようになる.

class HogeController
  def show
    @hoge = Hoge.first
    ActiveDecorator::Decorator.instance.decorate(@hoge)
    p @hoge.class #=> Hoge
    p @hoge.created_at_str #=> "Hi! at 2018-01-01 00:00:00"
  end
end

メリット, デメリット

Draper

Decoratorのメソッドを使いたい時に, Decoratorクラスのインスタンス
を呼び出してるのがわかりやすい.
変数に入ってるのがDecoratorクラスなのかオリジナルのクラスなのか,
わけわからんくなる. (変数名などに工夫がいる?)
なんとなく大げさな事やってる感ある. (完全主観)

ActiveDecorator

viewロジックをmodule管理してincludeしてる(ような感じ)が好き.
View以外でDecoratorのメソッドを使いたい時のdecoration方法がなんとなく微妙.
そもそもview以外で使うべきじゃないよねって思想のもと設計されてそう.
その設計にしっかり乗れるシステムならこっち.

【rubyで作るruby】を読んで

まとめ

1. 我々が書くrubyのコードは木構造にパースされる
2. 木構造のデータが解釈され実行される
3. この本は木構造のデータの実行部分についてプログラムを実際に作りながら進めていくスタイル
4. 浅くしか入り込めてないが個人的にパースする部分の方が面白そう
5. 初心者向け
6. 登場するキャラがかわいい

Rubyで末尾再帰最適化

背景

普段のrailsコーディングでは末尾再帰最適化なんて
まあ間違いなく使う機会がない。
けど訳あって末尾再帰最適化しなきゃ
stack level too deep (SystemStackError)
になるプログラム動かしたかった.

結論

このページが完璧にまとまってた.

obelisk.hatenablog.com


ので今日は書くのサボれる<-

末尾再帰とは

関数Aの呼び出しの最後が関数Aの呼び出しであること.

def func_a(n, a=0)
  # process
  return func_a(n - 1, n + a)
end

末尾再帰最適化とは

プログラミング言語が末尾再帰のプログラムを
スタックオーバーフローしないように
解釈してくれるようになってる事。

つまり末尾再帰でコードを書いても、
その言語が最適化してくれてないと意味にないよって話

普通の再帰

def loop_normal(n)
  1 + loop_normal(n-1)
end

loop_normal(4)

これはn=4の呼び出しは内部でn=3の呼び出しを行い,
n=3の呼び出しは、、、続く
となるのでn=4の呼び出しは
n=3, n=2, n=1の呼び出しの結果をずっと保持しなきゃならん
ということ.
これでnがでかくなると保持しきれず死ぬ

Nginxとrailsで死んだ話

状況

nginxでrailsにproxyしている。

死んだ理由

nginxでrequest hostをrailsに伝えてなかった.

どうゆうことかというとprotect_from_forgeryで以下のmethodが
呼び出されている.
nginxを以下のように設定しないと
request.origin と request.base_url
が異なって死んでしまう.

def valid_request_origin? # :doc:
  if forgery_protection_origin_check
    # We accept blank origin headers because some user agents don't send it.
    raise InvalidAuthenticityToken, NULL_ORIGIN_MESSAGE if request.origin == "null"
    request.origin.nil? || request.origin == request.base_url
  else
    true
  end
end

nginx.conf

upstream puma {
    server unix:///path/to/your_app.sock
}

location / {
    proxy_header Host $http_host;
    proxy_pass http://puma;
}

railsでresponsive化と思ってやめた話

既存のprojectをresponsive化したかった

理由は普通にhtml2つあるのが管理コスト高すぎる問題だったため.
formとか内容合わせるのめんどかったりした.

なんでできなかったか

前提としてあったのはデザインを変えないまま
レスポンシブ化しようとしていた。

レスポンシブかできない具体的な理由は
pcとspで表示してるコンテンツに差異がありすぎたため。
例えば
pcだと10個表示してるのがspだと4個とか、
spの場合取ってきてるデータが多いとか
controller で request.smart_phone?で分岐する一方で
メディアクエリは画面の幅で分岐する。
この分岐方法の矛盾の管理をしたくなさがすごいある。

結論

レスポンシブにするにはデザインからしっかりそれように練るべき