QueryObject for Rails - how to encapsulate set of operations on ActiveRecord -
how can encapsulate set of operations (most of .where(...)) , apply different models, in way of models may not implement of operations , should return empty collections. (non-essential code skipped)
what have designed (but not satisfied with):
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 of them suported modelc rescue nomethoderror return [] end end
usage
query = activityquery.new.created_before(last_time).with_hash(...) activities = activityfinder.find(query)
what don't like:
- the rescue nomethoderror
- if different models has different name field, has handled
case
statement in query, coupling in way query object each of models
so i'm searching suggestions better implementation
update
the problem want pass object got activemodel, e.g. activerecord::relation, can't define module methods (and override when needed) , include in models i'm using. question more point in right direction clean design, , not implementation details, i'll figure out somehow
to avoid tight coupling, put model specific code model:
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
now have consistent interface can program against:
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
no need rescue nomethoderror
, no more fiddling model's implementation details.
Comments
Post a Comment