Last active
May 11, 2023 06:56
-
-
Save delonnewman/fdc46ae313cf1ccc966553893b390fd6 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# @see https://ruby-naseby.blogspot.com/2008/11/traits-in-ruby.html | |
module Trait | |
class RequiredMethodMissing < RuntimeError; end | |
def append_features( mod ) | |
unless mod.is_a? Class #not the best check, probably... | |
list_of_methods = ( [ mod.get_required_methods ] << @required_methods ).flatten | |
mod.required_methods( *list_of_methods.compact ) | |
else | |
@required_methods.each do | id | | |
unless mod.instance_methods( true ).include?( id.to_s ) | |
raise Trait::RequiredMethodMissing, id.to_s | |
end | |
end | |
end | |
super_methods = mod.instance_methods( true ) - mod.instance_methods( false ) | |
self.instance_methods( false ).each do | meth | | |
if super_methods.include?( meth ) | |
alt_name = "_trait_#{self.class}_#{meth}" | |
send( :alias_method, alt_name, meth ) | |
mod.module_eval <<-END_EVAL | |
def #{meth}( *args, &block ) | |
#{alt_name}( *args, &block ) | |
end | |
END_EVAL | |
end | |
end | |
super | |
end | |
def extend_object( obj ) | |
append_features( obj.class ) | |
end | |
def required_methods( *ids ) | |
@required_methods = ids | |
end | |
def get_required_methods; @required_methods; end | |
end | |
#tests | |
if $0 == __FILE__ | |
module T1 | |
extend Trait | |
required_methods :to_s, :blah | |
def bb; "T1#bb"; end | |
end | |
module T2 | |
extend Trait | |
required_methods :t2hook | |
include T1 | |
def cc; "T2#cc"; end | |
end | |
class A | |
def to_s; end | |
def blah; end | |
def bb; "A#bb"; end | |
end | |
class B < A | |
end | |
class C; end | |
class D; def t2hook; end; end; | |
require 'test/unit' | |
class TC_Trait < Test::Unit::TestCase | |
def test_requirements_met | |
a = A.new | |
assert_nothing_raised { a.extend( T1 ) } | |
end | |
def test_requirements_not_met | |
assert_raises( Trait::RequiredMethodMissing ) { C.new.extend( T1 ) } | |
end | |
def test_existing_method_not_overridden | |
a = A.new | |
a.extend T1 | |
assert_equal "A#bb", a.bb | |
end | |
def test_super_method_overridden | |
b = B.new | |
b.extend T1 | |
assert_equal "T1#bb", b.bb | |
end | |
def test_composed_trait_requirements_not_met | |
assert_raises( Trait::RequiredMethodMissing ) { D.new.extend( T2 ) } | |
end | |
def test_composed_trait_requirements_met | |
a = A.new | |
a.class.send( :define_method, :t2hook, proc{} ) | |
assert_nothing_raised{ a.extend( T2 ) } | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment