4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RailsのモデルをVisitorパターンとカリー化ですっきり辿る

Posted at

以下の記事の続きです。

Visitorパターンでは各モデルを処理するメソッドの引数を複数にしたい状況が結構あります。

手前のノードと現在のノードを比較する処理をしたいケースを例として説明します。


# 各モデルが visitor.visit(self) と呼び出す。
def visit(object)
 if object.is_a?(Task)
  task(object)
 elsif object.is_a?(Step)
  step(object)
 end
end

def step(step)
 -> (prev_step) {
   # 前と後で比較する処理
 }
end

def task(task)
  prev_step = nil
  task.steps.each{|step|
    step.accept(self).(prev_step)
    prev_step = step
  } 
end

この例ではstepメソッドがlambdaを返すので、呼出し側で追加の引数を指定して処理を行います。

これをカリー化して第一引数を固定した関数を返すようにすると以下のように書けます。

# 各モデルが visitor.visit(self) と呼び出す。
# curryingで第一引数にモデルを指定しておく。
def visit(object)
 if object.is_a?(Task)
  self.method(:task).curry.(object)
 elsif object.is_a?(Step)
  self.method(:step).curry.(object)
 end
end

def step(step, prev_step)
  # 前と後で差分を集計する等
end

def task(task)
prev_step = nil
  task.steps.each{|step|
    step.accept(self).(prev_step)
    prev_step = step
  } 
end

Visitorの実装が増えてノードの扱いが複雑化してくると、#visitのディスパッチ時点でカリー化されているメリットを感じると思います。

以下、全体を通しで見たコードです。

module VisitorAcceptance
  def accept(visitor)
    visitor.visit(self)
  end
end

class Project < ApplicationRecord
  has_many :tasks
  include VisitorAcceptance
end

class Task < ApplicationRecord
  belongs_to :project
  has_many :steps
  include VisitorAcceptance
end

class Step < ApplicationRecord
  belongs_to :task
  include VisitorAcceptance
end

class Visitor
  def visit(object)
    if object.is_a?(Task)
      self.method(:task).curry.(object)
    elsif object.is_a?(Step)
      self.method(:step).curry.(object)
    elsif object.is_a?(Project)
      self.method(:project).curry.(object)
    end
  end
end

class ConcreteVisitor < Visitor

  private

  def step(step, prev_step)
    # 前と後で比較する処理
  end

  def task(task)
    prev_step = nil
    task.steps.each{|step|
      step.accept(self).(prev_step)
      prev_step = step
    }
  end

  def project(project)
    project.tasks.each{|task|
      task.accept(self)
    }
  end

end

すっきり。

4
4
0

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?