开发者

How do I modify Rails ActiveRecord query results before returning?

I have a table with data that needs to be updated at run-time by additional data from an external service. What I'd like to do is something like this:

MyModel.some_custom_scope.some_other_scope.enhance_with_external_data.each do |object|
  puts object.some_attribute_from_external_data_source
end

Even if I can't use this exact syntax, I would like the end result to respect any scopes I may use. I've tried this:

def self.enhance_with_external_data
  external_data = get_external_data
  E开发者_如何学编程numerator.new do |yielder|
    # mimick some stuff I saw in ActiveRecord and don't quite understand:
    relation.to_a.each do |obj|
      update_obj_with_external_data(obj)
      yielder.yield(obj)
    end
  end
end

This mostly works, except it doesn't respect any previous scopes that were applied, so if I do this:

MyModel.some_custom_scope.some_other_scope.enhance_with_external_data

It gives back ALL MyModels, not just the ones scoped by some_custom_scope and some_other_scope.

Hopefully what I'm trying to do makes sense. Anyone know how to do it, or am I trying to put a square peg in a round hole?


I figured out a way to do this. Kind of ugly, but seems to work:

def self.merge_with_extra_info
  the_scope = scoped
  class << the_scope
    alias :base_to_a :to_a
    def to_a
      MyModel.enhance(base_to_a)
    end
  end
  the_scope
end

def self.enhance(items)
  items.each do |item|
    item = add_extra_item_info(item)
  end
  items
end

What this does is add a class method to my model - which for reasons unknown to me seems to also make it available to ActiveRecord::Relation instances. It overrides, just for the current scope object, the to_a method that gets called to get the records. That lets me add extra info to each record before returning. So now I get all the chainability and everything like:

MyModel.where(:some_attribute => some_value).merge_with_extra_info.limit(10).all

I'd have liked to be able to get at it as it enumerates versus after it's put into an array like here, but couldn't figure out how to get that deep into AR/Arel.


I achieved something similar to this by extending the relation:

class EnhancedModel < DelegateClass(Model)
  def initialize(model, extra_data)
    super(model)
    @extra_data = extra_data
  end

  def use_extra_data
    @extra_data.whatever
  end
end

module EnhanceResults
  def to_a
    extra_data = get_data_from_external_source(...)
    super.to_a.map do |model_obj|
      EnhancedModel.new(model_obj, extra_data)
    end
  end
end

models = Model.where('condition')
models.extend(EnhanceResults)
models.each do |enhanced_model|
  enhanced_model.use_extra_data
end
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜