> In general, do folks have any recommendations for how to learn good
> ruby style?
If you recognize that something is ugly enough to bother you, consider
yourself blessed with vision and cursed with the desire to do better.
You can harness this power by seeking out solutions to addressing these
annoyances, and asking the mailing list and peers for advice is great.
It really helps to read other people's well-written libraries. I learned
many good techniques by reading the Ruby on Rails source code, although
admittedly, there's a frightening amount of indirection in places but I
usually understand why. Another good place to look is the Facets
library, it's full of nice, short methods that do amazing things.
Try to find opportunities to write code socially by pair programming,
sprinting and such. The discussions you have together on "That's ugly,
how can we make this better?" will really help.
...
I'd really like to get some code sprints going again, they were really
good for these sorts of discussions. Now that I'm recovering from my
illness, I hope to start making it to the Thursday hackathons at the
Lucky Lab and invite others to join me there. I'll post a reminder on
Wednesday. :)
-igal
> I would suggest writing a helper function to DRY (Don't Repeat
> Yourself) your code, i.e.:
>
> def is_nil_or_blank(x)
> x.nil? || x == ''
> end
>
> Then call it with if is_nil_or_blank(variable_name) ... end
>
Compare the above technique with this conceptually similar one in Ruby
on Rails' activesupport-2.3.4/lib/active_support/core_ext/blank.rb
class Object
# An object is blank if it's false, empty, or a whitespace string.
# For example, "", " ", +nil+, [], and {} are blank.
#
# This simplifies
#
# if !address.nil? && !address.empty?
#
# to
#
# if !address.blank?
def blank?
respond_to?(:empty?) ? empty? : !self
end
end
> A quick intro for myself. I'm a Ruby/Rails developer based in
> Atlanta, GA. I've been using Ruby for two and a half years and teach
> a beginning Ruby on Rails class called Emerald City (http://groups.google.com/group/atlrug-emeraldcity
> ). Emerald City is a coding group of the Atlanta Ruby Users Group ( ~
> 100 members), I'm also a co-leader for the AtlRUG group.
>
> I'm looking to relocate to the Seattle/Portland area within the next
> five years or so. In an effort to get to know the tech market
> better, I've started networking and that's how I found pdxruby.
>
> I hope you don't mind me lurking on the mailing list. :)
Great, glad to have here. :)
-igal
Hah, I'd never thought about !self. That's hilarious and totally wonderful.
I really want to start doing some sprints as well. I'm working in a
PHP shop now and I don't want my skills to get rusty until I get back
into Ruby development. If you guys show up and can't find people to
program with please give me a ring: I've probably forgotten but I do
really want to sprint. Same goes for if you're just bored and want to
hack something out involving a tin can, a wiimote, and 3 dust bunnies.
I'm sure we can make it do something entertaining. :P
My contact for all the world to see is:
Chuck Vose
651-214-0272
As long as you're going to be extending object, you could get the same
semantics by just writing (w. Python style indentation, 'cause that's
how my brain works):
class Object
def empty?
!self
end
end
and just calling x.empty? as your test. For Strings, Arrays, etc.
you'll get the usual empty? and for everything else you'll get !self.
-- Markus
The "empty?" method is found on a number of standard Ruby objects.
Changing how objects respond to a standard method is naughty. This
particular change probably won't hurt anyone, but is a step down a
slippery slope that should be approached with caution.
In contrast, the "blank?" method approach adds new behavior not present
in standard Ruby objects, which makes it clean and safe.
-igal
PS: I like how the term "Python style indentation" and its use above
should make both Ruby and Python programmers squirm. :)
PPS: I look forward to your brain-twister for tomorrow's meeting, if you
have time to prepare one.
The "empty?" method is found on a number of standard Ruby objects.
Changing how objects respond to a standard method is naughty. ...
Note also that ActiveSupport added #present? -- which is the opposite
of #blank? -- sometime after I learned about #blank?.
Quite useful for removing double negatives (e.g., "if !foo.blank?").
-Sam
> That's clever and seems to work, but I feel that there's an important issue.
> The "empty?" method is found on a number of standard Ruby objects.
> Changing how objects respond to a standard method is naughty. This
> particular change probably won't hurt anyone, but is a step down a
> slippery slope that should be approached with caution.
> In contrast, the "blank?" method approach adds new behavior not present
> in standard Ruby objects, which makes it clean and safe.
So a few random counter points, 'cause feeling around to find out where
the edge is in cases like this is both interesting and fun:
* Adding "empty?" in this way doesn't conflict with the behavior
of existing classes which implement "empty?", it simply gives it
(in a reasonable way) to classes that presently don't have it.
In other words, it doesn't "change how objects respond to a
standard method" at all; it just extends the method to cover all
objects.
* Adding "blank?" to Object, by the same token, isn't really any
safer--you could still conflict with a library that had defined
it with different semantics.
-- Markus
P.S. If you are worried about things like that, you probably don't want
to use rails.
Ever.
You should probably delete it off your system.
Oh, and gems too.
And don't get me started on how sausages are made.
This method missing magic is how RSpec handles a lot of the be_*
matchers. For example: "foo.should be_null" ends up checking if
"foo.null?" is true. Works for any ? method. "foo.should
be_some_crazy_check_that_only_works_on_foos" works just fine.
Not sure if that really helps you, but it's certainly very close to
the behavior you're describing.
-Jacob
The only trouble with that method missing trick is preserving it down through the inheritance hierarchy. If any class implements `method_missing` without using alias_method_chain then the new semantics will be lost.
Rspec doesn't have that problem because it handles all of the method missing magic in `Kernel`.
That said, I plan to start using alias_method_chain with all of my `method_missing` definitions from now on :)
> Actually, based on your original example, I think it's safe to say > that in ruby, particularly ...
=======================================================
Brent Miller
http://www.foliosus.com/
"The problem is that once you have done away with the
ability to make judgments as to right and wrong, true
and false, etc., there's no real culture left. All
that remains is clog dancing and macrame."
-- Neal Stephenson
=======================================================
--~--~---------~--~----~------------~-------~--~----~ You received this message because you are su...
> * Adding "empty?" in this way doesn't conflict with the behavior
> of existing classes which implement "empty?", it simply gives it
> (in a reasonable way) to classes that presently don't have it.
>
> In other words, it doesn't "change how objects respond to a
> standard method" at all; it just extends the method to cover all
> objects.
>
The "empty?" method is a standard part of many Ruby objects and there's
existing code that relies on its behavior and whether it's present. A
quick grep showed dozens of uses of "respond_to?(:empty?)" in my
libraries. Your patch changes this behavior, and may therefore break
these libraries, hence my concern.
> * Adding "blank?" to Object, by the same token, isn't really any
> safer--you could still conflict with a library that had defined
> it with different semantics.
I'm talking about reducing the likelihood of breakage and the effort
needed for a fix. It's easier to avoid loading an additional custom
library that redefines a same-named new behavior than it is to fix all
the existing libraries that depend on some standard behavior that changed.
> P.S. If you are worried about things like that, you probably don't want
> to use rails.
I like Rails and appreciate many of the extensions provided by
ActiveSupport. However, I've lost plenty of hours trying to unravel
really bizarre bugs caused by their changes to standard behavior of
methods like "to_s", "inspect", etc. Eventually these things were fixed,
but it wasted a lot of time for many people before the desired new
behavior could be reconciled with the expected standard old behavior.
> Oh, and [probably don't want to use] gems too.
>
It's hard to avoid, but can be a pain. For example, I had to ship fixes
for many apps that broke when RubyGems 1.3.5 was released because it
changed what it was monkey patching. Again, that's just the sort of
thing I'm concerned about.
-igal
Agreed. And if even a few people on the list enjoy or learn from the
back and forth, so much the better.
> > * Adding "empty?" in this way doesn't conflict with the behavior
> > of existing classes which implement "empty?", it simply gives it
> > (in a reasonable way) to classes that presently don't have it.
> >
> > In other words, it doesn't "change how objects respond to a
> > standard method" at all; it just extends the method to cover all
> > objects.
> >
> The "empty?" method is a standard part of many Ruby objects and there's
> existing code that relies on its behavior and whether it's present. A
> quick grep showed dozens of uses of "respond_to?(:empty?)" in my
> libraries. Your patch changes this behavior, and may therefore break
> these libraries, hence my concern.
Argh! You sank my battleship!
This (the use of respond_to? (& defined?, and methods.include? and ...))
is the only real killer counter argument I could see to this point. If
you'd gone too easy on me I was going to bring it up myself.
The only semi-cogent counter-counter argument I can see is that such
testing, if done "honestly" should not break under the mooted extension
of empty?'s range of applicability. They could, for example, occur only
in fungible implementations of the same functionality we're after, or
similar constructs.
I fear, though, that at least some of them would turn out to be
"dishonest" uses--testing for empty, say, as a proxy for checking for
indexablity or enumerablity (I once saw exactly this, because the coder
knew he could write "respont_to? :empty?" but didn't think he could
write "respond_to? :[]=") or even worse, as a backdoor way of doing
class-based dispatch (aka the "Duct taping" anti-pattern).
> > P.S. If you are worried about things like that, you probably don't want
> > to use rails.
> I like Rails and appreciate many of the extensions provided by
> ActiveSupport. However, I've lost plenty of hours trying to unravel
> really bizarre bugs caused by their changes to standard behavior of
> methods like "to_s", "inspect", etc. Eventually these things were fixed,
> but it wasted a lot of time for many people before the desired new
> behavior could be reconciled with the expected standard old behavior.
Yep, I'm right with you.
Except maybe for the "I like Rails" part. ;)
-- Markus
>>> * Adding "empty?" in this way doesn't conflict with the
>>> behavior
>>> of existing classes which implement "empty?", it simply
>>> gives it
>>> (in a reasonable way) to classes that presently don't have
>>> it.
>>>
>>> In other words, it doesn't "change how objects respond to a
>>> standard method" at all; it just extends the method to
>>> cover all
>>> objects.
>>>
>> The "empty?" method is a standard part of many Ruby objects and
>> there's
>> existing code that relies on its behavior and whether it's present. A
>> quick grep showed dozens of uses of "respond_to?(:empty?)" in my
>> libraries. Your patch changes this behavior, and may therefore break
>> these libraries, hence my concern.
>
> Argh! You sank my battleship!
>
> This (the use of respond_to? (& defined?, and methods.include?
> and ...))
> is the only real killer counter argument I could see to this point.
> If
> you'd gone too easy on me I was going to bring it up myself.
This brings up a question in my mind. In the Perl community, it's
considered good practice that anytime you use AUTOLOAD, you should
also override UNIVERSAL::can to respond in "the right way". However,
I don't think I've ever seen a Ruby class with a method_missing also
override respond_to?. Do Rubyists simply not consider this a
problem? Or are they just lazy? :-)
-kevin
Excellent point. The problem (IMHO) is that the barrier is much higher
for properly overriding "respond_to?" --
1) You have to properly chain it, and be aware that (at least in
theory) other may be doing the same thing at the same level.
2) If you are responding to "wildcard" methods, you have to make
sure that you match the same templates in both places (not DRY)
or add a level of shared template matching (not as simple).
3) If you are overriding a deeper method missing (perhaps
unintentionally!) you have to figure out what methods it adds
and mask them.
Even worse is the question of what to do with My_class.methods -- should
it be extended to match? What if the method_missing can respond to an
unbounded (effectively infinite) list of messages? Do you return them
as a lazy list, or...?
-- Markus
Ah ha!
My straw man rises from the dead!
> > > In other words, it doesn't "change how objects respond to a
> > > standard method" at all; it just extends the method to cover all
> > > objects.
> > The "empty?" method is a standard part of many Ruby objects and there's
> > existing code that relies on its behavior and whether it's present. A
> > quick grep showed dozens of uses of "respond_to?(:empty?)" in my
> > libraries. Your patch changes this behavior, and may therefore break
> > these libraries, hence my concern.
>
> Argh! You sank my battleship!
>
> This (the use of respond_to? (& defined?, and methods.include? and ...))
> is the only real killer counter argument I could see to this point. If
> you'd gone too easy on me I was going to bring it up myself.
But as Keven implicitly pointed out, if the "empty?" monkey patch were
done with method_missing (or if we overrode "respond_to?" to hide it!)
the problem goes away.
Yeeeeee...haaaaa!
-- Markus
P.S. For anyone out there who might think I'm seriously recommending
this, ask around about me. And for anyone who thinks I'm _not_ serious
about these sorts of tricks being a real, tangible possibility, go dig
up early discussions on bignum, open structs, active_support, active
record, gems, the agents library, etc.
Reasonable, unless no one ever blindly calls the method (they always
check to see if it's there first).
So to work this requires that the calling code act unsafely or do ugly
rescue stuff.
Also, it doesn't deal with all cases as nicely:
* Methods that are called only once on each object of a classes
* Methods that change with circumstance
* Large sets of methods that are called once each
-- Markus
> This brings up a question in my mind. In the Perl community, it's
> considered good practice that anytime you use AUTOLOAD, you should
> also override UNIVERSAL::can to respond in "the right way". However,
> I don't think I've ever seen a Ruby class with a method_missing also
> override respond_to?. Do Rubyists simply not consider this a
> problem? Or are they just lazy? :-)
I've done it in some custom extensions to Builder::XmlMarkup to make
#to_s work sanely. I'm sorry to say I can't remember exactly *why* I
did it, but I think I was finding that otherwise calling #to_s on the
builder object was causing <to_s> elements to be inserted into the
final XML. Of course, this might be a special case because Builder
uses a "blank slate" object. Anyway, FWIW...
module Builder
class XmlMarkup
def to_html; target!; end
def to_str; target!; end
def to_s; target!; end
def respond_to?(method_id, include_private=false)
method_name = method_id.to_s
%w{to_html to_str to_s}.include?(method_name) || method_name
=~ /^[A-Za-z0-9]+$/
end
end
--John
Odd. I wouldn't think you'd need to do that in the first place, since
just defining the method as you do should update the "respond_to?"
results (they're dynamically generated). But since you aren't calling
super, you aren't getting that effect, but you are saying it responds
to all alpha-numeric methods, but not the "target!" that it obviously
does respond to...why were you doing this again?
-- Markus