【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 on rails
railsはSTIをサクッと実装する方法を提供してくれているので,
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|
結論への紆余曲折
最初は form_for 内で url_for が呼ばれてるんだろどうせと思い,
railsのgithub 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の比較
前置き
シンプルなRailsはMVCの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のメソッドが使えるようになる.
- controllerからviewに, ActiveRecord::Base, ActiveRecord::Relationのinstanceを渡す時
- viewでActiveRecord::Baseのmodel(s)を渡してpartialをrenderするとき
- デコレートされたインスタンスからassociationを取得する時
英語原文: 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で末尾再帰最適化
背景
普段のrailsコーディングでは末尾再帰最適化なんて
まあ間違いなく使う機会がない。
けど訳あって末尾再帰最適化しなきゃ
stack level too deep (SystemStackError)
になるプログラム動かしたかった.
末尾再帰最適化とは
プログラミング言語が末尾再帰のプログラムを
スタックオーバーフローしないように
解釈してくれるようになってる事。
つまり末尾再帰でコードを書いても、
その言語が最適化してくれてないと意味にないよって話
普通の再帰は
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?で分岐する一方で
メディアクエリは画面の幅で分岐する。
この分岐方法の矛盾の管理をしたくなさがすごいある。
結論
レスポンシブにするにはデザインからしっかりそれように練るべき