Permalink
Showing with 35 additions and 0 deletions.
  1. +35 −0 activerecord/lib/active_record/core.rb
@@ -94,6 +94,7 @@ def self.disable_implicit_join_references=(value)
end
class_attribute :default_connection_handler, instance_writer: false
+ class_attribute :find_by_statement_cache
def self.connection_handler
ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
@@ -107,6 +108,40 @@ def self.connection_handler=(handler)
end
module ClassMethods
+ def initialize_find_by_cache
+ self.find_by_statement_cache = {}.extend(Mutex_m)
+ end
+
+ def inherited(child_class)
+ child_class.initialize_find_by_cache
+ super
+ end
+
+ def find_by(*args)
+ return super if current_scope || args.length > 1 || reflect_on_all_aggregations.any?
+
+ hash = args.first
+
+ return super if hash.values.any? { |v| v.nil? || Array === v }
+
+ key = hash.keys
+
+ klass = self
+ s = find_by_statement_cache[key] || find_by_statement_cache.synchronize {
+ find_by_statement_cache[key] ||= StatementCache.new { |params|
+ wheres = key.each_with_object({}) { |param,o|
+ o[param] = params[param]
+ }
+ klass.where(wheres).limit(1)
+ }
+ }
+ begin
+ s.execute(hash).first
+ rescue TypeError => e
+ raise ActiveRecord::StatementInvalid.new(e.message, e)
+ end
+ end
+
def initialize_generated_modules
super

3 comments on commit 77b18d7

Member

claudiob replied Oct 19, 2014

@tenderlove – I read through your (amazing) AdequateRecord contributions to ActiveRecord, and it got me thinking:

What if we could decouple these optimizations and its Relation class and expose them as part of ActiveModel?


Let me explain this statement and take you on the 🎢 that's in my mind.
As you illustrated in your post, ActiveRecord constructs SQL queries after doing a few transformations: Relation -> ARel::Node -> SQLString.

What if there was no database at all? Can you think of any other piece of code where it would be useful to:

  • have a syntax like Person.find_by(name: 'Aaron') or Person.find(3), and
  • cache the static part and/or the result if those queries are run multiple times?

Well, I think that a perfect example is ActiveResource! ActiveResource inherits from ActiveModel, but then has to newly define methods like find to feel like ActiveRecord.

Instead, if ActiveModel itself exposed the Relation class with its modules (FinderMethods, QueryMethods, …), then both ActiveRecord and ActiveResource would benefit from the caching optimization and share the same public interface.

The implementation, of course, would be different (in ActiveRecord User.find(1) would mean SELECT * FROM users WHERE id=1, in ActiveResource it would mean GET /users/1), but having a common public interface would… be great!


I know this a is a very long shot… and I might be completely missing something, I just thought I'd share and wait for your feedback! Thanks for you amazing work 🍭

Owner

rafaelfranca replied Oct 19, 2014

Member

claudiob replied Oct 19, 2014

Sounds good!

Please sign in to comment.