Style question: where to put additions to standard classes?

I’ve just started on my first decent-sized Ruby project and before the
number of files spirals too far out of control I wanted to ask for some
advice on what the accepted “best practice” for organizing files and
code might be. I’ve tried to download and look at some largish Ruby
projects but I don’t have a clear picture of what the Right Thing is.

Specifically, just say I add some methods to an existing standard class
like Array, what would be the best convention for choosing where to
store the added code? Here’s a concrete example: let’s say I want to
add a “eol?” method to the StringScanner class?

class StringScanner
def eol?
return true if self.eos? or self.match?(/[\n\r]/)
false
end
end

At the moment I have stuck it inside another file (for one of the
classes that needs to use this method). But what if I want to keep it
in a separate file? What would be the conventional name for such a file
(evidently not “strscan.rb” because that would most likely cause
confusion with the StringScanner implementation when doing a “require
‘strscan’”).

Likewise I had a similar doubt when I wanted to added some assertions
of my own in addition to those which come with Test::Unit::Assertions.

The original assertions are defined in a shared location terminating in
“test/unit/assertions.rb”; but how would I name my additions file if I
wanted to keep things in a separate file? For the time being I’ve just
stuck the new code at the top of another file, but somehow it just
doesn’t feel right…

On 1/11/07, Greg H. [email protected] wrote:

(evidently not “strscan.rb” because that would most likely cause
doesn’t feel right…
I don’t know the answer, and honestly, this is the only thing about
Ruby that makes me nervous. I once heard about a Python project where
the same method would have different return types depending on which
other methods had been called on the object at earlier points in the
program. It was a maintenance nightmare. In practice, I’ve never
actually seen it go wrong, but it seems to me that there are inherent
risks when you can modify any part of the system from any point within
the system. What if you use two different gems which both modify a
fundamental class?

In Rails projects, I’ve put changes to ActiveRecord::Base in the
environment config file because I was in a hurry. In the S3 library I
mentioned in another thread, I had to change an HTTP library’s
file-handling. I put it in the S3 library, because that was the
functionality which required the change. It’s pretty obvious that
either one of those changes could have had unpleasant side effects
further down the road, although thankfully, neither one has (yet).

I think what I’d probably recommend is something like a
“lib/modifications/” dir. It really won’t make a difference to Ruby
where these files live, but if you keep them somewhere like
“lib/modifications/new_assertions.rb”, that seems pretty tidy.

On Fri, Jan 12, 2007 at 12:55:09AM +0900, Greg H. wrote:
[…]

end
In many cases it is not necessary to reopen existing classes at all, and
is
often preferable not to. For your example, I would guess that you are
doing
some sort of parsing, maybe something like (pseudo-Ruby, because I don’t
really know StringScanner):

scanner = StringScanner.new(open(filename))
scanner.each do |token|
if scanner.eol?
#do stuff
else
#do other stuff
end
end

You need scanner to respond to #eol?, but you don’t need to modify
StringScanner to do it. Consider using a module:

module StringScannerEol
def eol?
# cleaned up a little
self.eos? or self.match?(/[\n\r]/)
end
end

Now we change the first line of the code above to:

scanner = StringScanner.new(open(filename)).extend(StringScannerEol)

Now scanner will respond to #eol? and all is well.

At the moment I have stuck it inside another file (for one of the
classes that needs to use this method). But what if I want to keep it
in a separate file? What would be the conventional name for such a file
(evidently not “strscan.rb” because that would most likely cause
confusion with the StringScanner implementation when doing a “require
‘strscan’”).

The module I gave above can be appropriately included in the file with
the
code that uses it.

Likewise I had a similar doubt when I wanted to added some assertions
of my own in addition to those which come with Test::Unit::Assertions.
[…]

Again, a good opportunity for a module. Your test classes can include a
module with the additional assertions.

–Greg