0

I don't even know how to write a proper title for this. I kind of cobbled together some routing code based on a bunch of different articles, and now I'm wondering if I've painted myself into a corner.

I've got a NewsArticle model, and I want the links to look like this:

/news                          # List of all articles
/news/2011                     # List of articles published this year
/news/2011/06                  # List of articles published this month
/news/2011/06/28               # List of articles published on this day
/news/2011/06/28/my-post-title # Actual article

Ok, going against the Rails way already, but so be it.

I've got routes setup like this:

controller :news_articles, :via => [:get] do
  match '/news(/:year/(/:month(/:day)))' => :index, :constraints => { :year => /\d{4}/, :month => /\d{2}/, :day => /\d{2}/ }
  match '/news/:year/:month/:day/:id'    => :show
end

Note there is no :as declaration. That's because when I do add something like :as => "news_archive" then I end up with news_archive_path which returns something stupid like "/news?year=2010&month=4". So, I excluded that bit and wrote my own path methods in my application helper file:

def news_archive_path(year = nil, month = nil, day = nil)
  return "/news" if year.nil?
  t = Time.zone.local(year.to_i, month.nil? ? nil : month.to_i, day.nil? ? nil : day.to_i)
  if month.nil?
    "/news/#{t.year}"
  elsif day.nil?
    "/news/#{t.year}/#{"%02d" % t.month}"
  else
    "/news/#{t.year}/#{"%02d" % t.month}/#{"%02d" % t.day}"
  end
end

def news_article_path(article)
  t = article.published_at.in_time_zone
  "#{news_archive_path(t.year, t.month, t.day)}/#{article.friendly_id}"
end

Great, this all works in practice. But now I've run into a problem where I'm testing my controllers and I want to make sure that the right links appear on the rendered templates. (Oh yeah, I'm not keeping separate view tests but instead using render_views in my controller tests.) But the tests are failing with the error undefined methodnews_article_path' for #`.

So, have I just approached this all wrong and painted myself into a corner? Or can I get out of this by somehow including the helper methods in the controller test? Or do I just suck it up for the sake of getting the test to pass and hardcode the links as I expect them to be?

Chris Bloom
  • 3,526
  • 1
  • 33
  • 47

2 Answers2

0

To make news_article_path available you need to do:

class MyClass
  include Rails.application.routes.url_helpers
end

Now MyClass#news_article_path will be defined. You can try to include the url_helpers into your test case. I've seen that break recently with "stack level too deep" errors (it creates an infinite recursion of "super" calls in initialize). If it doesn't work, create your own view helpers class and do "MyViewHelper.new.news_article_path" etc.

Spike Gronim
  • 6,154
  • 22
  • 21
  • My custom paths are currently defined in applicatoion_helper.rb, they are not generated routes. See the OP. Is there a way for me to actually add them to the `Rails.application.routes.url_helpers` collection instead? – Chris Bloom Jun 28 '11 at 21:08
  • In that case you just need to include ApplicationHelper into your test class instead. That will mix in the news_article_path method. Sorry that I missed your comment about where the _path helper was coming from. – Spike Gronim Jun 28 '11 at 21:14
0

It's probably not a good idea to be generating the paths by concatenating string in your path helpers. Instead, simply use the url_for() method. Eg:

def news_article_path(article, options = {})
   url_for(options.merge(:controller => :articles, 
     :action => :show, 
     :id => article.id, 
     :year => article.year, 
     :month => article.month, 
     :day => article.day))
end

(This assumes day, month, year are added as quick methods on your model, eg:

def year
   self.published_at.in_time_zone.year
end

For your tests, either include the helper methods via something along these lines: My helper methods in controller - or use the url_for method again.

...That said - as you suggested, testing views within controller tests isn't ideal. :)

Community
  • 1
  • 1
Frankie Roberto
  • 1,349
  • 9
  • 9
  • "testing views within controller tests isn't ideal" I blame the Rails Tutorial book, page 93, paragraph 2. (I did take the radical approach and create separate helper tests though.) – Chris Bloom Jun 28 '11 at 21:34
  • I do like this solution better than the string concatenation. – Chris Bloom Jun 28 '11 at 21:47
  • I tried using the url_for method inside news_archive_path too, but it keeps returning paths like `http://localhost/news/2004?day=2&month=4`. Is there anyway to make those optional params appear like I have them in the OP? Here's what I changed news_archive_path to: https://gist.github.com/6e46c1b7c5b4d86e015c – Chris Bloom Jun 28 '11 at 22:07