4

一部のモデルが一部の操作を実装せず、空のコレクションを返す必要があるように、一連の操作 (そのほとんどは本質的に .where(...) です) をカプセル化し、それをさまざまなモデルに適用するにはどうすればよいですか。(重要でないコードはスキップされます)

私が設計したもの(しかし満足していない):

class ActivityFinder

  def self.find(query, limit = nil)
    activities = get_activities(query)
    activities = activities.sort_by(&:created_at).reverse // some-kind of merge-sort
    activities = activities.slice(0, limit) if limit.present?
    activities
  end

  private

  def self.get_activities(query)
    activities = []
    activities += query.apply(ModelA)
    activities += query.apply(ModelB)
    activities += query.apply(ModelC)
    activities
  end
end

class ActivityQuery

  def created_before(time)
    @created_before = time
    self
  end

  def created_after(time)
    @created_after = time
    self
  end

  def apply(activity)
    activity = activity.where("#{activity.table_name}.created_at < ?", @created_before) if @created_before.present?
    activity = activity.where("#{activity.table_name}.created_at >= ?", @created_after) if @created_after.present?
    // more operations, not all of them are suported by ModelC
  rescue NoMethodError
    return []
  end
end

使用法

query = ActivityQuery.new.created_before(last_time).with_hash(...)
activities = ActivityFinder.find(query)

嫌いなもの:

  • NoMethodError のレスキュー
  • モデルごとにフィールドの名前が異なる場合はcase、クエリ内のステートメントとして処理する必要があり、この方法でクエリ オブジェクトを各モデルに結合します。

だから私はより良い実装のための提案を探しています

アップデート

問題は、ActiveModel から取得した任意のオブジェクト (ActiveRecord::Relation など) を渡したいため、メソッドを使用してモジュールを定義し (必要に応じてオーバーライドし)、使用しているモデルに含めることができないことです。 . 問題は、クリーンなデザインの正しい方向を示すことであり、実装の詳細についてではありません。

4

4 に答える 4

2

密結合を避けるには、モデル固有のコードをモデルに入れます。

class ModelA < ActiveRecord::Base
  scope :created_before, ->(timestamp) { where("created_at < ?", timestamp) }
  scope :created_after, ->(timestamp) { where("created_at >= ?", timestamp) }
  scope :with_hash, ->(hash) { ... }
end

class ModelB < ActiveRecord::Base
  scope :created_before, ->(timestamp) { where("other_column < ?", timestamp) }
  scope :created_after, ->(timestamp) { where("other_column >= ?", timestamp) }
  scope :with_hash, where('false') # empty, chain-able collection
end

これで、プログラミングできる一貫したインターフェースができました。

class ActivityQuery

  def apply(activity)
    activity = activity.scoped
    activity = activity.created_before(@created_before) if @created_before.present?
    activity = activity.created_after(@created_after) if @created_after.present?
    activity = activity.with_hash(@hash) if @hash.present?
    activity
  end

end

NoMethodErrorモデルの実装の詳細をレスキューしたりいじったりする必要はありません。

于 2013-04-18T14:34:28.747 に答える