#--
# Author:: Aaron Pfeifer, Neil Abraham, Tyler Rick
# Copyright:: Copyright (c) 2006-2007 Aaron Pfeifer & Neil Abraham
# License:: MIT License
# Submit to Facets?:: Yes!
# Developer notes::
# * Why are we calling it create? Why not just new? Would that cause any problems?
# To do::
# * Submit to pluginaweek mailing list
# Changes:
# r2964 (Tyler):
# * Started from http://svn.pluginaweek.org/trunk/plugins/ruby/module/module_creation_helper/ (Last Changed Rev: 320)
# * Renamed :parent option to :namespace. (:parent is still allowed for backwards compatibility)
# * Changed examples and tests to pass in the name as a symbol instead of a string.
# * Made it so you can pass in the namespace as part of the name if you want: Module.create(:'Foo::Bar') instead of Module.create(:Foo, :parent => Bar)
# * Added to the documentation
# * Added new tests
# * test_with_block_2
# * test_nested_class_with_superclass_with_same_name
# * test_referencing_a_namespace_that_isnt_defined
# * test_creating_a_class_more_than_once
# * test_using_return_value_of_one_create_within_another_create
# * Added __FILE__, __LINE__ to class_eval so that error messages would be more helpful.
#++
$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
require 'quality_extensions/hash/assert_has_only_keys'
require 'facets/hash/merge'
require 'facets/kernel/constant'
require 'quality_extensions/module/split'
require 'quality_extensions/module/basename'
require 'quality_extensions/module/dirname'
class Module
# Creates a new module with the specified name. This is essentially the
# same as actually defining the module like so:
#
# module NewModule
# end
#
# or as a class:
#
# class NewClass < SuperKlass
# end
#
# Configuration options:
# superclass - The class to inherit from. This only applies when using Class#create. Default is Object.
# namespace/parent - The class/module namespace that contains this module. Default is Object.
#
# You can also include the namespace in the +name+ if you'd prefer. For instance, name = :'Foo::Bar' is the same as specifying name = :Bar, :namespace => Foo. (Note: The namespace module specified must already be defined, just like it would have to be defined if you used the :namespace option.)
#
# Examples:
#
# Module.create(:Foo) # => Foo
# Module.create(:'Foo::Bar', :namespace => Foo) # => Foo::Bar
# Module.create(:Bar, :namespace => Foo) # => Foo::Bar
#
# Class.create(:Base) # => Base
# Class.create(:'Foo::Klass', :superclass => Base) # => Foo::Klass
#
# Unlike the built-in Ruby +module+/+class+ directive, this actually returns the newly created module/class as the return value. So, for example, you can do things like this:
#
# klass = Class.create(:'Foo::Klass', :superclass => Class.create(:Base)) # => Foo::Klass
# klass.name # => Foo::Klass
# klass.superclass # => Base
#
# You can also pass a block to create. This:
#
# Class.create(:ClassWithBlock, :superclass => BaseClass) do
# def self.say_hello
# 'hello'
# end
# end
#
# is equivalent to this:
#
# class ClassWithBlock < BaseClass do
# def self.say_hello
# 'hello'
# end
# end
#
def create(name, options = {}, &block)
# Validate arguments
raise ArgumentError unless name.respond_to?(:to_s)
options[:namespace] = options.delete(:parent) if options.has_key?(:parent)
options.assert_has_only_keys(
:superclass,
:namespace
)
module_or_class = self.to_s.downcase
raise ArgumentError, 'Modules cannot have superclasses' if options[:superclass] && module_or_class == 'module'
# Set defaults
namespace_module, superclass =
options[:namespace] || ::Object,
options[:superclass] || ::Object
# Determine the namespace to create it in
nesting = Module.split_name(name)
if nesting.size > 1
namespace_module = Module.namespace_of(name) # For example, would be A::B for A::B::C
base_name = Module.basename(name) # For example, would be :C for A::B::C
else
base_name = name
end
# Actually create the new module
if superclass != ::Object
superclass = " < ::#{superclass}"
else
superclass = ''
end
namespace_module.class_eval <<-end_eval, __FILE__, __LINE__
#{module_or_class} #{base_name}#{superclass}
# Empty
end
end_eval
our_new_module = namespace_module.const_get(base_name)
our_new_module.class_eval(&block) if block_given?
our_new_module
end
end
# _____ _
# |_ _|__ ___| |_
# | |/ _ \/ __| __|
# | | __/\__ \ |_
# |_|\___||___/\__|
#
=begin test
require 'test/unit'
require 'quality_extensions/module/attribute_accessors'
require 'quality_extensions/module/namespace'
require 'quality_extensions/symbol/constantize'
require 'facets/module/basename'
module Namespace
end
class BaseClass
cattr_accessor :test_name
cattr_accessor :test_inspect
cattr_accessor :test_to_s
def self.inherited(base)
self.test_name = base.name
self.test_inspect = base.inspect
self.test_to_s = base.to_s
end
end
class ModuleCreationHelperTest < Test::Unit::TestCase
def setup
BaseClass.test_name = nil
BaseClass.test_inspect = nil
BaseClass.test_to_s = nil
end
def test_no_options_for_class
klass = Class.create(:Foo)
assert_equal Object, klass.superclass
assert_equal Object, klass.namespace_module
assert Object.const_defined?('Foo')
end
def test_no_options_for_module
mod = Module.create(:FooMod)
assert_equal Object, mod.namespace_module
assert Object.const_defined?('FooMod')
end
def test_invalid_option
assert_raise(ArgumentError) {Class.create(nil, :invalid => true)}
end
def test_superclass_for_module
assert_raise(ArgumentError) {Module.create(nil, :superclass => Object)}
end
def test_superclass
klass = Class.create(:Bar, :superclass => BaseClass)
assert_equal BaseClass, klass.superclass
assert_equal Object, klass.namespace_module
assert Object.const_defined?('Bar')
end
def test_namespace_for_class
klass = Class.create(:Baz, :namespace => Namespace)
assert_equal Object, klass.superclass
assert_equal Namespace, klass.namespace_module
assert Namespace.const_defined?('Baz')
end
def test_namespace_for_class__namespace_as_part_of_name
klass = Class.create(:'Namespace::Baz')
assert_equal Object, klass.superclass
assert_equal Namespace, klass.namespace_module
assert Namespace.const_defined?('Baz')
end
def test_namespace_for_module
mod = Module.create(:BazMod, :namespace => Namespace)
assert_equal Namespace, mod.namespace_module
assert Namespace.const_defined?('BazMod')
end
def test_superclass_and_namespace
klass = Class.create(:Biz, :superclass => BaseClass, :namespace => Namespace)
assert_equal BaseClass, klass.superclass
assert_equal Namespace, klass.namespace_module
assert Namespace.const_defined?('Biz')
end
def test_name_before_evaluated
klass = Class.create(:Waddle, :superclass => BaseClass)
assert_equal 'Waddle', BaseClass.test_name
end
def test_inspect_before_evaluated
klass = Class.create(:Widdle, :superclass => BaseClass)
assert_equal 'Widdle', BaseClass.test_inspect
end
def test_to_s_before_evaluated
klass = Class.create(:Wuddle, :superclass => BaseClass)
assert_equal 'Wuddle', BaseClass.test_to_s
end
def test_name_before_evaluated_with_namespace
klass = Class.create(:Waddle, :superclass => BaseClass, :namespace => Namespace)
assert_equal 'Namespace::Waddle', BaseClass.test_name
end
def test_inspect_before_evaluated_with_namespace
klass = Class.create(:Widdle, :superclass => BaseClass, :namespace => Namespace)
assert_equal 'Namespace::Widdle', BaseClass.test_inspect
end
def test_to_s_before_evaluated_with_namespace
klass = klass = Class.create(:Wuddle, :superclass => BaseClass, :namespace => Namespace)
assert_equal 'Namespace::Wuddle', BaseClass.test_to_s
end
def test_subclass_of_dynamic_class
klass = Class.create(:Foobar)
subclass = Class.create(:Foobaz, :superclass => klass)
assert_equal klass, subclass.superclass
assert_equal 'Foobaz', subclass.name
assert_equal 'Foobaz', subclass.inspect
assert_equal 'Foobaz', subclass.to_s
end
def test_with_block_1
klass = Class.create(:ClassWithBlock, :superclass => BaseClass) do
def self.say_hello
'hello'
end
end
assert_equal 'hello', ClassWithBlock.say_hello
end
def test_with_block_2
klass = Class.create(:ClassWithBlock, :superclass => BaseClass) do
def say_hello
'hello'
end
end
assert_equal 'hello', ClassWithBlock.new.say_hello
end
def test_nested_class_with_superclass_with_same_name
klass = Class.create(:Employee)
nested_class = Class.create(:Employee, :superclass => klass, :namespace => Namespace)
assert_equal klass, nested_class.superclass
assert_equal klass.basename, nested_class.basename
end
def test_nested_class_with_superclass_with_same_name
klass = Class.create(:Employee)
nested_class = Class.create(:Employee, :superclass => klass, :namespace => Namespace)
assert_equal klass, nested_class.superclass
assert_equal klass.basename, nested_class.basename
end
def test_referencing_a_namespace_that_isnt_defined
assert_raise(NameError) { Class.create(:'Zzt::Klass') }
end
def test_creating_a_class_more_than_once
klass = Class.create(:'Foo')
klass = Class.create(:'Foo')
end
def test_using_return_value_of_one_create_within_another_create
klass = Class.create(:'Foo')
klass = Class.create(:'Base')
klass = Class.create(:'Foo::Klass',
:superclass => Class.create(:Base)
)
assert_equal 'Foo::Klass', klass.name
assert_equal Base, klass.superclass
end
end
=end