目次
then(yield_self)について
thenはレシーバをブロックの引数として受け取り、ブロックの評価をレシーバに反映させるメソッドです。yield_self
はthen
のエイリアス(等価のメソッド)です。
### thenの挙動を紹介するコードです。実際は『"ruby".upcase』だけで事足ります。
> "ruby".then(&:upcase)
=> "RUBY"
thenとtapの違い
ブロックの結果がレシーバに反映されるのがthen
、反映されないのがtap
です。
レシーバにブロックの実行結果を反映させたい場合はthen
を利用します。
以下はthen
を利用して文字列を加工する例です。
> message = "ruby"
> message.then(&:upcase)
.then(&:reverse)
> message
=> "YBUR"
### 参考: tapはブロックの結果をレシーバに反映させないので変化なし
> message = "ruby"
> message.tap(&:upcase)
.tap(&:reverse)
> message
=> "ruby"
レシーバにブロックの実行結果を反映させなくてよい場合はtap
を利用します。
以下はtap
を利用してメソッドチェインの途中結果を出力する例です。
> msg = "ruby"
> new_msg = msg.upcase.tap { |msg| p "1st: #{msg}"} # "1st: RUBY"
.reverse.tap { |msg| p "2nd: #{msg}" } # "2nd: YBUR"
> new_msg
=> "YBUR"
### 参考: thenだとブロックの結果がレシーバに反映されるため、メソッドチェインに影響を与えてしまう
> msg = "ruby"
> new_msg = msg.upcase.then { |msg| p "1st: #{msg}"} # "1st: RUBY"
.reverse.then { |msg| p "2nd: #{msg}" } # "2nd: YBUR :ts1"
> new_msg
=> "2nd: YBUR :ts1"
thenの使いどころ
then
の使いどころの具体例を紹介します。
値やインスタンスへの作用のメソッドチェイン化
値やインスタンスに対して次々とメソッドを実行する場合にthen
が活躍します。
以下のコードはAPIにアクセスした際のステータスコードを取得する例です。
require "net/http"
url = "https://jsonplaceholder.typicode.com/todos/1"
### URLの文字列からURIインスタンスを作成
uri = URI.parse(url)
### GETリクエストを実行してレスポンスを取得
response = Net::HTTP.get_response(uri)
### レスポンスのコードを取得
response.code
# "200"
上記のコードをthen
で書き換えると以下のようになります。then
を利用することで一連の処理をメソッドチェインで表現できます。
require "net/http"
url = "https://jsonplaceholder.typicode.com/todos/1"
url.then(&URI.method(:parse))
.then(&Net::HTTP.method(:get_response))
.code # "200"
Active Recordに対する処理のメソッドチェイン化
Active Recordに対してさまざまな処理をする場合にthen
が活躍します。
例えばArticleモデルを扱うCollector Objectクラスを作成するとします。サンプルコードは以下の通りです。
class ArticlesCollector
attr_reader :published, :order_by
def initialize(published: nil, order_by: nil)
@published = published
@order_by = order_by
end
def call
articles = Article.all
articles = filtering(articles)
articles = ordering(articles)
articles
end
private
def filtering(articles)
return articles if published.blank?
articles.where(published: true)
end
def ordering(articles)
if order_by == 'title'
articles.order(:title)
else
articles.order(:created_at)
end
end
end
上記のcallメソッドはthen
を利用すると以下のように簡潔になります。
class ArticlesCollector
(略)
def call
Article.all
.then(&method(:filtering))
.then(&method(:ordering))
end
(略)
end
なお、上記のCollector Objectの利用方法および実行結果は以下の通りです。
### サンプルデータ
+-------+-----------+---------------------+
| title | published | created_at |
+-------+-----------+---------------------+
| foo | 0 | 2021-12-29 06:29:00 |
| bar | 0 | 2021-12-29 06:29:03 |
| baz | 1 | 2021-12-29 06:29:10 |
| qux | 1 | 2021-12-29 06:29:13 |
+-------+-----------+---------------------+
$ rails c
> ArticlesCollector.new.call.map(&:title)
=> ["foo", "bar", "baz", "qux"]
> ArticlesCollector.new(order_by: 'title').call.map(&:title)
=> ["bar", "baz", "foo", "qux"]
> ArticlesCollector.new(published: true, order_by: 'title').call.map(&:title)
=> ["baz", "qux"]
メソッドチェインの途中にロジックを挿入させたいとき
then
を利用することで既存のメソッドチェインの間にロジックを挿入できます。
以下はArticleモデルのメソッドチェインにfilter_by
というメソッドをthen
を利用して挿入した例です。
class ArticlesController < ApplicationController
def index
@articles = Article
.where(published: true)
.then do |relation|
filter_by(
relation,
params
)
end
.order(:title)
render json: @articles.map(&:title)
end
private
def filter_by(relation, params)
if params[:filter] == 'popular'
relation = relation.where(popular: true)
end
if params[:category].present?
relation = relation.where(category: params[:category])
end
relation
end
end
シンプルな絞り込みを実装する場合は、thenでメソッドを挿入するよりもモデルにscopeを定義するのが一般的です。
なお、上記のindexメソッドのレスポンス例は以下の通りです。
### サンプルデータ
+-----------------+-----------+---------+-------------+
| title | published | popular | category |
+-----------------+-----------+---------+-------------+
| qux | 1 | 0 | NULL |
| foo_programming | 1 | 1 | programming |
| bar_programming | 1 | 1 | programming |
| bar_diary | 1 | 1 | diary |
| baz_programming | 1 | 0 | programming |
+-----------------+-----------+---------+-------------+
### localhostでrails serverが起動している前提
$ curl http://localhost/articles
["bar_diary","bar_programming","baz_programming","foo_programming","qux"]
$ curl http://localhost/articles?filter=popular
["bar_diary","bar_programming","foo_programming"]
$ curl http://localhost/articles?category=programming
["bar_programming","baz_programming","foo_programming"]
$ curl http://localhost/articles?filter=popular&category=programming
["bar_programming","foo_programming"]
時刻の構築
yield_selfを使ったリファクタリングではthen
の利用例として『時刻の構築』を挙げています。具体的なコードと実行結果は以下の通りです。詳細については当該記事をご覧になってください。
class TimeConverter
attr_reader :year, :month
def initialize(year: nil, month: nil)
@year = year
@month = month
end
def call
Time.now.then { |time|
year ? time.change(year: year) : time
}.then { |time|
month ? time.change(month: month) : time
}
end
end
$ rails c
> Time.now
=> 2021-12-29 08:00:58.1003061 +0000
> TimeConverter.new.call
=> 2021-12-29 08:01:01.3205858 +0000
> TimeConverter.new(year: 2022).call
=> 2022-12-29 08:01:04.8489875 +0000
> TimeConverter.new(year: 2022, month: 1).call
=> 2022-01-29 08:01:07.699235 +0000
さいごに
Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!