Created
June 28, 2015 12:53
-
-
Save nicoolas25/057c8be2127c4cb2f0a9 to your computer and use it in GitHub Desktop.
Custom relation with caching and preloading (ActiveRecord)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "active_support/concern" | |
require "loadable/relation" | |
module Loadable | |
module Model extend ActiveSupport::Concern | |
module ClassMethods | |
def loadable(name, options) | |
loadable_relations[name] = build_relation(name, options) | |
define_reader(name) | |
end | |
def loadable_relations(name=nil) | |
@loadable_relations ||= {} | |
if name | |
@loadable_relations.fetch(name).new | |
else | |
@loadable_relations | |
end | |
end | |
private | |
def build_relation(name, options) | |
Relation.build(self, name, options) | |
end | |
def define_reader(name) | |
self.class_eval <<-CODE, __FILE__, __LINE__ + 1 | |
def #{name}_relation | |
loadable_relations(:#{name}) | |
end | |
def #{name} | |
#{name}_relation.load | |
end | |
CODE | |
end | |
end | |
def loadable_relations(name) | |
@loadable_loaded_relations ||= {} | |
@loadable_loaded_relations[name] ||= | |
self.class.loadable_relations(name).tap do |relation| | |
relation.instance = self | |
end | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Loadable | |
class Relation | |
extend Forwardable | |
class << self | |
def build(model_class, name, options) | |
Class.new(self) do | |
self.model = model_class | |
self.name = name | |
self.loader = options.fetch(:with).new | |
end | |
end | |
attr_accessor :model, :name, :loader | |
end | |
attr_accessor :instance | |
def load(collection=nil) | |
if instance.present? | |
load_instance | |
elsif collection.present? | |
load_collection(collection) | |
end | |
end | |
def loaded? | |
!!@elements | |
end | |
def loaded!(elements) | |
@elements = elements | |
end | |
private | |
def load_instance | |
return @elements if loaded? | |
loader.on_instance(instance).tap { |elements| loaded!(elements) } | |
end | |
def load_collection(collection) | |
loader.on_collection(collection).tap do |elements| | |
collection.each do |instance| | |
matches = elements.select { |element| loader.related?(instance, element) } | |
instance.__send__("#{name}_relation").loaded!(matches) | |
end | |
end | |
end | |
def_delegators "self.class", :model, :name, :loader | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Keyword < ActiveRecord::Base | |
has_and_belongs_to_many :articles | |
end | |
class Article < ActiveRecord::Base | |
has_and_belongs_to_many :keywords | |
include ::Loadable::Model | |
loadable :related_articles, with: Loader::RelatedArticles | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
articles = Article.where(author: author).load | |
# Load the related article using the `RelatedArticles#on_instance` method | |
articles.first.related_articles # Triggers a SQL query | |
# Preload all the related articles of the Article array using the | |
# `RelatedArticles#on_collection` method then use the `RelatedArticles#relaed?` | |
# method to dispatch the related_articles into the `articles` array. | |
Article.loadable_relations(:related_articles).load(articles) | |
# Everything is already loaded | |
articles.last.related_articles # Does not trigger any SQL query |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment