require 'active_support/core_ext/array'
class Module
protected
def define_pair(setter, *args) # getter = setter, options = {}
options = args.extract_options!
getter = args[0] || setter
setter_visibility = options[:setter_visibility] || :protected
getter_visibility = options[:getter_visibility] || :protected
define_method getter do; end
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def self.#{setter}(*args, &block) # def self.layout(*args, &block)
define_getter :#{getter}, :#{getter_visibility}, *args, &block # define_getter :_layout, :protected, *args, &block
end # end
#
#{setter_visibility} :#{setter} # protected :layout
#{getter_visibility} :#{getter} # protected :_layout
RUBY
end
def define_getter(name, visibility, *args, &block)
arg = block_given? ? block : args[0]
method_body = case arg
when Proc
helper_method = "_#{name}_from_proc"
define_method helper_method, &arg
module_eval "#{visibility} :#{helper_method}"
helper_method
when Symbol
arg
else
arg.inspect
end
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{name} # def _layout
#{method_body} # # dependent stuff
end # end
#{visibility} :#{name} # protected :_layout
RUBY
end
end
# examples
module Test
define_pair :smth
smth do
"from proc rand: #{Random.rand(1000)}"
end
def result
smth
end
end
class A
include Test
end
class B
define_pair :other_one
other_one :method
def method
:from_method
end
def result
other_one
end
end
class C < B
end
class D < B
other_one 'string'
end
puts "A: #{A.new.result}"
It also can be replaced with method definitions, but one have to track methods visibility while redefining methods.
And it's also looks little prettier than method definition.
Here is temporary method names think we need decide new ones if you like the stuff.