GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS

Written by thoughtbot

Using @url Instance Variable in Rails

I was going into a project to do some general code refactoring (it’s a hobby, you know), and found some tests that were failing. Figured I’d fix those first and then bitch at whoever left failing tests in a project later. One test failure in particular looked a bit odd to me:

1) Error:
test: The 'Add to Web' form should pass a URL into the form. (LettucesControllerTest):
ActionView::TemplateError: undefined method `rewrite' for "http://mocked.com":String
    On line #1 of layouts/_footer.html.erb

        1: <%= link_to 'Privacy Policy', privacy_policy_path %>
        2: <%= link_to 'Terms & Conditions', terms_path %>
        3: <%= link_to 'Contact Us', site_contact_path %>

        (eval):15
        app/views/layouts/_footer.html.erb:1

…that’s interesting, I thought. What the hell does http://mocked.com have to do with anything in that line? Well the functional test looked like this:

setup do
  login_as :activated_user
  get :new, :url => 'http://mocked.com'
end

…and the action in the controller looked like this:

# User added lettuces page, scrapes lettuce URL if given for suggested title and images
def new
  @url = params[:url]
  @lettuce = @url.blank? ? Lettuce.new : Lettuce.from_url(@url)
end

Ok, it looks like an external site can pass a url param in via the url, to indicate which page this action should reach out to and scrape for Lettuce content. I removed the @url assignment line from the action and put a <%=h @url.class %> and and <%=h @url.methods.sort %> into the new view file and loaded that in my browser.

Sure enough, @url is an ActionController::UrlRewriter, and it has a #rewrite method, amongst some other things. I’m guessing that Rails routing is using this somewhere, but I’m not curious to go find out because it will delay my refactoring mission.

Now, thankfully I didn’t like that there was an instance variable named @url in the first place, since it was being created only for the purpose of letting the view use an instance variable instead of accessing #params directly. However, there was a conditional case in the view for how to handle a #blank? url vs a non blank url, so it had to be available to the view somehow. I refactored to:

def new
  @lettuce = requested_lettuce_url.blank? ? Lettuce.new : Lettuce.from_url(requested_lettuce_url)
end

protected

helper_method :requested_lettuce_url
def requested_lettuce_url
  params[:url] || ''
end

And now:

  • The functional tests all pass.
  • @url does not get overwritten, so the routing code will now work.
  • The view has access to a #requested_lettuce_url method (a bit verbose, but #lettuce_url collides with a named route).
  • The incoming param stays the same, so any external pages which linked to this import lettuce view won’t have to change how they link to the page.

Now that the lettuces_controller is looking good, I can start working on the meats_controller and see what it has in store.