2

In my project, a large number of POROs are composed from various data sources such as external APIs. The objects looks like:

{id: 1, name: 'Peter', age: 8}, {id: 2, name: 'Jack', age: 4}, {id: 3, name: 'Tom', age: 12}

I would like to have an ActiveRecord-like interface to query these objects. Such as Person.where(name: 'Jack') and Person.where("age > ?", 5)

My attempt looks like this:

class Query
    def initialize(objs)
      @objs = objs
    end

    def where(name: nil, age: nil)
      result = @objs 
      result = result.select{|x| x.name == name} if name
      result = result.select{|x| x.age == age}   if age
      result
    end
end

It works, but I don't think it is a nice solution:

  1. What if there are 20 attributes? The where method can become very long and error-prone.
  2. What about other useful ActiveRecord query? e.g. find, find_by, pluck, order by and many others. Even if I can implement them all, how do I "chain" multiple queries?
  3. Efficiency: How to optimize the queries like the sql query planner?
  4. Most importantly, how do I implement Person.where("age > ?", 5) and other flexible queries?

Am I missing anything? I feel like I am re-inventing the wheel.

I've checked ActiveModel but unfortunately it doesn't have a query system.

Are there any gems out there that can help?

mye
  • 103
  • 2
  • 6
  • You could probably do a reasonable job duplicating the functionality of `where`, but it would be a Herculean task to recreate the entire ActiveRecord query interface for in-memory objects. That's not a good subject for a StackOverflow question. – moveson Mar 10 '17 at 22:10
  • You are right. I don't want to implement the ActiveRecord interface; I'm looking for gems or tools that can do the heavy-lifting for me. – mye Mar 10 '17 at 22:17

1 Answers1

1

You would probably do well to use an object mapper, for example one from https://www.ruby-toolbox.com/categories/orm. An example implementation is shown at http://rom-rb.org/learn/repositories/reading-simple-objects/, but that is probably overkill for what you're doing.

Hashie has support for methods like deep_find for single objects and deep_locate for collections of objects. deep_locate may work for what you're doing, but keep in mind that a Hashie object will take up more memory than a standard hash.

Example code for deep_locate:

books = [
  {
    title: "Ruby for beginners",
    pages: 120
  },
  {
    title: "CSS for intermediates",
    pages: 80
  },
  {
    title: "Collection of ruby books",
    books: [
      {
        title: "Ruby for the rest of us",
        pages: 576
      }
    ]
  }
]

books.extend(Hashie::Extensions::DeepLocate)

# for ruby 1.9 leave *no* space between the lambda rocket and the braces
# http://ruby-journal.com/becareful-with-space-in-lambda-hash-rocket-syntax-between-ruby-1-dot-9-and-2-dot-0/

books.deep_locate -> (key, value, object) { key == :title && value.include?("Ruby") }
# => [{:title=>"Ruby for beginners", :pages=>120}, {:title=>"Ruby for the rest of us", :pages=>576}]

books.deep_locate -> (key, value, object) { key == :pages && value <= 120 }
# => [{:title=>"Ruby for beginners", :pages=>120}, {:title=>"CSS for intermediates", :pages=>80}]
anothermh
  • 9,815
  • 3
  • 33
  • 52