Wondershake 開発者ブログ

LOCARI(ロカリ)の運営会社の開発者ブログです。

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 さんがリモート以外のテーマで何か書いてくれるみたいです。楽しみ 😁 ❤️

References