Problem with Rails.root being a Pathname

Changing Rails.root to a Pathname was great and all (I’m a strong believer that us Rubyists shouldn’t handle paths or URLs as String objects), but there’s one thing I’ve discovered just now that kinda ruins the joy.

With string paths, these two lines are equivalent:

“#{Rails.root}/tmp/foo”

Rails.root + “/tmp/foo”

Now that Rails.root is a Pathname, they do totally different things. While the first one becomes “/path/to/app/tmp/foo”, the second becomes just “/tmp/foo” because Pathname overloads the “+” operator in a similar way that URI does it.

Thoughts? Think this may break apps? It broke mine, but I’m not complaining – being subscribed to edge commits helps me resolve these kinds of issues in a matter of seconds. To other folks upgrading to 2.3, this may take much longer to track down.

I know of at least two of my apps that will break. But I also agree
with the sentiment of
dealing with a slightly higher abstraction than just strings when
working with the filesystem.
So I'm willing to pay the (small) price and I thank you for bringing
it to my attention!

I do think this should be addressed. Why is the "+" operator
overridden in Pathname in the first place? Is there some benefit to
this way of concatenating that I'm not seeing? Rails.root + "/foo"
should append "/foo" to the end of Rails.root. That's what the code
says anyway.

Regards,

Ryan

Pathname #+ just coerces the argument to a pathname before
concatenating, so that it can return another pathname instance. The
problem with Mislav's example is the preceding "/" on "/tmp/foo":

(Rails.root + "/tmp/foo").to_s

=> "/tmp/foo"

(Rails.root + "tmp/foo").to_s

=> "/Users/geoff/Sites/edgerails/tmp/foo"

Not intuitive, I agree. String interpolation, however, works as
expected:

"#{Rails.root}/tmp/foo"

=> "/Users/geoff/Sites/edgerails/tmp/foo"

Maybe we could issue a warning when people try to call Pathname#+ with
a String argument? And suggest the usage with Pathname#join.

If people still want to concatenate, they should do it the ugly way:
Rails.root.to_s + '/tmp/foo'.

Pathname #+ just coerces the argument to a pathname before
concatenating, so that it can return another pathname instance. The
problem with Mislav's example is the preceding "/" on "/tmp/foo":

(Rails.root + "/tmp/foo").to_s

=> "/tmp/foo"

(Rails.root + "tmp/foo").to_s

=> "/Users/geoff/Sites/edgerails/tmp/foo"

Man that's weird!

Not intuitive, I agree. String interpolation, however, works as
expected:

"#{Rails.root}/tmp/foo"

=> "/Users/geoff/Sites/edgerails/tmp/foo"

This is a little iffy, but I'd like to let this sit in edge for a
while and we'll see what other surprises are lurking in there.

We'll probably need to make sure this is well documented in the
eventual 2.3 release notes, or rolled back if it's too painful to make
a smooth migration path for users.

It is cool, though – for instance, you can also do this:

Rails.root + ‘…/foo’

Yes, this will work with strings also – but Pathnames are smart enough to actually interpret those relative paths before passing them to ‘require’ and friends.

... which is a helpful feature -- you can end up requiring a file
twice if you pass two different strings to require that reference the
same file:

# requires 'mylib' twice
require 'mylib'
require 'mysubdir/../mylib'