Partial append_features?

O

Ola Bini

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.
 
E

Eero Saynatkari

Ola said:
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:

<snip />

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.

I think the 'proper' solution is to chop your module into
smaller pieces. Alternatively, Method#to_proc, maybe?
 
M

Matthew Johnson

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.

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
 
L

Logan Capaldo

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.

% 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)
 
E

Eero Saynatkari

Robert said:
unfortunately that approach undefines eralier defined methods in the
appendee, especially inherited ones :(
I guess that could be fixed but the code will become rather clumsy.

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.
 
N

nobu

Hi,

At Tue, 29 Aug 2006 04:00:39 +0900,
Ola Bini wrote in [ruby-talk:211191]:
and I would have a module Baz which could be included, without having
do_one_thing included.

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)
 
T

Trans

Ola said:
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.

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.
 
O

Ola Bini

Logan said:
% 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

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.
 
M

Mauricio Fernandez

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.

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.
 
M

Mauricio Fernandez

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.

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
 
M

Mauricio Fernandez

The above code effectively removes inherited methods too (as well as those
from other modules that were included previously): [...]
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:
[...]

Brilliant, the is_a? relation should not hold anyway after a partial
include, that was kind of where I blocked.

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).
 
T

Trans

Mauricio said:
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:

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.
 
L

Logan Capaldo

The above code effectively removes inherited methods too (as well
as those
from other modules that were included previously): [...]
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:
[...]

Brilliant, the is_a? relation should not hold anyway after a partial
include, that was kind of where I blocked.

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).

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.
 

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. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,990
Messages
2,570,211
Members
46,796
Latest member
SteveBreed

Latest Threads

Top