On Monday, August 16, 2010 07:34:19 am Diego B. wrote:
Ruby was build to help manage linux
systems, rigth?
I don’t think so. Perl, maybe, but Wikipedia has this quote from Matz:
“I wanted a scripting language that was more powerful than Perl, and
more
object-oriented than Python. That’s why I decided to design my own
language.”
Why build a gem/program to send mail if you can send with sendmail?
[email protected] pretty much nailed this one – sendmail isn’t a
particularly good interface for sending mail. Maybe it’s a flaw in Ruby,
but
it really is more difficult to invoke an external command (with
appropriate
arguments), communicate with it, and capture the result than it is to
use
something more native to Ruby.
I haven’t sent email in awhile, but let’s use a more direct example:
grabbing
the contents of a URL. I could do it with wget:
system(‘wget’, some_url) || raise “wget exited with error code #{$?}!”
contents = File.read ‘/wherever/wget/put/the/file’
Or I could do it with the ‘curl’ binary (or a more complex wget
command):
contents = curl #{some_url}
…but then, how do I detect errors? What if some_url needs to be shell-
escaped? Hmm. Maybe I could do popen, but that’s more complicated…
Or I could do this in pure Ruby:
require ‘open-uri’
contents = open(some_url) {|x| x.read}
To me, this is both more natively Ruby, and more in keeping with the
Unix
philosophy – open-uri is a small library that does one job, and does it
well.
I then don’t have to repeat myself with tons of error-handling crap all
over
the place, as I do with system, or risk silent errors, as my above
backticks
example might do – I can assume open-uri will return an error if
something
goes wrong.
If I need more control, I can always use Net::HTTP, or one of the many
other
libraries available. Aside from having comprehensive documentation,
it’s,
again, more native. Here’s an example from the standard library docs:
res =
Net::HTTP.post_form(URI.parse(‘http://www.example.com/search.cgi’),
{‘q’=>‘ruby’, ‘max’=>‘50’})
And here’s that same example with Curl:
curl -F q=ruby -F max=50 http://www.example.com/search.cgi
Here it is calling Curl from Ruby:
uri = ‘http://www.example.com/search.cgi’
q = ‘ruby’
max = 50
res = curl #{uri} -F q=#{q} -F max=#{max}
But what if I want to generate those query options on the fly, from a
hash?
It’s trivial to do with Net::HTTP.post_form, but with curl?
uri = ‘http://www.example.com/search.cgi’
options = {‘q’ => ‘ruby’, ‘max’ => 50}
options_string = options.each_pair.map{|k,v| “-F #{k}=#{v}”}.join(’ ')
res = curl #{uri} #{options_string}
And the best part? In each of the two examples above, I again may have
to
escape things for the shell, if I don’t know where those options are
coming
from. Curl is sane enough that I probably don’t have to escape them for
Curl
itself, at least. And I’ve again done no error checking – I’m just
getting a
giant string back.
To be fair, I didn’t do any error checking in my earlier example from
Net::HTTP, but that’s easy to do:
res =
Net::HTTP.post_form(URI.parse(‘http://www.example.com/search.cgi’),
{‘q’=>‘ruby’, ‘max’=>‘50’})
res_value = res.value #throws exception unless it was successful
And if there is an error, I can tell exactly what kind of error:
res.kind_of? Net::HTTPNotFound # true
res.code # "404"
res.body # contents of the 404 page
With Curl, I have to pass some extra commandline options to ensure I get
that
information, probably specifying a custom output format so I can get the
error
code, and then I have to parse that output format. Yuck – I may as well
parse
raw HTTP.
I didn’t really answer your question about Sendmail, then, but I suspect
the
answer would be similar to the answer to the same question about Curl –
maybe
more so. The Mail gem, for instance, handles things like file
attachments – I
don’t think the sendmail interface does that, at least not directly, so
you’d
still need something like Mail to wrap Sendmail if you wanted to use it
sanely.
Now, you might well ask why we don’t just wrap these existing binaries
in a
gem. It’s not clear that there would be a huge benefit, though. Most of
the
time, properly-engineered Unix binaries also have C libraries, and it’s
probably easier and cleaner to write a Ruby binding for a C library than
to
write a Ruby wrapper around a Unix executable – for one, you don’t have
to
deal with escaping commandline arguments or passing text back and forth
through a pipe just to communicate, you’ve got an actual API to work
with.
There are a number of side benefits to the current approach. For one,
it’s
ridiculously portable – the JRuby guys have Rails running on AppEngine
and
IRB running on Android, in environments which wouldn’t make it easy (if
it
were allowed at all) to use C extensions or binaries. I can write my
HTML
parsing once, using Nokogiri, and it will use libxml on MRI and (I
think) the
standard Java XML libraries on JRuby.
And, even more fringe benefits – not forking off a separate process per
request is likely faster, and passing around Ruby data structures, or
ever
converting from Ruby strings to C strings, seems cheaper than converting
everything to giant blobs of text, squeezing it through a pipe, and
parsing it
in another process.
But you’ll notice, I call these “fringe benefits”, and I use a lot of
weasel
words. For all I know, separate processes might be faster. I do use
RESTful
services as components of a larger app, the Unix philosophy taken to an
extreme – this could be one physical server that does one job and
does it
well.
I think it just comes down to this: Ruby semantics are richer than Unix
semantics, and where Unix makes sense, HTTP might make even more sense.
I’m not saying “system considered harmful” – far from it, I’d be the
first to
call Unix commands when appropriate (xdg-open, for example), or even to
write
a quick shell script in sh or bash instead of trying to learn something
like
Rush.
Why build process monitor if you can use monit?
Well, or God, or any number of other things. I think the answer to this
one
is, choice is good, and this isn’t quite a solved problem yet.