Partial append_features?

Discussion in 'Ruby' started by Ola Bini, Aug 28, 2006.

  1. Ola Bini

    Ola Bini Guest

    Hi,

    I'm looking for a way to copy methods from a Module, or specify more
    directly which methods get included in a class. More or less, I would
    like to be able to do something like this:

    module Foo
    def do_one_thing
    end
    def do_second
    end
    def do_third
    end
    end

    class Bar
    append_from Foo, :do_second, :do_third
    end

    or
    module Baz
    append_from Foo, :do_second, :do_third
    end

    and I would have a module Baz which could be included, without having
    do_one_thing included.

    Is this possible in Ruby right now? My first approach was to get the
    UnboundMethod instance_method from the Module, but I couldn't find a way
    to attach these to an unrelated class since UnboundMethod must have a
    is_a?-relationship with the binding object.

    Regards
    --
    Ola Bini (http://ola-bini.blogspot.com)
    JvYAML, RbYAML, JRuby and Jatha contributor
    System Developer, Karolinska Institutet (http://www.ki.se)
    OLogix Consulting (http://www.ologix.com)

    "Yields falsehood when quined" yields falsehood when quined.
     
    Ola Bini, Aug 28, 2006
    #1
    1. Advertisements

  2. noah.easterly, Aug 28, 2006
    #2
    1. Advertisements

  3. I think the 'proper' solution is to chop your module into
    smaller pieces. Alternatively, Method#to_proc, maybe?
     
    Eero Saynatkari, Aug 28, 2006
    #3
  4. I'm looking for a way to copy methods from a Module, or specify
    The closest I have seen to this is generating a module on the fly
    (with Module.new) that includes the Module and then undefs all the
    methods you don't want. That gives you a custom module to include
    that will have only the methods you are after.

    Matthew
     
    Matthew Johnson, Aug 28, 2006
    #4
  5. % cat Projects/Ruby Experiments/append_from.rb
    class Module
    def append_from( mod, *methods_to_keep )
    methods_to_keep.map! { |m| m.to_s }
    methods_to_remove = mod.instance_methods(false) - methods_to_keep
    new_mod = Module.new
    new_mod.module_eval do
    include mod
    methods_to_remove.each { |meth| undef_method meth }
    end
    include new_mod
    end
    end

    module M
    def a
    puts "a"
    end

    def b
    puts "b"
    end
    end

    class A
    append_from M, :b
    end

    a = A.new
    a.b
    a.a

    % ruby Projects/Ruby Experiments/append_from.rb
    b
    -:40: undefined method `a' for #<A:0x1e89b4> (NoMethodError)
     
    Logan Capaldo, Aug 28, 2006
    #5
  6. Heh, take another look:

    new_mod = Module.new # Only this anonymous is affected
    new_mod.module_eval do
    include mod
    methods_to_remove.each { |meth| undef_method meth }
    end

    A rather nice solution.
     
    Eero Saynatkari, Aug 29, 2006
    #6
  7. Ola Bini

    nobu Guest

    Hi,

    At Tue, 29 Aug 2006 04:00:39 +0900,
    Ola Bini wrote in [ruby-talk:211191]:
    http://www.rubyist.net/~nobu/ruby/aliasing.rb may help you.

    require 'aliasing'

    module Foo
    def do_one_thing
    "one_thing"
    end
    def do_second
    "second"
    end
    def do_third
    "third"
    end
    end

    class Bar
    include Foo.only_aliasing:)do_second, :do_third)
    end

    p Bar.instance_methods.grep(/^do/)
    bar = Bar.new
    p bar.do_second
    p bar.do_third
    p (begin bar.do_one_thing; rescue NoMethodError => e; e; end)
     
    nobu, Aug 29, 2006
    #7
  8. Ola Bini

    Trans Guest

    Try Facets' module/integrate.rb Here's the doc:

    # Using integrate is just like using include except the
    # module included is a reconstruction of the one given
    # altered via commands in the block.
    #
    # Convenient commands available are: #rename, #redef,
    # #remove, #nodef and #wrap. But any module method
    # can be used.
    #
    # module W
    # def q ; "q" ; end
    # def y ; "y" ; end
    # end
    #
    # class X
    # integrate W do
    # nodef :y
    # end
    # end
    #
    # x = X.new
    # x.q #=> "q"
    # x.y #=> missing method error
    #
    # This is like #revisal, but #revisal only
    # returns the reconstructred module. It does not
    # include it.

    http://facets.rubyforge.org

    T.
     
    Trans, Aug 29, 2006
    #8
  9. Ola Bini

    Ola Bini Guest

    Hi,

    Thank you for writing it up for me. This was more or less what I had in
    mind of writing up myself. This solution is definitely the best for me,
    since the methods I want to keep is a small subset compared to how many
    to remove, and the ones to remove will grow with time.

    Thanks.

    --
    Ola Bini (http://ola-bini.blogspot.com)
    JvYAML, RbYAML, JRuby and Jatha contributor
    System Developer, Karolinska Institutet (http://www.ki.se)
    OLogix Consulting (http://www.ologix.com)

    "Yields falsehood when quined" yields falsehood when quined.
     
    Ola Bini, Aug 29, 2006
    #9
  10. I think this is what he meant:

    RUBY_VERSION # => "1.8.5"
    class X; def foo; "X#foo" end end
    class Y < X; end
    module M; def foo; "M#foo" end end
    N = Module.new
    N.module_eval do
    include M
    undef_method :foo
    end

    y = Y.new
    y.foo # => "X#foo"
    class Y; include N end
    y.foo # =>
    # ~> -:14: undefined method `foo' for #<Y:0xa7e04000> (NoMethodError)


    IMO it's better to have the last call to #foo return X#foo.
    You can do that by cloning the module then using remove_method on the copy.
     
    Mauricio Fernandez, Aug 29, 2006
    #10
  11. The above code effectively removes inherited methods too (as well as those
    from other modules that were included previously):

    class Module
    def append_from( mod, *methods_to_keep )
    methods_to_keep.map! { |m| m.to_s }
    methods_to_remove = mod.instance_methods(false) - methods_to_keep
    new_mod = Module.new
    new_mod.module_eval do
    include mod
    methods_to_remove.each { |meth| undef_method meth }
    end
    include new_mod
    end
    end

    module A; def foo; "A#foo" end end
    module B
    def foo; "B#foo" end
    def bar; "B#bar" end
    end
    class X; include A end
    x = X.new
    x.foo # => "A#foo"
    class X; append_from B, :bar end
    x.bar # => "B#bar"
    x.foo # =>

    # ~> -:24: undefined method `foo' for #<X:0xa7d72a88> (NoMethodError)

    In this example, #undef_method has blocked A#foo too.


    Here's another way to do it without clobbering inherited methods; the
    key difference is that the original module will not be added to the
    inheritance chain:



    RUBY_VERSION # => "1.8.5"
    RUBY_RELEASE_DATE # => "2006-08-25"
    class Module
    def append_from(mod, *methods)
    methods.map!{|x| x.to_s}
    m = mod.clone
    m.module_eval do
    (instance_methods(false) - methods).each{|x| remove_method x }
    end
    include m
    end
    end

    module A; def foo; "A#foo" end end
    module B
    def foo; "B#foo" end
    def bar; "B#bar" end
    end
    class X; include A end
    x = X.new
    x.foo # => "A#foo"
    class X; append_from B, :bar end
    x.bar # => "B#bar"
    x.foo # => "A#foo"
    X.ancestors # => [X, #<Module:0xa7d94214>, A, Object, Kernel]
    ====================
    B(') is missing in
    the inheritance chain
     
    Mauricio Fernandez, Aug 29, 2006
    #11
  12. Right, since is_a? doesn't hold anymore, you cannot add methods to the module
    either, e.g.

    module B; def foo; "B#foo" end end
    class X; append_from B, :foo end
    module B; def bar; "B#bar" end end

    X.new.bar # ====> would raise a NoMethodError

    ... and you cannot capture new methods in B.method_added since Ruby won't let
    you rebind them. But that's OK, since we wanted *only* the methods explicitly
    passed to #append_from to be imported. In fact, you'd have to add some code
    (similar to BlankSlate's) to the other solution (the one with a "child module"
    and #undef_method) to handle this correctly (and it'd still suffer from the
    method shadowing/clobbering problem).
     
    Mauricio Fernandez, Aug 29, 2006
    #12
  13. Ola Bini

    Trans Guest

    If you don't include the module but keep the inherited methods you have
    a sort of out-of-sync situation. That doesn't make much sense.

    And is there a reason you use clone vs. dup?

    Thanks,
    T.


    PS. Maybe I can improve Facets via this discussion. From Facets' I
    already have:

    class Module

    # Returns an anonymous module with only the specified methods
    # of the receiving module intact.
    def clone_using( *meths )
    meths = meths.collect { |m| m.to_s }
    methods_to_remove = (self.instance_methods - meths)
    mod = self.dup
    mod.class_eval { methods_to_remove.each { |m| undef_method m } }
    return mod
    end

    end

    T.
     
    Trans, Aug 29, 2006
    #13
  14. Yeah I knew it would clobber the old methods, but I couldn't think of
    a way to do it w/o clobbering them. Silly me not thinking of clone.
    (My original attempt want to use UboundMethod#to_proc + define_method
    (IMO being closest to the desired behavior), unfortunately there is
    no such thing as UnboundMethod#to_proc.
     
    Logan Capaldo, Aug 29, 2006
    #14
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.