Skip to content

Instantly share code, notes, and snippets.

@denyago
Created August 23, 2012 09:05
Show Gist options
  • Save denyago/3434469 to your computer and use it in GitHub Desktop.
Save denyago/3434469 to your computer and use it in GitHub Desktop.
Add belongs_to_remote to preload collections of ActiveResources without N+1 requests
source :rubygems
gem 'pg'
gem "hashie"
gem 'activerecord', require: 'active_record'
gem 'activesupport'
gem 'activeresource', :require => 'active_resource'
gem "debugger"
require 'bundler/setup'
Bundler.require :default
require 'debugger'
require 'active_record'
require 'active_support'
require 'active_resource'
ActiveRecord::Base.establish_connection(
{ adapter: 'postgresql',
encoding: 'unicode',
database: 'ar_dm2',
host: 'localhost',
port: 5432,
pool: 5,
username: 'rails',
password: 'rails'}
)
ActiveRecord::Base.connection.execute(<<SQL
DROP TABLE IF EXISTS "public"."users";
DROP SEQUENCE IF EXISTS "users_id_seq";
CREATE SEQUENCE "users_id_seq" INCREMENT 1 START 1 MAXVALUE 9223372036854775807 MINVALUE 1 CACHE 1;
ALTER TABLE "users_id_seq" OWNER TO "rails";
CREATE TABLE "public"."users" (
"id" int4 NOT NULL DEFAULT nextval('users_id_seq'::regclass),
"name" varchar(255) NOT NULL,
"age" int4,
CONSTRAINT "users_pkey" PRIMARY KEY ("id") NOT DEFERRABLE INITIALLY IMMEDIATE
)
WITH (OIDS=FALSE);
ALTER TABLE "public"."users" OWNER TO "rails";
insert into "public"."users" ( "name", "age") values ( 'octocat', 18);
insert into "public"."users" ( "name", "age") values ( 'dhh', 26);
SQL
)
class Profile < ActiveResource::Base
self.site = "http://127.0.0.1:3000"
end
class Facebook < ActiveResource::Base
self.site = "http://127.0.0.1:3000"
end
module RemoteBelonger
def self.included(base)
base.extend ClassMethods
base.send :include, InstanceMethods
base.class_eval do
end
end
module ClassMethods
def belongs_to_remote(remote_rel, options ={})
rel = activeresource_relations
rel[remote_rel.to_sym] = {
klass: ( options[:class_name] ? options[:class_name].constantize : remote_rel.to_s.classify.constantize),
join_key: ( options[:foreign_key] ? options[:foreign_key] : self.model_name.to_s.foreign_key )
}
class_eval <<-RUBY, __FILE__, __LINE__+1
attr_accessor :#{remote_rel}
def #{remote_rel}
if @remote_resources_prefetched == true
@#{remote_rel} ? @#{remote_rel}.first : nil
else
@#{remote_rel} ||= #{rel[remote_rel.to_sym][:klass].to_s}.find(:first, params: { #{rel[remote_rel.to_sym][:join_key]}: [self.id]})
end
end
RUBY
@activeresource_relations = rel
end
def has_many_remote(remote_rel, options ={})
end
def activeresource_relations
@activeresource_relations ||= {}
end
end
module InstanceMethods
end
end
class User < ActiveRecord::Base
include RemoteBelonger
validates :name, presence: true
belongs_to_remote :profile, class_name: "Profile", foreign_key: 'user_id'
belongs_to_remote :facebook
delegate :likes, :to => :profile, :prefix => true, :allow_nil => true
delegate :hates, :to => :facebook, :prefix => true, :allow_nil => true
end
module ActiveRecord
class Relation
def prefetch_remote_resources
keys = self.map(&:id)
klass.activeresource_relations.each do |k,d|
join_key = d[:join_key]
activeresource_accessor = k.to_s
activeresource_klass = d[:klass]
set = activeresource_klass.find(:all, :params => { join_key => keys })
self.each do |u|
u.send("#{activeresource_accessor}=", set.select {|s| s.send(join_key) == u.id })
u.instance_variable_set(:@remote_resources_prefetched, true)
end
end
self.all
end
end
end
puts User.scoped.prefetch_remote_resources.to_json(methods: [:profile_likes, :facebook_hates])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment