Skip to content

Instantly share code, notes, and snippets.

@delonnewman
Last active May 11, 2023 06:56
Show Gist options
  • Save delonnewman/fdc46ae313cf1ccc966553893b390fd6 to your computer and use it in GitHub Desktop.
Save delonnewman/fdc46ae313cf1ccc966553893b390fd6 to your computer and use it in GitHub Desktop.
# @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