Skip to content

Commit

Permalink
add AST cache to the find_by method
Browse files Browse the repository at this point in the history
  • Loading branch information
tenderlove committed Feb 17, 2014
1 parent 23ce2f6 commit 77b18d7
Showing 1 changed file with 35 additions and 0 deletions.
35 changes: 35 additions & 0 deletions activerecord/lib/active_record/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down

3 comments on commit 77b18d7

@claudiob
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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 🍭

@rafaelfranca
Copy link
Member

@rafaelfranca rafaelfranca commented on 77b18d7 Oct 19, 2014 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@claudiob
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!

Please sign in to comment.