lib/sinclair/method_definition.rb in sinclair-1.1.3 vs lib/sinclair/method_definition.rb in sinclair-1.2.0
- old
+ new
@@ -4,34 +4,86 @@
# @api private
# @author darthjee
#
# Definition of the code or block to be aded as method
class MethodDefinition
+ include Sinclair::OptionsParser
+ # Default options of initialization
+ DEFAULT_OPTIONS = {
+ cached: false
+ }.freeze
+
# Returns a new instance of MethodDefinition
#
# @overload initialize(name, code)
+ # @example
+ # Sinclair::MethodDefinition.new(:name, '@name')
+ #
# @overload initialize(name, &block)
+ # @example
+ # Sinclair::MethodDefinition.new(:name) { @name }
#
- # @param name [String,Symbol] name of the method
- # @param code [String] code to be evaluated as method
- # @param block [Proc] block with code to be added as method
- #
- # @example
- # Sinclair::Method.new(:name, '@name')
- #
- # @example
- # Sinclair::Method.new(:name) { @name }
- def initialize(name, code = nil, &block)
- @name = name
- @code = code
- @block = block
+ # @param name [String,Symbol] name of the method
+ # @param code [String] code to be evaluated as method
+ # @param block [Proc] block with code to be added as method
+ # @param options [Hash] Options of construction
+ # @option options cached [Boolean] Flag telling to create
+ # a method with cache
+ def initialize(name, code = nil, **options, &block)
+ @name = name
+ @code = code
+ @options = DEFAULT_OPTIONS.merge(options)
+ @block = block
end
# Adds the method to given klass
#
# @param klass [Class] class which will receive the new method
#
+ # @example Using string method with no options
+ # class MyModel
+ # end
+ #
+ # instance = MyModel.new
+ #
+ # method_definition = Sinclair::MethodDefinition.new(
+ # :sequence, '@x = @x.to_i ** 2 + 1'
+ # )
+ #
+ # method_definition.build(klass) # adds instance_method :sequence to
+ # # MyModel instances
+ #
+ # instance.instance_variable_get(:@x) # returns nil
+ #
+ # instance.sequence # returns 1
+ # instance.sequence # returns 2
+ # instance.sequence # returns 5
+ #
+ # instance.instance_variable_get(:@x) # returns 5
+ #
+ # @example Using string method with no options
+ # class MyModel
+ # end
+ #
+ # instance = MyModel.new
+ #
+ # method_definition = Sinclair::MethodDefinition.new(:sequence) do
+ # @x = @x.to_i ** 2 + 1
+ # end
+ #
+ # method_definition.build(klass) # adds instance_method :sequence to
+ # # MyModel instances
+ #
+ # instance.instance_variable_get(:@sequence) # returns nil
+ # instance.instance_variable_get(:@x) # returns nil
+ #
+ # instance.sequence # returns 1
+ # instance.sequence # returns 1 (cached value)
+ #
+ # instance.instance_variable_get(:@sequence) # returns 1
+ # instance.instance_variable_get(:@x) # returns 1
+ #
# @return [Symbol] name of the created method
def build(klass)
if code.is_a?(String)
build_code_method(klass)
else
@@ -41,22 +93,50 @@
private
# @private
attr_reader :name, :code, :block
+ delegate :cached, to: :options_object
# @private
#
+ # Flag telling to use cached method
+ #
+ # @return [Boolean]
+ alias cached? cached
+
+ # @private
+ #
# Add method from block
#
# @return [Symbol] name of the created method
def build_block_method(klass)
- klass.send(:define_method, name, block)
+ klass.send(:define_method, name, method_block)
end
# @private
#
+ # Returns the block that will be used for method creattion
+ #
+ # @return [Proc]
+ def method_block
+ return block unless cached?
+
+ inner_block = block
+ method_name = name
+
+ proc do
+ instance_variable_get("@#{method_name}") ||
+ instance_variable_set(
+ "@#{method_name}",
+ instance_eval(&inner_block)
+ )
+ end
+ end
+
+ # @private
+ #
# Add method from String code
#
# @return [Symbol] name of the created method
def build_code_method(klass)
klass.module_eval(code_definition, __FILE__, __LINE__ + 1)
@@ -66,12 +146,13 @@
#
# Builds full code of method
#
# @return [String]
def code_definition
+ code_line = cached? ? "@#{name} ||= #{code}" : code
<<-CODE
def #{name}
- #{code}
+ #{code_line}
end
CODE
end
end
end