Adam Laycock

Adam Laycock

EdTech Network Manager & Developer

Active Record like Search Results

Active Records output may look like an array but its actually a special class that behaves like an array but has a lot of extensions attached to it.

For Adauth I want to provide results in something more than an array with the ability to order etc… but not over complicate things for the end user (if you just want an array you can still get it). So how do we make something like AR?

So straight away lets create a new class that inherits from Array and to begin with it needs no extra methods because Array.new can take an array and obviosuly that will return our new object acting as an Array.

This new class would look like this

class SearchResults < Array
end

That now means that you can create your new class quickly from array returns by calling SearchResults.new(SomeThing.query("foo")) instead of just SomeThing.query("foo").

Now that we have our class lets start using it! First and easiest functionality is limit(x) which is pretty simple 1 line function

def limit(x)
    return self[0..(x-1)]
end

This simply returns a new instance of self that is limited to the range of 0 to the supplied value.

The next function to bring to the table is order which should take a couple of options e.g. order(:name, :asc) which should look something like this

def order(field, direction)
    case direction
    when :asc
        return sort! { |x, y| x.send(field) <=> y.send(field) }
    when :desc
        return order(field, :asc).reverse!
    else
        raise "Invalid Order Provided, please use :asc or :desc"
    end
end

This assumes that your search results are objects with the data retrievable via methods but you can sort your objects how ever you want as long as they match what your user is asking for. The advantage to this setup is that your code becomes very “englishy” and is very easy to read.

Taking my example from earlier of SearchResults.new(SomeThing.query("foo")) you can now:

SearchResults.new(SomeThing.query("foo")).limit(3)
SearchResults.new(SomeThing.query("foo")).order(:age, :desc)
SearchResults.new(SomeThing.query("foo")).order(:name, :asc).limit(2)

Things look even nicer if your gem returns its data using your search results class as you wont need the .new so it looks like this:

YourGem.some_dataset.order(:name, :asc)
YourGem.all.limit(3)

The rest of the Array methods are still there and will function exactly the same.

Be warned, any none destructive methods (no !) will return a new Array not a new SearchResults

The finished class would look like this

class SearchResults < Array
    def limit(x)
        return self[0..(x-1)]
    end

    def order(field, direction)
        case direction
            when :asc
                return sort! { |x, y| x.send(field) <=> y.send(field) }
            when :desc
                return order(field, :asc).reverse!
        else
            raise "Invalid Order Provided, please use :asc or :desc"
        end
    end
end

Hardly a huge class is it? This is a little rough around the edges but it serves its purpose and when combined with AR like search methods will be incredibly easy to read and use.