LoginSignup
4
4

More than 5 years have passed since last update.

メタプログラミングの考えをRubyで使用するときのパターン

Posted at

メタプログラミングとは

「コードを記述するコードを記述すること」と一般的には呼ばれている。

rubyのメソッドである「find」「find_each」「find_in_batches」も抽象化して考えることができる(メタプログラミングかどうか実際のところ、調査不足でわからないが)

どういうときに使用するのか

そもそも・・メタプログラミングすると動的にプログラムを実行することができる。プログラムの一部をパラメーター化することでコードの重複を取り除くこともできる。うまく使えばリーダブルにもなる。がしかし、使い方によっては理解しにくくなったり、メンテナンスしにくくなったりする。

処理を1箇所に局所化できるか、、、言いかえると、
「メタプログラミングでやっていることの詳細を知らなくても使えるくらい抽象化できるか」

どうやって使用していくのか

主に2パターンだと考えてよい

パターン1「モンキーパッチ」
既存のクラスを拡張し上書きすること

class Fixnum
  def +(v)
    self - v
  end
  def *(v)
    self / v
  end
end
puts 42 + 42
puts 42 * 42

パターン2「動的なメソッド定義」
似たような処理を持つメソッド群を、まとめて定義すること
(具体例でコードを出します)

そのときに気をつけるべきポイントは

パターン1
全ての文字列が保持しても問題のない汎用的な機能か見極めることが大事
→意図していない挙動になりバグにつながる

パターン2
method_missing
「未定義メソッドに対しても好きな処理を加えられる」
→バグが起こりやすい、起こっても特定しにくい
→唯一性を考えてメソッドを作成する必要がある

実際の例

パターン1に関してはあまり実行する機会がなさそうなので今回はパターン2のみ
扱います。

<% if request.path=~ /^\/top/ %>
  <%# ITエンジニア %>
  <% if @login_user.exp_job_types == "10" %>
    <% job = engineer %>
  <%# デザイナー %>
  <% else @login_user.exp_job_types == "20" %>
    <% job = designer %>
  <% end %>
<% end %>
  #経験職種(大分類)
  def experienced_job?(login_user,type)
    login_user.exp_job_types.present? && login_user.exp_job_types == number
  end
@login_user.experienced_job?(@login_user,JobType::IT_ENGINEER)

rubyは引数でどうこうする言語ではない。
つまり今回experienced_parent_job_engineer?とかexperienced_parent_job_creative_web?とかってメソッドがそれぞれ別個で存在するように作っているのはRuby的には違和感がない。。が・・・今後もこの形のメソッドを作成する可能性は十二分にあることはなんとなくわかる。また同じメソッドがいくつもあるのには違和感がある→メタプログラミングだっ!

   def experienced_job?(number)
      exp_job_types.present? && exp_job_types == number
    end

    def method_missing(method_name,*args)
      #正規表現でメソッド呼びだし
      method_name.to_s.match(/^experienced_parent_job_(\w+)\?$/) do|ms|
        super unless JobType.const_defined?("#{ms[1].upcase}")
        return experienced_job?(eval"JobType::" + ms[1].upcase)
      end
      super
    end

おまけ(マッチした複数の文字列を取得)

よく見かけるのは正規表現オブジェクトのパターンにマッチした文字列を取得するというもの。ただ1つのパターンをいくつかに分割し、それぞれの部分にマッチした文字列を取得することも可能であり、このパターンのものがメタプログラミングのときにはよく目につく。

def method_missing(method_name, *args)
    if /^(.+)_with_save(\!?)$/.match(method_name)
      if defined? $1
        send($1,*args)
        if $2.blank?
          return self.save
        else
          return self.save!
        end
      end
    end
・
・
・
・
end

マッチすると括弧の数だけ変数「\$1」や変数「\$2」に部分文字列が代入されていきます。順番はパターンの中でマッチ箇所が現れた順となります。

4
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4