# = classmethods.rb # # == Copyright (c) 2005 Nobu Nakada, Thomas Sawyer # # Ruby License # # This module is free software. You may use, modify, and/or redistribute this # software under the same terms as Ruby. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. # # == Special Thanks # # Thanks to Nobu and Ulysses for their original work on this. # # == Authors and Contributors # # * Nobu Nakada # * Thomas Sawyer # * Ulysses # Author:: Nobu Nakada, Thomas Sawyer # Copyright:: Copyright (c) 2005 Nobu Nakada, Thomas Sawyer # License:: Ruby License # = ClassMethods # # Miniframework provides a very convenient way to have modules # pass along class methods in the inheritance chain. # # An oddity of Ruby, when including modules, class/module methods # are not inherited. To achieve this behavior requires some clever # Ruby Karate. Instead ClassMethods provides an easy to use and clean # solution. Simply place the class inheritable methods in a block of # the special module method #ClassMetods. # # module Mix # def inst_meth # puts 'inst_meth' # end # # class_methods do # def class_meth # "Class Method!" # end # end # end # # class X # include Mix # end # # X.class_meth #=> "Class Method!" # # This is equivalent to the original (but still functional) techinique of # putting the class/module methods in a nested ClassMethods module # and extending the original module *manually*. Eg. # # module Mix # def inst_meth # puts 'inst_meth' # end # # module ClassMethods # def class_meth # "Class Method!" # end # end # # extend ClassMethods # end # # class X # include Mix # end # # X.class_meth #=> "Class Method!" # # Also note that #class_inherit is an available alias # for #class_methods for the sake of backward compatability. # And #class_extension is alias (potentially) looking forward # to a future version on Ruby. # # == Notes # # Just a quick comment on the need for this behavior. # # A module is an encapsulation of code, hence when a module is included # (or extends), the module itself should have discretion over how it # effects the receiving class/module. That is the very embodiment of # encapsulation. Having it otherwise, as Ruby now does, stymies the # practice --and we end up with "hacks", like this and ClassMethods, # to compensate. # # Ruby would be much improved by making this bevaivor standard. # And making non-inheritance the exception, which is alwasy easy # enough to achieve: put the code in a separate (and thus uninherited) # module. class Module alias_method :append_features_without_classmethods, :append_features def append_features( base ) result = append_features_without_classmethods( base ) if const_defined?( :ClassMethods ) base.extend( self::ClassMethods ) unless base.is_a?( Class ) unless base.const_defined?( :ClassMethods ) base.const_set( :ClassMethods, Module.new ) end my = self base::ClassMethods.class_eval do include my::ClassMethods end end end result end def class_methods( &yld ) if const_defined?( :ClassMethods ) self::ClassMethods.class_eval( &yld ) else self.const_set( :ClassMethods, Module.new( &yld ) ) end extend( self::ClassMethods ) self::ClassMethods end # For compatibility with old rendition. alias_method :class_inherit, :class_methods end class Class undef_method :class_methods undef_method :class_inherit end # _____ _ # |_ _|__ ___| |_ # | |/ _ \/ __| __| # | | __/\__ \ |_ # |_|\___||___/\__| # =begin test require 'test/unit' class TC_ClassMethods < Test::Unit::TestCase # fixture module N class_methods do def n ; 43 ; end def s ; self ; end end end class X include N def n ; 11 ; end end module K include N class_methods do def n ; super + 1 ; end end end class Z include K end # tests def test_01 assert_equal( 43, N.n ) assert_equal( N, N.s ) end def test_02 assert_equal( 43, X.n ) assert_equal( X, X.s ) end def test_03 assert_equal( 11, X.new.n ) end def test_04 assert_equal( 44, K.n ) assert_equal( K, K.s ) end def test_05 assert_equal( 44, Z.n ) assert_equal( Z, Z.s ) end end =end