link_to different behavior from rails2 to rails3 - ruby-on-rails-3

In rails2, I was able to have code like this:
link_to(user.company.name, user.company)
which would map to:
/companies/id
but in rails 3, this same line of code throws a error stating:
undefined method `user_companies_path'
The obvious fix is to do something like:
link_to(user.company.name, company_path(user.company))
But I was wondering if anyone could explain the reason behind the change? The logic seemed a lot cleaner.
EDIT: Adding samples of my routes
In rails2, my routes looked like:
map.resources :users, :except => :edit, :member => { :details => :get }
map.resources :companies, :except => :edit, :member => { :details => :get }
In rails3, my routes are:
resources :users, :except => :edit do
member do
get :details
end
end
resources :companies, :except => :edit do
member do
get :details
end
end

The short answer is that the Rails 3 routing API bases your application on resources which is why these RESTful routes are being used, and also means that it does things like support constraints.
In Rails 2, you'd do:
resources :cars do
resource :models
member do
post :year
end
collection do
get :details
end
end
In Rails 3, you'd do:
map.resources :cars, :member => {:year => :post}, :collection => {:details => :get} do |cars|
cars.resource :model
end
You also have the :as key available which means you can then use named route helpers anywhere that url_for is available (i.e. controllers, mailers etc.)

Related

Links to resources with friendly URLs

I'm trying to write a blog in Rails 3, so I have posts. I want to make nice routes for the posts like this: posts/year/month/day/post-title. So I overrided to_param in model/post.rb and use friendly_id for title:
extend FriendlyId
friendly_id :title, :use => :slugged
def to_param
"#{year}/#{month}/#{day}/#{title.parameterize}"
end
And I added this to routes.rb:
resources :posts do
collection do
match ":year/:month/:day/:id", :action => "show"
end
member do
post 'comment'
delete 'comment/:comment_id', :action => 'destroy_comment', :as => 'destroy_comment'
end
end
In show this works:
<%= link_to image_tag("/images/edit_icon.png"),
{ :controller => 'posts', :action => 'edit' }, :id => #post %>
But in index it says
routing error:
No route matches {:action=>"edit", :controller=>"posts"}.
I'm new to Rails and I haven't managed to find out what causes this error. Can anyone help me figure out what I do wrong?

Rails 3.1 How do I create an API route for "me" that points to the user resource

I have a set of API routes in rails as follows
namespace "api" do
namespace "v1" do
resources :users do
resources :posts
resources :likes
...
end
end
end
So far, so good. I can GET /api/v1/users/fred_flintstone and retrieve all of the information for that user.
What I would like to do now is add the concept of "me" (ala facebook) such that if the user is authenticated (fred_flintstone), I can also do the following
GET /api/v1/me
GET /api/v1/me/posts
...
I require both sets of routes. So I want to achieve the same results either using GET /api/v1/me/posts OR GET /api/v1/users/fred_flintstone/posts.
I've been through the route tutorial and have googled so a pointer would be as much appreciated as a direct answer.
EDIT:
What I've done that has worked is pretty hacky. I've created a second set of entries in the routes table using a scope:
scope "/api/v1/me", :defaults => {:format => 'json'}, :as => 'me' do
resources :posts, :controller => 'api/v1/users/posts'
resources :likes, :controller => 'api/v1/users/likes'
...
end
And then I added a set_user method that tests for the presence of params[:user_id]. I'm really looking for a way to DRY this up.
What about leaving the routes the way they are in your post, and just solving this inside the controller?
Heres a before_filter that you could apply to all of the routes you have which pull a User from a :user_id.
# Set the #user variable from the current url;
# Either by looking up params[:user_id] or
# by assigning current_user if params[:user_id] = 'me'
def user_from_user_id
if params[:user_id] == 'me' && current_user
#user = current_user
else
#user = User.find_by_user_id params[:user_id]
end
raise ActiveRecord::RecordNotFound unless #user
end
Then in your controller functions you can just use the #user variable without having to worry about whether the user passed a user_id, or me.
Hope that helps! :)
EDIT:
Lemme take another shot, given your comments.
How about a function that lists all the resources you wish to access via both the standard routes and the /me route. Then you can just use the function in both the namespaces you require.
routes.rb
# Resources for users, and for "/me/resource"
def user_resources
resources :posts
resources :likes
...
end
namespace 'api' do
namespace 'v1' do
resources :users do
user_resources
end
end
end
scope '/api/v1/:user_id', :constraints => { :user_id => 'me' },
:defaults => {:format => 'json'}, :as => 'me' do
user_resources
end
# We're still missing the plain "/me" route, for getting
# and updating, so hand code those in
match '/api/v1/:id' => 'users#show', :via => :get,
:constraints => { :id => 'me' }
match '/api/v1/:id' => 'users#update', :via => :put,
:constraints => { :id => 'me' }

Rename a custom member route named helper in Rails 3

I have routes listed as follows
resources :jobs do
resources :invoices, :only => [:show] do
get 'submit_invoice', :on => :member
end
end
So the middle route creates a url like /jobs/:job_id/invoices/:id/submit_invoice which is exactly what I want. However rails assigns the name submit_invoice_job_invoice to the path which is ugly and horrible to type.
How can I make the name just submit_invoice so that I can have submit_invoice_path and submit_invoice_url?
The answer should be:
get "/jobs/:job_id/invoices/:id/submit_invoice" => "invoices#submit_invoice",
:as => "submit_invoice"
resources :jobs do
resources :invoices, :only => [:show] do
get 'submit_invoice', :on => :member, :as => 'submit_invoice'
end
end
Use :as => 'routename' and invoke it as routename_path.
:)

How to tweak nested route

I have routes like this:
namespace :admin do
resources :users, :only => :index do
resources :skills, :only => :index
end
end
resources :skills
In this case I got:
admin_user_skills GET /admin/users/:user_id/skills(.:format)
{:action=>"index", :controller=>"admin/skills"}
How to change nested route in order to point to SkillsController instead of Admin::SkillsController? I'd like to have this:
admin_user_skills GET /admin/users/:user_id/skills(.:format)
{:action=>"index", :controller=>"skills"}
Interesting thing - if we have no Admin::SkillsController, it will use SkillsController automatically, but only in development.
Using namespace in routes implies to have special directory for "namespaced" controllers, admin in your case. But if you use scope instead you have what you need:
scope '/admin' do
resources :users, :only => :index do
resources :skills, :only => :index
end
end

Help migrating routes to rails 3 format

I am trying to figure out changing this routes.rb to the new rails 3 syntax but it's proving to be quite difficult... I know most of the sutff is simply removing the map.
but some of these routes I cant figure out what they were supposed to do in rails 2 to begin with... so if someone could help me get this to work without deprecation warnings I'd appreciate it'. Please Help.
MyProject::Application.routes.draw do |map|
map.resources :grading_levels
map.resources :class_timings
map.resources :subjects
map.resources :attendances
map.resources :employee_attendances
map.resources :attendance_reports
map.feed 'courses/manage_course', :controller => 'courses' ,:action=>'manage_course'
map.feed 'courses/manage_batches', :controller => 'courses' ,:action=>'manage_batches'
map.resources :courses, :has_many => :batches
map.resources :batches do |batch|
batch.resources :exam_groups
batch.resources :additional_exam_groups
batch.resources :elective_groups, :as => :electives
end
map.resources :exam_groups do |exam_group|
exam_group.resources :exams, :member => { :save_scores => :post }
end
map.resources :additional_exam_groups do |additional_exam_group|
additional_exam_group.resources :additional_exams , :member => { :save_additional_scores => :post }
end
map.root :controller => 'users', :action => 'login'
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id/:id2'
map.connect ':controller/:action/:id.:format'
end
resources :grading_levels
resources :class_timings
resources :subjects
resources :attendances
resources :employee_attendances
resources :attendance_reports
match 'courses/manage_course' => 'courses#manage_course', :as => :feed
match 'courses/manage_batches' => 'courses#manage_batches', :as => :feed
resources :courses
resources :batches do
resources :exam_groups
resources :additional_exam_groups
resources :elective_groups
end
resources :exam_groups do
resources :exams do
member do
post :save_scores
end
end
end
resources :additional_exam_groups do
resources :additional_exams do
member do
post :save_additional_scores
end
end
end
match '/' => 'users#login'
match '/:controller(/:action(/:id))'
match ':controller/:action/:id/:id2' => '#index'