Project

priloo

0.0
No commit activity in last 3 years
No release in over 3 years
A generalized Rails-like preloader
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 0.1
~> 0.8
~> 0
~> 3
= 0.59.2
~> 1.3

Runtime

~> 5.2.1
~> 0.3
 Project Readme

Priloo

Build Status Coverage Status Gem Downloads

Description

Priloo is a generalized preloader. It is inspired from ActiveRecord#preload(...), but it works for any object implementing its preloading protocol.

Property tree

Priloo accepts a collection and a tree of properties. The syntax is very similar to ActiveRecord#preload. The only difference is the special property __each__, used to visit items of a collection.

# ActiveRecord
collection.preload(user: { posts: :attachment })

# Priloo
collection.priload(user: { posts: { __each__: :attachment } })

ActiveRecord integration

Priloo integrates natively with ActiveRecord's preloader. The advantage if that you can traverse both AR and non-AR object, or have add custom preloadable properties to your ARs.

BABL integration

BABL can be configured to use Priloo by default.

::Babl.configure do |config|
    config.preloader = Priloo
end

BABL extracts the property tree from the template, and passes it to Priloo. No more N+1!

Preloading protocol

In order to have a preloadable property foo, an object must respond to the method foo__priloo__ and return a preloader.

All objects having the same preloader are loaded together using preloader.preload([...]).

If a property is not preloadable, Priloo fallbacks to calling the method (or access the property, for Hash). If the method doesn't exist, the error is ignored.

Custom preloadable properties

PreloadDependencies()

PreloadDependencies can be used to ensure properties are preloaded before the property is computed.

class Post
end

class User
    include Priloo::Preloadable

    has_many :posts

    # This decorator tells Priloo to load all posts, before
    # calling the method.
    PreloadDependencies(:posts)
    def number_of_likes
        posts.map(&:likes)
    end
end

# Usage
users.priload(:number_of_likes).each(&:number_of_likes) # No N+1, all posts are loaded at once.

PreloadBatch()

PreloadBatch is more general and makes it possible to write a completely custom preloading logic.

class Post
end

class User
    include Priloo::Preloadable

    has_many :posts

    # This decorator tells Priloo how to preload 'number_of_likes' for a collection of users.
    # For convenience, it also creates a similarly-named instance method.
    PreloadBatch()
    def self.number_of_likes(users)
        users.map { ... }
    end
end

# Usage
users.priload(:number_of_likes).each(&:number_of_likes) # No N+1

Install

gem 'priloo'

Limitations

This gem is a PoC and has certain limitations which makes it unsuitable for production, unless you know what you're doing.

  • The current implementation assumes that all objects at the same level have the same dependencies. If that's not the case, an exception is raised.

  • This gem is badly tested. Some parts are not even tested at all.

  • Error handling sucks. We should definitely adopt a "fail-fast" approach, instead of "ignore errors".

  • ActiveRecord integration relies on Rails internals, and the way we're calling native ActiveRecord::Preloader isn't safe (see rails/rails#32140).

License

Copyright (c) 2018 Bannerman, Frederic Terrazzoni

Licensed under the MIT license.