Permalink
Browse files

import to git

  • Loading branch information...
0 parents commit dff088eb8c5aed549bfbced20feded285fcc875c @jnewland committed Jan 27, 2008
Showing with 250 additions and 0 deletions.
  1. +21 −0 MIT-LICENSE
  2. +49 −0 README
  3. +9 −0 init.rb
  4. +145 −0 lib/active_record/lazy.rb
  5. +26 −0 lib/active_record/lazy/record.rb
@@ -0,0 +1,21 @@
+Copyright (c) 2007 Jesse Newland <jnewland@gmail.com>
+lazy.rb Copyright 2005-2006 MenTaLguY <mental@rydia.net>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
49 README
@@ -0,0 +1,49 @@
+LazyRecord
+==========
+
+Proof of concept Lazy-Loading for ActiveRecord. Inspired by the 'kickers' of Ambition.
+
+ >> b = Buzz.lazy_find(:first)
+ => #<ActiveRecord::Lazy::Promise computation=#<Proc:0x025d1e50@...>>
+ -------------No SQL query is run until a method is called on this 'Promise'
+ >> b.to_s
+ -------------Buzz Load (0.000578) SELECT * FROM buzz LIMIT 1
+ => "Inaugural Buzz"
+
+Use the +lazy_record+ class method to make this the default for a certain class:
+
+ class Buzz << ActiveRecord::Base
+ lazy_record
+ end
+
+ >> b = Buzz.find(:first)
+ => #<ActiveRecord::Lazy::Promise computation=#<Proc:0x025d1e50@...>>
+ -------------No SQL query is run until a method is called on this 'Promise'
+ >> b.to_s
+ -------------Buzz Load (0.000578) SELECT * FROM buzz LIMIT 1
+ => "Inaugural Buzz"
+
+
+Why you might want to use this
+===========
+
+Say you've got some kick-ass cache_fu going on in your views - huge blocks of HTML being cached with a TTL of 30 mins or so.
+But, each hit on your controller still fires off the 'spensive DB queries to fetch your tag cloud. With lazy loading, these
+queries aren't run until absolutely necessary - giving your DB a rest til your cache expires, and boosting your reqs/sec.
+
+
+Why you might not want to use this
+===========
+
+ >> b = Buzz.lazy_find(123023424)
+ => #<ActiveRecord::Lazy::Promise computation=#<Proc:0x025d1e50@...>>
+ >> puts "booleans are screwed" if b
+ booleans are screwed
+
+
+Promise code from here: http://moonbase.rydia.net/software/lazy.rb/
+
+Contact
+=======
+Jesse Newland
+jnewland@gmail.com
@@ -0,0 +1,9 @@
+require 'active_record/lazy'
+require 'active_record/lazy/record'
+ActiveRecord::Base.class_eval do
+ include ActiveRecord::Lazy
+ class << self
+ alias_method :find_without_lazy, :find
+ end
+ include ActiveRecord::Lazy::Record
+end
@@ -0,0 +1,145 @@
+# = lazy.rb -- Lazy evaluation in Ruby
+#
+# Author:: MenTaLguY
+#
+# Copyright 2005-2006 MenTaLguY <mental@rydia.net>
+#
+# You may redistribute it and/or modify it under the same terms as Ruby.
+#
+
+module ActiveRecord
+ module Lazy
+
+ # Raised when a demanded computation diverges (e.g. if it tries to directly
+ # use its own result)
+ #
+ class DivergenceError < Exception
+ def initialize( message="Computation diverges" )
+ super( message )
+ end
+ end
+
+ # Wraps an exception raised by a lazy computation.
+ #
+ # The reason we wrap such exceptions in LazyException is that they need to
+ # be distinguishable from similar exceptions which might normally be raised
+ # by whatever strict code we happen to be in at the time.
+ #
+ class LazyException < DivergenceError
+ # the original exception
+ attr_reader :reason
+
+ def initialize( reason )
+ @reason = reason
+ super( "Exception in lazy computation: #{ reason } (#{ reason.class })" )
+ set_backtrace( reason.backtrace.dup ) if reason
+ end
+ end
+
+ # A handle for a promised computation. They are transparent, so that in
+ # most cases, a promise can be used as a proxy for the computation's result
+ # object. The one exception is truth testing -- a promise will always look
+ # true to Ruby, even if the actual result object is nil or false.
+ #
+ # If you want to test the result for truth, get the unwrapped result object
+ # via #demand.
+ #
+ class Promise
+ alias __class__ class #:nodoc:
+ instance_methods.each { |m| undef_method m unless m =~ /^__/ }
+
+ def initialize( &computation ) #:nodoc:
+ @computation = computation
+ end
+ def __synchronize__ #:nodoc:
+ yield
+ end
+
+ # create this once here, rather than creating a proc object for
+ # every evaluation
+ DIVERGES = lambda { raise DivergenceError.new } #:nodoc:
+ def DIVERGES.inspect #:nodoc:
+ "DIVERGES"
+ end
+
+ def __result__ #:nodoc:
+ __synchronize__ do
+ if @computation
+ raise LazyException.new( @exception ) if @exception
+
+ computation = @computation
+ @computation = DIVERGES # trap divergence due to over-eager recursion
+
+ begin
+ @result = ActiveRecord::Lazy.demand( computation.call( self ) )
+ @computation = nil
+ rescue DivergenceError
+ raise
+ rescue Exception => exception
+ # handle exceptions
+ # @exception = exception
+ raise exception
+ # raise LazyException.new( @exception )
+ end
+ end
+
+ @result
+ end
+ end
+
+ def inspect #:nodoc:
+ __synchronize__ do
+ if @computation
+ "#<#{ __class__ } computation=#{ @computation.inspect }>"
+ else
+ @result.inspect
+ end
+ end
+ end
+
+ def respond_to?( message ) #:nodoc:
+ message = message.to_sym
+ message == :__result__ or
+ message == :inspect or
+ __result__.respond_to? message
+ end
+
+ def method_missing( *args, &block ) #:nodoc:
+ __result__.__send__( *args, &block )
+ end
+ end
+
+ # The promise() function is used together with demand() to implement
+ # lazy evaluation. It returns a promise to evaluate the provided
+ # block at a future time. Evaluation can be demanded and the block's
+ # result obtained via the demand() function.
+ #
+ # Implicit evaluation is also supported: the first message sent to it will
+ # demand evaluation, after which that message and any subsequent messages
+ # will be forwarded to the result object.
+ #
+ # As an aid to circular programming, the block will be passed a promise
+ # for its own result when it is evaluated. Be careful not to force
+ # that promise during the computation, lest the computation diverge.
+ #
+ def self.promise( &computation ) #:yields: result
+ ActiveRecord::Lazy::Promise.new &computation
+ end
+
+ # Forces the result of a promise to be computed (if necessary) and returns
+ # the bare result object. Once evaluated, the result of the promise will
+ # be cached. Nested promises will be evaluated together, until the first
+ # non-promise result.
+ #
+ # If called on a value that is not a promise, it will simply return it.
+ #
+ def self.demand( promise )
+ if promise.respond_to? :__result__
+ promise.__result__
+ else # not really a promise
+ promise
+ end
+ end
+
+ end
+end
@@ -0,0 +1,26 @@
+module ActiveRecord
+ module Lazy #:nodoc:
+ module Record
+
+ def self.append_features(base)
+ super
+ base.extend(ClassMethods)
+ end
+
+
+ module ClassMethods
+ #lazy finder
+ def lazy_find(*args)
+ ActiveRecord::Lazy.promise { find_without_lazy(*args) }
+ end
+
+ def lazy_record()
+ class << self
+ alias_method :find, :lazy_find
+ end
+ end
+
+ end
+ end
+ end
+end

0 comments on commit dff088e

Please sign in to comment.