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'