Permalink
Browse files

add attr_lazy.rb: displaces lazy_struct.rb and lazy_attribute.rb

  • Loading branch information...
1 parent 041acc5 commit 380c14c5d836c9435e8741664edaa1a9966c5d2b @quix committed Aug 12, 2009
Showing with 218 additions and 169 deletions.
  1. +67 −0 lib/quix/attr_lazy.rb
  2. +0 −38 lib/quix/lazy_attribute.rb
  3. +0 −55 lib/quix/lazy_struct.rb
  4. +151 −0 test/attr_lazy_test.rb
  5. +0 −39 test/lazy_attribute_test.rb
  6. +0 −37 test/lazy_struct_test.rb
View
@@ -0,0 +1,67 @@
+
+module Quix
+ #
+ # Lazily-evaluated attributes.
+ #
+ # An attr_lazy block is evaluated in the context of the instance
+ # when the attribute is requested. The same result is then returned
+ # for subsequent calls until the attribute is redefined with another
+ # attr_lazy block.
+ #
+ module AttrLazy
+ def attr_lazy(name, &block)
+ AttrLazy.define_attribute(class << self ; self ; end, name, false, &block)
+ end
+
+ def attr_lazy_accessor(name, &block)
+ AttrLazy.define_attribute(class << self ; self ; end, name, true, &block)
+ end
+
+ class << self
+ def included(mod)
+ (class << mod ; self ; end).class_eval do
+ def attr_lazy(name, &block)
+ AttrLazy.define_attribute(self, name, false, &block)
+ end
+
+ def attr_lazy_accessor(name, &block)
+ AttrLazy.define_attribute(self, name, true, &block)
+ end
+ end
+ end
+
+ def define_attribute(klass, name, define_writer, &block)
+ klass.class_eval do
+ # Factoring this code is possible but convoluted, requiring
+ # the definition of a temporary method.
+
+ remove_method name rescue nil
+ define_method name do
+ value = instance_eval(&block)
+ (class << self ; self ; end).class_eval do
+ remove_method name rescue nil
+ define_method name do
+ value
+ end
+ end
+ value
+ end
+
+ if define_writer
+ writer = "#{name}="
+ remove_method writer rescue nil
+ define_method writer do |value|
+ (class << self ; self ; end).class_eval do
+ remove_method name rescue nil
+ define_method name do
+ value
+ end
+ end
+ value
+ end
+ end
+ end
+ end
+ end
+ end
+end
View
@@ -1,38 +0,0 @@
-
-module Quix
- #
- # Mixin for lazily-evaluated attributes.
- #
- module LazyAttribute
- #
- # &block is evaluated when this attribute is requested. The same
- # result is returned for subsequent calls until the attribute is
- # assigned a different value.
- #
- def attribute(reader, &block)
- writer = "#{reader}="
-
- singleton = (class << self ; self ; end)
-
- define_evaluated_reader = lambda { |value|
- singleton.class_eval {
- remove_method(reader)
- define_method(reader) { value }
- }
- }
-
- singleton.class_eval {
- define_method(reader) {
- value = block.call
- define_evaluated_reader.call(value)
- value
- }
-
- define_method(writer) { |value|
- define_evaluated_reader.call(value)
- value
- }
- }
- end
- end
-end
View
@@ -1,55 +0,0 @@
-
-require 'ostruct'
-
-module Quix
- #
- # An OpenStruct with the ability to define lazily-evaluated fields.
- #
- class LazyStruct < OpenStruct
- #
- # For mixing into an existing OpenStruct instance singleton class.
- #
- module Mixin
- #
- # &block is evaluated when this attribute is requested. The
- # same result is returned for subsquent calls, until the field
- # is assigned a different value.
- #
- def attribute(reader, &block)
- singleton = (class << self ; self ; end)
-
- singleton.instance_eval {
- #
- # Define a special reader method in the singleton class.
- #
- define_method(reader) {
- value = block.call
- #
- # The value has been computed. Replace this method with a
- # one-liner giving the value.
- #
- singleton.instance_eval {
- remove_method(reader)
- define_method(reader) { value }
- }
- value
- }
-
- #
- # Revert to the old OpenStruct behavior when the writer is called.
- #
- writer = "#{reader}=".to_sym
- define_method(writer) { |value|
- singleton.instance_eval {
- remove_method(reader)
- remove_method(writer)
- }
- method_missing(writer, value)
- }
- }
- end
- end
-
- include Mixin
- end
-end
View
@@ -0,0 +1,151 @@
+require File.dirname(__FILE__) + "/common"
+
+require 'quix/attr_lazy'
+
+class TestAttrLazy < Test::Unit::TestCase
+ def test_1_reader
+ n = 0
+ cls = Class.new do
+ include Quix::AttrLazy
+ attr_lazy :f do
+ n += 1
+ 33
+ end
+ end
+ s = cls.new
+
+ 3.times { assert_equal(33, s.f) }
+ assert_equal(1, n)
+
+ s.attr_lazy :f do
+ 77
+ end
+ assert_equal(77, s.f)
+
+ assert_raises(NoMethodError) {
+ s.f = 99
+ }
+ end
+
+ def test_1_accessor
+ n = 0
+ cls = Class.new do
+ include Quix::AttrLazy
+ attr_lazy_accessor :f do
+ n += 1
+ 33
+ end
+ end
+ s = cls.new
+
+ 3.times { assert_equal(33, s.f) }
+ assert_equal(1, n)
+
+ s.attr_lazy :f do
+ 77
+ end
+ assert_equal(77, s.f)
+
+ s.f = 99
+ assert_equal(99, s.f)
+ end
+
+ def test_2
+ cls = Class.new do
+ include Quix::AttrLazy
+
+ attr_lazy :x do
+ 33
+ end
+
+ attr_lazy :y do
+ 44
+ end
+
+ attr_lazy_accessor :z do
+ x + y
+ end
+ end
+
+ assert_equal(33 + 44, cls.new.z)
+ a = cls.new
+ a.z = 99
+ assert_equal(99, a.z)
+ end
+
+ def test_3
+ cls = Class.new do
+ include Quix::AttrLazy
+
+ attr_lazy :f do
+ @u = 99
+ 55
+ end
+
+ attr_reader :u
+ end
+
+ a = cls.new
+ assert_equal nil, a.u
+ assert_equal 55, a.f
+ assert_equal 99, a.u
+
+ a.attr_lazy_accessor :f do
+ 77
+ end
+ assert_equal 77, a.f
+
+ a.f = 88
+ assert_equal 88, a.f
+ end
+
+ def test_4
+ cls = Class.new do
+ include Quix::AttrLazy
+ attr_lazy :x do
+ 33
+ end
+ end
+
+ a = cls.new
+ class << a
+ attr_lazy :x do
+ 99
+ end
+ end
+
+ assert_equal(99, a.x)
+ end
+
+ def test_5
+ cls = Class.new do
+ include Quix::AttrLazy
+ attr_lazy :x do
+ 33
+ end
+ end
+
+ a = cls.new
+ b = cls.new
+
+ a.attr_lazy :x do
+ 44
+ end
+ assert_equal 44, a.x
+ assert_equal 33, b.x
+ end
+
+ class A
+ include Quix::AttrLazy
+ def initialize(x)
+ attr_lazy :x do
+ x
+ end
+ end
+ end
+
+ def test_6
+ assert_equal 99, A.new(99).x
+ end
+end
+
@@ -1,39 +0,0 @@
-require File.dirname(__FILE__) + "/common"
-
-require 'quix/lazy_attribute'
-require 'ostruct'
-
-class TestLazyAttribute < Test::Unit::TestCase
- class Stuff
- include Quix::LazyAttribute
- end
-
- def common(s)
- n = 0
- s.attribute(:f) {
- n += 1
- 44
- }
-
- 3.times {
- assert_equal(44, s.f)
- }
- assert_equal(1, n)
-
- s.f = 77
- assert_equal 77, s.f
- end
-
- def test_1
- common(Stuff.new)
- end
-
- def test_2
- s = OpenStruct.new
- class << s
- include Quix::LazyAttribute
- end
- common(s)
- end
-end
-
View
@@ -1,37 +0,0 @@
-require File.dirname(__FILE__) + "/common"
-
-require 'quix/lazy_struct'
-
-class TestLazyStruct < Test::Unit::TestCase
- def common(s)
- s.f = 33
- assert_equal(33, s.f)
-
- n = 0
- s.attribute(:f) {
- n += 1
- 44
- }
-
- 3.times {
- assert_equal(44, s.f)
- }
- assert_equal(1, n)
-
- s.f = 77
- assert_equal 77, s.f
- end
-
- def test_lazy_struct_1
- common(Quix::LazyStruct.new)
- end
-
- def test_lazy_struct_2
- s = OpenStruct.new
- class << s
- include Quix::LazyStruct::Mixin
- end
- common(s)
- end
-end
-

0 comments on commit 380c14c

Please sign in to comment.