Skip to content

Instantly share code, notes, and snippets.

@boy-jer
Created November 21, 2013 21:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save boy-jer/7589684 to your computer and use it in GitHub Desktop.
Save boy-jer/7589684 to your computer and use it in GitHub Desktop.
mongoid-activerecord-eagerloadable source https://rubygems.org/gems/mongoid-activerecord-eagerloadable
module Mongoid
module ActiveRecord
module EagerLoadable
module Criteria
extend ActiveSupport::Concern
def includes_active_record(*relations)
relations.each do |relation|
metadata = RelationMetadata.new(relation)
active_record_inclusions.push(metadata) unless active_record_inclusions.include?(metadata)
end
clone
end
def active_record_inclusions
@active_record_inclusions ||= []
end
end
end
end
end
module Mongoid
module ActiveRecord
module EagerLoadable
module Eager
extend ActiveSupport::Concern
included do
alias_method :original_eager_load, :eager_load
alias_method :original_eager_loadable?, :eager_loadable?
def eager_load(docs)
load_active_record_inclusions(docs)
original_eager_load(docs)
end
def eager_loadable?(document = nil)
return false if criteria.inclusions.empty? && criteria.active_record_inclusions.empty?
document ? !inclusions_loaded?(document) : !eager_loaded
end
end
def load_active_record_inclusions(docs)
criteria.active_record_inclusions.each do |metadata|
metadata.eager_load(eager_loaded_ids(docs, metadata)) if !docs.empty?
end
end
end
end
end
end
require 'active_support/core_ext/module/delegation'
require 'mongoid/active_record/eager_loadable/version'
require 'mongoid/active_record/eager_loadable/eager'
require 'mongoid/active_record/eager_loadable/relation_metadata'
require 'mongoid/active_record/eager_loadable/criteria'
module Mongoid
module ActiveRecord
module EagerLoadable
extend ActiveSupport::Concern
included do
Mongoid::Contextual::Mongo.send(:include, Eager)
Mongoid::Contextual::Memory.send(:include, Eager)
Mongoid::Criteria.send(:include, Criteria)
@active_record_relations = {}
end
module ClassMethods
# todo: put this in Accessors similar to mongoid
def active_record_getter(name, metadata)
re_define_method name do
record_id = self.send(metadata.foreign_key)
IdentityMap.get(metadata.relation_class, record_id) || metadata.relation_class.find(record_id)
end
self
end
def active_record_relate(name, metadata)
@active_record_relations[name] = metadata
active_record_getter(name, metadata)
end
def belongs_to_active_record(association_id)
metadata = RelationMetadata.new(association_id)
active_record_relate(association_id, metadata)
end
end
end
end
end
require 'active_record'
require 'test/unit'
require 'mongoid'
require './lib/mongoid/active_record/eager_loadable.rb'
ENV['MONGOID_ENV'] = 'test'
Mongoid.load!('./config/mongoid.yml', :test)
ActiveRecord::Base.establish_connection(
adapter: 'sqlite3',
database: ':memory:'
)
require './test/support/models'
require './test/support/active_record/sql_counter.rb'
ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "../log/debug.log"))
ActiveSupport::Notifications.subscribe('sql.active_record', ActiveRecord::SQLCounter.new)
class EagerLoadableTest < ActiveRecord::TestCase
def setup
@jim = Person.create!(name: 'Jim')
@bill = Person.create!(name: 'Bill')
@bob = Person.create!(name: 'Bob')
@joe = Person.create!(name: 'Joe')
@jack = Person.create!(name: 'Jack')
@fletch = Pet.create!(name: 'Fletch', person_id: @jim.id)
@wilson = Pet.create!(name: 'Wilson', person_id: @bill.id)
@mocha = Pet.create!(name: 'Mocha', person_id: @bob.id)
@tanner = Pet.create!(name: 'Tanner', person_id: @joe.id)
@freckles = Pet.create!(name: 'Freckles', person_id: @jack.id)
end
def teardown
Person.delete_all
Pet.delete_all
end
def test_respond_to_belongs_to_active_record
assert_respond_to Pet, :belongs_to_active_record
end
def test_respond_to_includes_active_record
assert_respond_to Pet.scoped, :includes_active_record
end
def test_mongoid_model_responds_to_association_method
assert_respond_to Pet.first, :person
end
def test_mongoid_model_without_includes_active_record_finds_associated_model_instance
assert_kind_of Person, Pet.first.person
end
def test_mongoid_model_with_includes_active_record_finds_associated_model_instance
assert_kind_of Person, Pet.scoped.includes_active_record(:person).first.person
end
def test_getter_method_returns_proper_associatied_object_without_eager_loading
assert_equal Pet.first.person, Person.find(Pet.first.person_id)
end
def test_getter_method_returns_proper_associatied_object_with_eager_loading
assert_equal Pet.scoped.includes_active_record(:person).first.person,
Person.find(Pet.scoped.includes_active_record(:person).first.person_id)
end
def test_multiple_queries_execute_without_eager_loading
assert_queries(5) do
Pet.all.map(&:person)
end
end
def test_one_query_executes_with_eager_loading
assert_queries(1) do
Pet.scoped.includes_active_record(:person).map(&:person)
end
end
end
class Pet
include Mongoid::Document
include Mongoid::ActiveRecord::EagerLoadable
belongs_to_active_record :person
end
class Person < ActiveRecord::Base
attr_accessible :name
end
#The `belongs_to_active_record :person` statement enables the following
#to execute a single query against your mongodb instance and a single
#query against your ActiveRecord connection:
# Pet.scoped.includes_active_record(:person).all.map(&:person)
#Also, the `person` method is added to each instance of `Pet` regardless
#of whether or not the `includes_active_record` method is invoked
module Mongoid
module ActiveRecord
module EagerLoadable
class RelationMetadata
attr_reader :relation
def initialize(relation)
@relation = relation
relation_class.define_singleton_method(:collection_name) { table_name }
end
def eager_load(ids)
relation_class.where(id: ids.uniq).each do |record|
IdentityMap.set(record)
end
end
def relation_class
relation.to_s.classify.constantize
end
def foreign_key
"#{relation}_id"
end
def stores_foreign_key?
true
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment