4
JAN2006
Pathname and Enumerator
I'm always digging around in Ruby's standard library looking for new toys. It's one-stop-shopping for geeks. I love it.
There really are some great tools in there that can help you do your work easier. Let me tell you a little about two I am using on a current application.
Pathname
Pop Quiz: Which would you rather work with?
if File.exist? some_dir
data = File.read( File.join( File.dirname(some_dir),
"another_dir",
File.basename(some_file) ) )
# ...
end
Or:
if some_dir.exist?
data = (some_dir.dirname + "another_dir" + some_file.basename).read
# ...
end
If you prefer the second version, the Pathname
library is for you.
Ruby's file manipulations are usually fine for normal work, but anytime I start messing with directories I really start feeling the pain. For that kind of work, I find Pathname
to be a superior interface. My current project makes great use of the methods in the above example as well as Pathname#entries
, Pathname#relative_path_from
, and more.
Look up the documentation if the above has you curious and you won't be sorry. You're only one require away from a smooth directory interface.
Enumerator
Pop Quiz: Ever wish Ruby's Strings iterated over characters instead of numbers?
unless "TEAM".enum_for(:each_byte).find { |c| c == ?I }
puts "There's no I in T-E-A-M!"
end
Pop Quiz: Ever needed map_with_index()
?
table_rows.enum_for(:each_with_index).map do |row, i|
[row, i % 2 == 0 ? :even : :odd]
end
The magic enum_for()
returns an Enumerator
, which is a standard Enumerable
object using the passed method as each()
. This gives you the power of all the other iterators (including find()
and map()
), but on the data of your choosing.
This is so handy, Enumerator
has been moved from the standard library to the language core, as of Ruby 1.9. Not only does this allow you to drop the require statement, the core version of Enumerator
has been enhanced. All of the standard iterators will now return an Enumerator
, if called without a block. That makes map_with_index()
even easier to create:
table_rows.each_with_index.map do |row, i|
# ...
end
As an added bonus, the Enumerator
library adds two more handy iterators to Enumerable
:
tic_tac_toe = "XO X OX".split("")
tic_tac_toe.each_slice(3) do |row|
puts " " + row.join(" | ")
puts "-" * 11 unless row == tic_tac_toe[-3..-1]
end
As you can see, each_slice()
let's you grab a passed number of elements at a time. The other method, each_cons()
, is similar, but will keep consecutive elements together:
"by James Gray".split.each_cons(2) do |words|
if words.all? { |word| word =~ /^[A-Z]/ }
puts "#{words.join(' ')} is an author."
end
end
Note that each_slice()
wouldn't have found my name, since it would have tried ["by", "James"]
and ["Gray"]
, but not ["James", "Gray"]
.
Again, see the documentation for more details. Advanced Enumerator
usage is Ruby Voodoo and it will help you impress your friends and coworkers.
Comments (1)
-
Daniel Berger February 5th, 2006 Reply Link
Regarding
Pathname
, seepathname2
if you want aPathname
library that works properly on Windows (i.e. handles backslashes and UNC paths).Come to think of it, I think I forgot to implement
relative_path_from
. I'll add that to my TODO list. :)