lib/shamu/services/lazy_association.rb in shamu-0.0.21 vs lib/shamu/services/lazy_association.rb in shamu-0.0.24
- old
+ new
@@ -1,31 +1,63 @@
module Shamu
module Services
# Lazily look up an associated resource
- class LazyAssociation < Delegator
+ module LazyAssociation
+ EXCLUDE_PATTERN = /\A(block_given\?|id|send|public_send|iterator|object_id|to_model_id|binding|class|kind_of\?|is_a\?|instance_of\?|respond_to\?|p.+_methods|__.+__)\z/ # rubocop:disable Metrics/LineLength
+ MUTEX = Mutex.new
- # ============================================================================
- # @!group Attributes
- #
+ def self.class_for( klass ) # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
+ return klass.const_get( :Lazy_ ) if klass.const_defined?( :Lazy_ )
- # @!attribute
- # @return [Object] the primary key id of the association. Not delegated so
- # it is safe to use and will not trigger an unnecessary fetch.
- attr_reader :id
+ MUTEX.synchronize do
- #
- # @!endgroup Attributes
+ # Check again in case another thread defined it while waiting for the
+ # mutex
+ return klass.const_get( :Lazy_ ) if klass.const_defined?( :Lazy_ )
- def initialize( id, &block )
- @id = id
- @block = block
- end
+ lazy_class = Class.new( klass ) do
+ # Remove all existing public methods so that they can be delegated
+ # with #method_missing.
+ klass.public_instance_methods.each do |method|
+ next if EXCLUDE_PATTERN =~ method
+ undef_method method
+ end
- def __getobj__
- return @association if defined? @association
+ def initialize( id, &block )
+ @id = id
+ @block = block
+ end
- @association = @block.call
+ # @!attribute
+ # @return [Object] the primary key id of the association. Not delegated so
+ # it is safe to use and will not trigger an unnecessary fetch.
+ attr_reader :id
+
+ def __getobj__
+ return @association if defined? @association
+
+ @association = @block.call( @id ) if @block
+ end
+
+ def method_missing( method, *args, &block )
+ if respond_to_missing?( method )
+ __getobj__.public_send( method, *args, &block )
+ else
+ super
+ end
+ end
+
+ def respond_to_missing?( method, include_private = false )
+ __getobj__.respond_to?( method, include_private ) || super
+ end
+
+ end
+
+ klass.const_set( :Lazy_, lazy_class )
+ lazy_class
+ end
end
+
end
end
-end
\ No newline at end of file
+end