Ruby の Refinements 使ってみた
この投稿は Wondershake Advent Calendar 2016 12日目の記事です。
mmyoji です。今日含めてあと3回、がんばります。
今日は Ruby の Refinements について、「こんな感じで使ってますよー」という紹介をできればと思います。
簡単にいうとクラスを「特定の箇所(ファイル)でだけ拡張したい時」に使うもの、と思っていただければいいと思います。もっと詳しく知りたい方は最後に挙げてある References を確認していただけると理解がしやすいかと思います。
自分も最近ようやく使ってみた、という感じなのでこれが適切な使い方なのかは怪しいです。。。ただ使ってみてかなり「気持ちよかった」のでやっぱり Ruby は最高ですね。最高です(大事なことなので(ry
具体例
LOCARI の記事1つ1つに対して、ライターが一人いて、それを記事一覧で表示するんですが、記事の状態などに応じて出し分けをする必要があります。その ライターの情報 を Post::ListProfile
というクラスで表現するようにしていて、そのプロフィールに種類があります。
ListProfile
を特定するために記事の状態をチェックする必要がありますが、そこでしか使われないため、単に記事クラス( Post
)にメソッドを生やしたくない、と思い、 Refinements を使ってスコープを制限しました
(以下微妙に実際のクラス名、メソッド名とは異なります)
# app/models/post.rb # 記事クラス class Post < ApplicationRecord # ... end # app/models/post/list_profile_detector.rb # 記事を元にライター情報を特定するクラス class Post::ListProfileDetector attr_reader :post def initialize(post) @post = post end def call if post.show_username? return Post::ListProfile::External.new(post) end if post.shared? return Post::ListProfile::Shared.new(post) end if post.default? return Post::ListProfile::Default.new(post) end Post::ListProfile::Official.new(post) end end # こんな感じで使う post = Post.find(12) profile = Post::ListProfileDetector.new(post).call #=> #<Post::ListProfile::Default:0x007f983e757550
上記の ListProfileDetector
内で使った post
に生えているメソッドを Refinements を使って定義します
# app/models/post/list_profile/detectable.rb module Post::ListProfile::Detectable refine ::Post do def show_username? # ... end def shared? # ... end def default? # ... end end end
簡単ですね 😁
で、実は Post::ListProfileDetector
には足りていない行があったので一行追加します
# app/models/post/list_profile_detector.rb class Post::ListProfileDetector # `using` で refine を使う宣言をする using Post::ListProfile::Detectable attr_reader :post # ... end
これで一応は使えるようになりました。今度はテストです。
Post::ListProfileDetector
はそのままテストを書いて問題ないですが、 Detectable
モジュールはどうテストしましょう?(そんなに難しいものではないですが 😅 )
# spec/models/post/list_profile/detectable_spec.rb require 'rails_helper' describe Post::ListProfile::Detectable do ## これがないと有効にならない! ## using Post::ListProfile::Detectable let(:post) { create(:post) } describe '#show_username?' do # いつものように example を書く end end
Refinements は ファイル毎 にスコープを指定するため、メソッド呼び出しを行いたい場合は各ファイルで using
を宣言する必要があるため、テストでも上記のようにする必要があります。
気づいてないだけで意外と使えるところはたくさんあるのかなーと思うので、これからもガシガシ使っていきたいですね!
以上になります。明日は Eric さんがリモート以外のテーマで何か書いてくれるみたいです。楽しみ 😁 ❤️