class_evalでDRYにしてみたかったけど

Railsアプリで書いたビューのためのヘルパークラスをしつこくリファクタリング。以下の2つのクラスがDRYじゃない。今後、LinkedInも足すかもしれないし。

class TwitterPhotoUrl < PhotoUrl
  def path
    case @options[:size]
    when :small
      add_suffix_to_path(@user.remote_photo_url, "_normal")
    when :normal
      add_suffix_to_path(@user.remote_photo_url, "_bigger")
    when :big
      @user.remote_photo_url
    else
      raise "Invalid size option for photo_tag"
    end
  end
end
  
class FacebookPhotoUrl < PhotoUrl
  def path
    case @options[:size]
    when :small
      add_suffix_to_path(@user.remote_photo_url, "_q")
    when :normal
      add_suffix_to_path(@user.remote_photo_url, "_q")
    when :big
      add_suffix_to_path(@user.remote_photo_url, "_b")
    else
      raise "Invalid size option for photo_tag"
    end
  end
end

まず、TwitterPhotoUrlとかFacebookPhotoUrlという名前がいけない。外側を「module PhotoUrl」とくくって、トップレベルの名前空間の汚染は1つだけとし、その中にBase、TwitterFacebook、Localの4つクラスを用意した。Base以外はBaseを継承している。

で、次にPhotoUrl::Twitterクラスと、PhotoUrl::Facebookクラスを動的に定義するようにしてみた。

  Mapping = { 
    twitter: { small:"_normal", normal:"_bigger", big:""   },
    facebook:{ small:"_q",      normal:"_q",      big:"_b" },
  }

  %w(twitter facebook).each do |provider|
    c = Class.new(Base)
    c.class_eval <<-DEF
      def path
        size = @options[:size]
        if [:small, :normal, :big].include?(size)
          add_suffix_to_path(@user.remote_photo_url,
            PhotoUrl::Mapping[:#{provider}][size])
        else
          raise "Invalid size option for photo_tag"
        end
       end
    DEF
    PhotoUrl.const_set(provider.capitalize, c)
  end

ちゃんと意図通りに動いてるけど、これはやりすぎ感がある。単にclass_evalを使ってみたかっただけで、「なるほどなー」という感じ。そもそも、PhotoUrl::Mappingというハッシュを定義するんなら、TwitterFacebookでクラスを分ける必要もない。