Skip to content

Instantly share code, notes, and snippets.

@sinsoku
Last active May 14, 2019 08:52
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 sinsoku/b5b800570ec95ac4526ed973dfcd131f to your computer and use it in GitHub Desktop.
Save sinsoku/b5b800570ec95ac4526ed973dfcd131f to your computer and use it in GitHub Desktop.
`lazy_find_by` returns AR instance that executes queries when attributes is needed.
## Simple UseCase
user = User.lazy_find_by(id: 1, name: "foo")
#=> #<User id: 1, name: "foo", age: nil, created_at: nil, updated_at: nil>
user.id
#=> 1
user.name
#=> "foo"
user.age
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
#=> 1
## Use has_many
user_2 = User.lazy_find_by(id: 1)
user_2.posts.find_by(id: 1)
Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = ? AND "posts"."id" = ? LIMIT ? [["user_id", 1], ["id", 1], ["LIMIT", 1]]
#=> #<Post id: 1, user_id: 1, title: "bar", created_at: "2019-05-10 15:44:39", updated_at: "2019-05-10 15:44:39">
user_3 = User.lazy_find_by(id: 1)
post = user_3.posts.create(title: "buz")
User Load (3.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
(0.1ms) begin transaction
Post Create (3.0ms) INSERT INTO "posts" ("user_id", "title", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["user_id", 1], ["title", "buz"], ["created_at", "2019-05-10 17:12:06.418763"], ["updated_at", "2019-05-10 17:12:06.418763"]]
(1.5ms) commit transaction
#=> #<Post id: 4, user_id: 1, title: "buz", created_at: "2019-05-10 17:12:06", updated_at: "2019-05-10 17:12:06">
# frozen_string_literal: true
module ActiveRecordLazyFindBy
STATE_METHOD_NAMES = %i[valid? new_record? persisted?].freeze
class << self
def module_for(klass, attributes)
lazy_attr_names = (klass.attribute_names - attributes.keys.map(&:to_s)).sort
key = [klass, lazy_attr_names]
cache[key] ||= build(lazy_attr_names)
end
def cache
@cache ||= {}
end
private
def build(attr_names)
lazy_method_names = attr_names.flat_map { |x| [x, "#{x}?", "#{x}="] } + STATE_METHOD_NAMES
Module.new do
lazy_method_names.each do |m|
define_method(m) do |*args|
unless @lazy_new_record
@lazy_new_record = true
reload
end
super(*args)
end
end
end
end
end
module Methods
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def lazy_find_by(attributes = {})
mod = ActiveRecordLazyFindBy.module_for(self, attributes)
new(attributes).extend(mod)
end
end
end
end
class ApplicationRecord < ActiveRecord::Base
include ActiveRecordLazyFindBy::Methods
self.abstract_class = true
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment