General Ruby Programming questions

S

Simon Kitching

Hi,

I'm writing a Ruby app at the moment, and have some questions I hope you
can help me with:

(a) string->class

I have a string containing the name of a class, and want to get the
corresponding Class object. My current approach is:
Module.class_eval(classname)

Is this the best approach?

(b) changing "require" searchpath

The $: variable holds the path used to locate files when the "require"
command is used. However this variable is read-only. In Perl, it is
possible to modify the equivalent path; can this be done in ruby?

The reason I want this is that I want to run my test harness from one
directory, and pick up my in-development code from some other directory.
Using "require '../xmldigester.rb'" doesn't work, because all the
requires in that required file resolve against $:.

(c) how invoke "hidden" methods?

I have a class Digester which responds to SAX events generated by REXML.
However I don't want methods like "start_element" to be public on
Digester, because this is just untidy: these methods are "internal" to
the Digester class, and should not be available for ordinary users of
this class.

In Java, I would declare package-scope methods on the Digester class,
then write an adapter class which implements SaxContentHandler, and
forwards calls to its methods to the appropriate Digester object
methods.

However Ruby doesn't have "package-scope" methods. Any idea how I
achieve a similar effect in Ruby?

(d) iterating in reverse

I have an Array object, and wish to iterate over its members in reverse.
I would prefer not to have to create a new Array object in order to do
this iteration. Is there any way other than writing
for(i=array.length-1; i>=0; --i) ....


Thanks,

Simon
 
F

Florian Gross

Simon said:
Hi,
Moin!

I have a string containing the name of a class, and want to get the
corresponding Class object. My current approach is:
Module.class_eval(classname)
Is this the best approach?

It isn't -- an user might inject arbitary code. Use this instead:

irb(main):001:0> Object.const_get('Class')
=> Class
(b) changing "require" searchpath
The $: variable holds the path used to locate files when the "require"
command is used. However this variable is read-only. In Perl, it is
possible to modify the equivalent path; can this be done in ruby?

It might be read-only, but that doesn't mean that you can't change the
object which it refers to:

irb(main):002:0> $:.push('new')
=> [..., 'new']
(c) how invoke "hidden" methods?

Simply send the message a method invocation would produce yourself:

irb(main):003:0> 5.send:)rand)
=> 0.748920713318512
(d) iterating in reverse
I have an Array object, and wish to iterate over its members in reverse.
I would prefer not to have to create a new Array object in order to do
this iteration. Is there any way other than writing

irb(main):004:0> [1, 2, 3].reverse_each { |item| puts item }
3
2
1

Regards,
Florian Groß
 
G

Gavin Sinclair

Simon Kitching wrote:
It isn't -- an user might inject arbitary code. Use this instead:

irb(main):001:0>> Object.const_get('Class')
=>> Class

That won't work for class names like "Test::Unit::TestCase". This is
discussed on the Wiki. Search "class" and you'll find it I'm sure.

Gavin
 
S

Simon Kitching

Hi Florian..


?? This isn't a greeting I've heard of before...

Anyway, Kia Ora!
Simply send the message a method invocation would produce yourself:

irb(main):003:0> 5.send:)rand)
=> 0.748920713318512

Whoa! So private methods can be called after all???!

Not the end of the world, I guess. After all, Python doesn't support
private methods at all, right? Still a bit odd in my opinion.

Is there any reason why Ruby allows private methods to be invoked like
this? (rather than provide a tidy alternative for problems like mine).

Anyway, you answered all my questions in one swoop - thanks!


Regards,

Simon
 
E

Eric Hodel

--gRZ38brEgCoUohoa
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
Simon said:
(b) changing "require" searchpath
The $: variable holds the path used to locate files when the "require"
command is used. However this variable is read-only. In Perl, it is
possible to modify the equivalent path; can this be done in ruby?
=20
It might be read-only, but that doesn't mean that you can't change the=20
object which it refers to:
=20
irb(main):002:0> $:.push('new')
=3D> [..., 'new']

You really want to unshift here, the Array is read front to back, so new
libs that override base libs won't get require'd.
(d) iterating in reverse=20
I have an Array object, and wish to iterate over its members in reverse.
I would prefer not to have to create a new Array object in order to do
this iteration. Is there any way other than writing
=20
irb(main):004:0> [1, 2, 3].reverse_each { |item| puts item }
3
2
1

You can also use reverse. An Array is smart, reverse takes constant time.

[1,2,3].reverse.map do |i| i * 2 end

--=20
Eric Hodel - (e-mail address removed) - http://segment7.net
All messages signed with fingerprint:
FEC2 57F1 D465 EB15 5D6E 7C11 332A 551C 796C 9F04


--gRZ38brEgCoUohoa
Content-Type: application/pgp-signature
Content-Disposition: inline

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.2 (FreeBSD)

iD8DBQE/jdeZMypVHHlsnwQRAr3HAKCKrbnPdL6qsRPy4hO4Bfe0OvXqwACg39Ja
BnyNvXWDbaq1d1ia1wzhoks=
=k36U
-----END PGP SIGNATURE-----

--gRZ38brEgCoUohoa--
 
E

Eric Hodel

--Y46ssxGX9/CNNfN6
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
=20
Whoa! So private methods can be called after all???!
=20
Not the end of the world, I guess. After all, Python doesn't support
private methods at all, right? Still a bit odd in my opinion.

Is there any reason why Ruby allows private methods to be invoked like
this? (rather than provide a tidy alternative for problems like mine).

I believe I'm giving a fairly accurate paraphrase of matz when I say
that Ruby doesn't prevent you from writing sloppy code, it just makes it
look ugly enough so that you don't do it.

Scary things tend to look scary in Ruby.
Evil things tend to look evil in Ruby.

--=20
Eric Hodel - (e-mail address removed) - http://segment7.net
All messages signed with fingerprint:
FEC2 57F1 D465 EB15 5D6E 7C11 332A 551C 796C 9F04


--Y46ssxGX9/CNNfN6
Content-Type: application/pgp-signature
Content-Disposition: inline

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.2 (FreeBSD)

iD8DBQE/jdiEMypVHHlsnwQRAtdxAKDERec4ZwpSgFymVIij+yiDJlLrbQCgtFiW
B5EVDSLj/Ur4zC2z9mRwm8E=
=DYTC
-----END PGP SIGNATURE-----

--Y46ssxGX9/CNNfN6--
 
G

Gavin Sinclair

[Simon wrote:]
Whoa! So private methods can be called after all???!

Not the end of the world, I guess. After all, Python doesn't support
private methods at all, right? Still a bit odd in my opinion.

Is there any reason why Ruby allows private methods to be invoked like
this? (rather than provide a tidy alternative for problems like mine).

It may help to think of it not as a method being invoked, rather a message
being sent. The object decides how it wishes to respond to that message.

After all, you can't invoke a method that doesn't exist, but you can get a
response back from a message that doesn't have a corresponding method, if
you get my drift.

class X
def method_missing(sym, *args)
5
end
end

x = X.new
x.foo # -> 5
x.bar # -> 5
x.quux # -> 5
x.method:)foo) # -> NameError: undefined method 'foo'
x.nil? # -> false

Gavin
 
J

James Britt

Eric said:
Simon Kitching ([email protected]) wrote: ...


I believe I'm giving a fairly accurate paraphrase of matz when I say
that Ruby doesn't prevent you from writing sloppy code, it just makes it
look ugly enough so that you don't do it.

And if you really want to trap such calls you can override 'send':

module SafeSend
alias evil_unsafe_send send
def send( m, *args )
msg = "No sending of private messages!"
raise msg if self.private_methods.include? m.to_s
evil_unsafe_send( m, *args )
end
end


class Foo
include SafeSend
# ...
end
 
S

Simon Kitching

[Simon wrote:]
(c) how invoke "hidden" methods?

Simply send the message a method invocation would produce yourself:

irb(main):003:0> 5.send:)rand)
=> 0.748920713318512

Whoa! So private methods can be called after all???!

Not the end of the world, I guess. After all, Python doesn't support
private methods at all, right? Still a bit odd in my opinion.

Is there any reason why Ruby allows private methods to be invoked like
this? (rather than provide a tidy alternative for problems like mine).

It may help to think of it not as a method being invoked, rather a message
being sent. The object decides how it wishes to respond to that message.

After all, you can't invoke a method that doesn't exist, but you can get a
response back from a message that doesn't have a corresponding method, if
you get my drift.

Yes, that's clear.

However I could still rephrase my original question as:

Why doesn't ruby throw a NoMethodError exception when a message is
sent to a method which is private using the "send" feature?

The purpose of marking a method as private is to indicate that the only
valid invocation of the code within that method is when the invoker is
another method on the same class (java convention) or the same object
(ruby convention).

Quite often private methods don't preserve class invariants. And
exposing methods that can break class invariants to the outside world is
never a good idea.

As I said, it's no big deal. Perl, javascript and (as far as I know)
Python don't support the concept of private methods at all.

However I am curious to know if there is a real justification for
allowing private methods to be invoked via "send", or if it just an
accident that this works. Ruby takes encapsulation so seriously in many
other ways...

Cheers,

Simon

PS: I did eventually find that class lookup implementation on the wiki.
I think it would be really nice to have this in the std libs (eg as a
class method on the Class or Object classes).

http://www.rubygarden.org/ruby?FindClassesByName

def get_class(name)
name.split(/::/).inject(Object) {|p,n|
p.const_get(n)
}
end
 
S

Simon Kitching

And if you really want to trap such calls you can override 'send':

module SafeSend
alias evil_unsafe_send send
def send( m, *args )
msg = "No sending of private messages!"
raise msg if self.private_methods.include? m.to_s
evil_unsafe_send( m, *args )
end
end


class Foo
include SafeSend
# ...
end

Hah! Consider my questions answered :)

I love the way Ruby can override default behaviours.....

Regards,

Simon
 
G

Gavin Sinclair

[Simon wrote:]
PS: I did eventually find that class lookup implementation on the wiki.
I think it would be really nice to have this in the std libs (eg as a
class method on the Class or Object classes).

http://www.rubygarden.org/ruby?FindClassesByName

def get_class(name)
name.split(/::/).inject(Object) {|p,n|
p.const_get(n)
}
end

I'll add this to extensions.rubyforge.org within a few days. At first
blush, Kernel seems the right place to put it.

Gavin
 
F

Frank Schmitt

Simon Kitching said:
Hi Florian..



?? This isn't a greeting I've heard of before...

It's (Northern) German for good morning, although AFAIK, most people
there use it as a replacement for hello ("Moin Moin!") any time of
the day.

You might also encounter

Guten Morgen! "high", formal german (good morning)
Guten Tag! "high", formal german (hello)
Gruess Gott! southern Germany, esp. Bavaria (hello)
Gruess Dich! southern Germany, esp. Bavaria (hello, among friends)
Servus! southern Germany (hello, among friends)
Gruezi! Switzerland (hello)
....

As you might guess, I'm living in Bavaria :)
Anyway, Kia Ora!

What language is this?

kind regards
frank
 
M

Marcin 'Qrczak' Kowalczyk

W li¶cie z czw, 16-10-2003, godz. 02:14, James Britt pisze:
And if you really want to trap such calls you can override 'send':

And then if someone really wants to invoke a private method, he will
just add a public forwarding method.

You could try to protect against all that, but what for?
 
M

Marcin 'Qrczak' Kowalczyk

W li¶cie z czw, 16-10-2003, godz. 01:26, Eric Hodel pisze:
You can also use reverse. An Array is smart, reverse takes constant time.

VALUE
rb_ary_reverse(ary)
VALUE ary;
{

[...]

while (p1 < p2) {
tmp = *p1;
*p1++ = *p2;
*p2-- = tmp;
}

Doesn't look like constant time for me.
 
R

Robert Klemme

Marcin 'Qrczak' Kowalczyk said:
W li¶cie z czw, 16-10-2003, godz. 01:26, Eric Hodel pisze:
You can also use reverse. An Array is smart, reverse takes constant
time.

VALUE
rb_ary_reverse(ary)
VALUE ary;
{

[...]

while (p1 < p2) {
tmp = *p1;
*p1++ = *p2;
*p2-- = tmp;
}

Doesn't look like constant time for me.

I think he meant reverse_each which has no overhead compared to each, i.e.
both are O(n).

Regards

robert
 
J

James Britt

Marcin said:
W li¶cie z czw, 16-10-2003, godz. 02:14, James Britt pisze:




And then if someone really wants to invoke a private method, he will
just add a public forwarding method.

You could try to protect against all that, but what for?


I suspect that 'send' is most used when a specific method name is
unknown until run time, and you want to dynamicaly invoke a method.
However, there's the chance one could inadvertantly call a private
method; adding some protective measures to 'send' would help block this.

Sure it's true that one can always hack around such measures, but the
idea is to raise the bar. (And I suppose some combinbation of
overriding 'method_added' and inspecting the call stack could be thrown
in to make things harder.)

James
 
C

Christoph

Simon Kitching wrote:
....
Is there any reason why Ruby allows private methods to be invoked like
this? (rather than provide a tidy alternative for problems like mine).

If I remember correctly Matz once said something to the effect
that forcing people to use obscure syntacs like

obj.__send__:)my_meth,my_arg)

or the actually faster

obj.instance_eval { my_meth(my_arg) }

is mend as a deterrent for using private methods capriciously.
As Marcin 'Qrczak' Kowalczyk already pointed out you can

always wrap a private method in a public method anyway -
or you can simply publicize a private method with Module::public.
(incidentally Module::public itself is a private method:)

---
class A
end

class << A
public :include
end

module M
end

A.include M
 
E

Eric Hodel

--tqI+Z3u+9OQ7kwn0
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
W li?cie z czw, 16-10-2003, godz. 01:26, Eric Hodel pisze:
=20 me.
=20
Doesn't look like constant time for me.

Ah yes, you're right. My sample must have been too small to show the
difference.

--=20
Eric Hodel - (e-mail address removed) - http://segment7.net
All messages signed with fingerprint:
FEC2 57F1 D465 EB15 5D6E 7C11 332A 551C 796C 9F04


--tqI+Z3u+9OQ7kwn0
Content-Type: application/pgp-signature
Content-Disposition: inline

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.2 (FreeBSD)

iD8DBQE/kvuBMypVHHlsnwQRAj3cAKDfQjCxPbumz2g9cz+ccQqycWSRhwCfSSHQ
34TtTxwPUGUqMJ4qYFlO0CE=
=iHjn
-----END PGP SIGNATURE-----

--tqI+Z3u+9OQ7kwn0--
 

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

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top