Thursday, October 10, 2013

Ruby variable assignment... how does that really work?

I've recently been working to improve my Ruby skills, so I've started coding many of the exercises in "Cracking the Coding Interview" by Gayle Laakmann McDowell, implementing the problems in TDD/Ruby instead of in Java.

In the process, I ran into a real puzzler -- a bug that I and several friends looked at and couldn't quickly solve. It turned out to be really basic Ruby.

I had developed the following code as part of the answer to CtCI's problem 9.4: Create all the subsets from a set (you can find the complete solution here).
def ss_raw set
  @result_raw << set
  return if set.size == 1
  set.each_index do |i|
    tmp = set
    tmp.delete_at i
    ss_raw(tmp)
  end
end
The intent of this code was to recursively remove one element from the input and generate the subsets. The tmp variable was intended as a proxy for the set variable so that the each_index block could process each subset by deleting one element at a time. It didn't work and gave some really perplexing results, apparently replacing the contents of @result_raw rather than appending to it.

Unfortunately, I forgot the basics:
tmp = set
doesn't create a new variable, it simply creates a new pointer to the same location in storage that set points to. Therefore, modifying tmp also modifies set and the whole process goes south. The solution? Just say
tmp = set.clone
and you get a new and separate variable and the method works as expected.

In many cases, an oversight like this won't cause a problem, but in this cae it wound up modifying the method parameter as well as modifying the basis of the each_index block... a definite no-no and exactly what I was trying to avoid in the first place.

Hopefully this little reminder will help someone else avoid the pain.

Friday, September 6, 2013

RVM oddity

Even though I don't use all the features, I love RVM... it generally makes my life easier when dealing with new/multiple versions of Ruby and/or different gemsets. Definitely recommended, although I know there are people who swear by rbenv. In either case, they both make magical things happen by working in the background... all terrific as long as it works. But when it doesn't work as expected, it can really be difficult to diagnose and resolve.

Case in point. The project I'm working on is done in Ruby 1.9.3-p327 and I have a 'Rails3213' gemset that I've been using. I've always used a .rvmrc file (analogous to .vimrc or .bashrc) to specify my desired ruby and gemset versions on a per-project basis. Somewhere along the line, I upgraded RVM and then get a prompt that I should switch from .rvmrc to .ruby-version for my ruby/gemset information, which I followed.

Mistake. Unbeknownst to me, the information in my .ruby-version file was silently ignored and I was using gems from another location inside RVM.  It only came to light when I encountered some strange test results and then debugging changes I made to a gem seemed to disappear. I finally got a clue of what was going on through a tell-tale signal from vim's "tag jump" (Ctrl-]) command : I had been working on the gem in:
~/.rvm/gems/ruby-1.9.3-p327@rails3213/clearance-1.0.1
but when Ctrl-] I jumped to a method definition inside vim, I was routed to a file in:
~/.rvm/gems/ruby-1.9.3-p327/clearance-1.0.1
Going back to the RVM site, I saw that .rvmrc was still (probably always had been?) the preferred way to define things, so good-bye .ruby-version, hello .rvmc, and things were back to normal.

Lesson Learned. For the future, if I run into such strangeness again, I'll look to my environment much earlier to make sure I'm working with the right set of gems or rubies or whatever.  One thing I was reminded of that I had forgetten was that rvm nicely tells you the ruby & gemset that you're using when you enter a project directory with a .rvmrc file:
Using /home/jseidel/.rvm/gems/ruby-1.9.3-p327 with gemset rails3213
And if I need to check up on things, a quick :
rvm list && rvm gemset list
tells me all I need to know that I'm in the right place:
rvm rubies
   ruby-1.9.2-p290 [ x86_64 ]
=* ruby-1.9.3-p327 [ x86_64 ]
   ruby-1.9.3-p448 [ x86_64 ]
   ruby-2.0.0-p0 [ x86_64 ]
   ruby-2.0.0-p247 [ x86_64 ]

# => - current
# =* - current && default
#  * - default
gemsets for ruby-1.9.3-p327 (found in /home/jseidel/.rvm/gems/ruby-1.9.3-p327)
   (default)
   global
   rails3020
   rails3212
=> rails3213
Another thing I'm going to do more carefully is re-create my tags file whenever there are gem changes so that I'm always up-to-date. I found a great bash one-liner (if I can remember where I found this, I'll come back and give credit):
ctags -R . $(bundle show --path)
This puts all your gems into the tags file which means that you can Ctrl-] on methods that are included in Rails or other gems.

Update:
Now when I enter the project directory, I get the RVM warning:
You are using '.rvmrc', it requires trusting, it is slower and it is not compatible with other ruby managers,
you can switch to '.ruby-version' using 'rvm rvmrc to [.]ruby-version'
or ignore this warnings with 'rvm rvmrc warning ignore /home/jseidel/Dev/MyPasswords_Upgrade/.rvmrc',
'.rvmrc' will continue to be the default project file in RVM 1 and RVM 2,
to ignore the warning for all files run 'rvm rvmrc warning ignore all.rvmrcs'.
It may be slower (I sure can't tell the difference), but I'm going to stick with .rvmrc as long as I can.