Rails Resources are giving me headache - ruby-on-rails

I am a .NET developer moving to Ruby on Rails. I have been programming in ASP.NET MVC and now trying to apply the same concepts to Rails. I created index action and now when I say home/index it automatically redirects me the "show" action which does not exists. My routes.rb file has this particular line:
resources :home
home is the home_controller.
What am I doing wrong?
class HomeController < ApplicationController
def show
end
# show all the articles
def index
#articles = Array.new[Article.new,Article.new,Article.new]
respond_to do |format|
format.html
format.xml { render :xml => #articles }
end
end
def new
#article = Article.new
respond_to do |format|
format.html
format.xml { render :xml => #article }
end
end
def create
#article = Article.new(params[:post]);
if #article.save
format.html { redirect_to(#post,
:notice => 'Post was successfully created.') }
end
end
def confirm
end
end

You can run "rake routes" to check out what rails thinks about your routes and which urls will be dispatched to which controllers.
In your case, I get:
home_index GET /home(.:format) {:action=>"index", :controller=>"home"}
home_index POST /home(.:format) {:action=>"create", :controller=>"home"}
new_home GET /home/new(.:format) {:action=>"new", :controller=>"home"}
edit_home GET /home/:id/edit(.:format) {:action=>"edit", :controller=>"home"}
home GET /home/:id(.:format) {:action=>"show", :controller=>"home"}
home PUT /home/:id(.:format) {:action=>"update", :controller=>"home"}
home DELETE /home/:id(.:format) {:action=>"destroy", :controller=>"home"}
So to get to the index action, you need to go to "/home". If you go to "/home/index", it will think "index" is the ID of the resource, thus dispatching to the show action.
However, in Rails, it's custom to use plural names for controllers, and naming them after the resource they represent (this is usually a model, but it doesn't have to be). So in your case the name of the controller should be "ArticlesController" and your routes.rb should contain "resources :articles". Rails is very anal about plural and singular names.
The big advantage of using the plural name of the resource you're accessing, is that you can now use short notations, like "redirect_to #article", "form_for #article do |f|", etc.
So, resources in Rails are supposed to be telling about what you want your actually getting. This helps maintenance too, since other developers have to guess less. If you find yourself needing more than one ArticlesController, consider using namespaces, or try to figure out if one of those controllers are actually another resource (even though they store their data in the same database table).
More information about the routers can be found in the Rails Guide: http://guides.rubyonrails.org/routing.html

Related

Route to method always uses first listed despite different name?

I'm new to rails and trying to create up / down vote buttons (implemented as text links for now for clarity). However no matter which link is clicked it calls the action of the first link despite the different names.
I've read over and over the docs and answers on here and wrestled with it all day but still can't understand why rails can't see the difference, any help greatly appreciated.
Routes
Futurebot::Application.routes.draw do
resources :posts do
resources :comments
end
resources :posts do
member do
post 'delete'
post 'upVote'
post 'downVote'
end
end
match ':posts/:id/:upVote', :controller => 'posts', :action => 'upVote'
match ':posts/:id/:downVote', :controller => 'posts', :action => 'downVote'
If I remove the resources :posts block it can't find the route but it seems like the match statements should work (that's what the url looks like)
view
<%= link_to "up: ", :action => 'upVote', :id => post.id %>
<%= link_to "down: ", :action => 'downVote', :id => post.id %>
controller
def upVote
#post = Post.find(params[:id])
if #post.increment!(:score)
respond_to do |format|
format.html { redirect_to posts_url }
format.json { head :no_content }
end
end
end
def downVote
#post = Post.find(params[:id])
if #post.decrement!(:score)
respond_to do |format|
format.html { redirect_to posts_url }
format.json { head :no_content }
end
end
end
rake routes: with the 2 new routes commented out (so just the block is there)
up_vote_post POST /posts/:id/up_vote(.:format) posts#up_vo
te
down_vote_post POST /posts/:id/down_vote(.:format) posts#down_
vote
GET /posts(.:format) posts#index
POST /posts(.:format) posts#creat
e
GET /posts/new(.:format) posts#new
GET /posts/:id/edit(.:format) posts#edit
GET /posts/:id(.:format) posts#show
PUT /posts/:id(.:format) posts#updat
e
DELETE /posts/:id(.:format) posts#destr
oy
root /
posts#index
Rake routes with two routes in routes.rb
delete_post POST /posts/:id/delete(.:format) posts#delet
e
up_vote_post POST /posts/:id/up_vote(.:format) posts#up_vo
te
down_vote_post POST /posts/:id/down_vote(.:format) posts#down_
vote
GET /posts(.:format) posts#index
POST /posts(.:format) posts#creat
e
GET /posts/new(.:format) posts#new
GET /posts/:id/edit(.:format) posts#edit
GET /posts/:id(.:format) posts#show
PUT /posts/:id(.:format) posts#updat
e
DELETE /posts/:id(.:format) posts#destr
oy
/:post/up_vote/:id(.:format) post#up_vot
e
/:post/down_vote/:id(.:format) post#down_v
ote
root /
posts#index
The added routes
match ':post/up_vote/:id' => "post#up_vote"
match ':post/down_vote/:id' => "post#down_vote"
UPDATE
strangely if I change the route around to:
match ':post/:id/up_vote/' => "post#up_vote"
match ':post/:id/down_vote/' => "post#down_vote"
..as that looks like the link then the error is
uninitialized constant PostController
I've tried using both post and posts based on the solution to that in another question
It seems to me that
resources :posts do
member do
post 'delete'
post 'up_vote'
post 'down_vote'
end
end
should give you a routes output similar to this
POST /posts/:id/delete(.:format)
/posts/:id/up_vote(.:format)
/posts/:id/down_vote(.:format)
And they should map out to the PostsController and its respective actions delete, up_vote, down_vote.
Is there any reason why you have those two match routes at the end? They pretty much look like wildcards to me and I don't think you need them.
What is really happening is that anything that matches the following
something/another/path
will get mapped to those routes, specifically to the first one and it will be split in the params like
params[:posts] => something
params[:id] => another
params[:upVote]=> path
That happens because you are using the colon character which allows you to specify dynamic routes. For example something like this
match 'hello/world/:name' => "hello#say"
would get mapped out to a HelloController and action say. Inside the action you would then have params[:name] that would be equal to whatever is under name.
So hello/world/leo would have params[:name] equal to leo
For more information take a look at this: Rails Routes: Dynamic Segments
NOTE Also, try not to use camel case for method names in Ruby :D
UPDATE You need a :method => "post" on your link_to in order for it to send a post request

Rails: URL after validation fails when creating new records via form

Lets say I am creating a new Foo using a form and a standard Rails restful controller, which looks something like this:
class FoosController < ApplicationController
...
def index
#foos = Foo.all
end
def new
#foo = Foo.new
end
def create
#foo = Foo.create(params[:foo])
if #foo.save
redirect_to foos_path, :notice => 'Created a foo.'
else
render 'new'
end
end
...
end
So, if I use the standard restful controller (as above), then when I'm creating the Foo I am at example.com/foos/new, and if I submit the form and it saves correctly I'm at example.com/foos showing the index action. However, if the form is not filled correctly the form is rendered again and error messages are shown. This is all plain vanilla.
However, if errors are shown, the form page will be rendered but the URL will be example.com/foos, because the CREATE action posts to that url. However, one would expect to find Foos#index at example.com/foos, not the form they just submitted now with error messages added.
This seems to be Rails standard behavior, but it doesn't make a lot of sense to me. Obviously I could redirect back to new instead of rendering new from the create action, but the problem with that is the error messages etc. would be lost along with the partially complete Foos in memory.
Is there a clean solution for this problem, a way to send people back to example.com/foos/new when there are errors in the new Foo form they submitted?
Thanks!
To answer your comment on another answer:
I'm wondering if there's a way, without rewriting the controller at all, to tell rails that you want the URL to match the rendered template, rather than the controller action that called it.
I don't think so; URLs are tied directly to routing, which is tied into a controller and action pair--the rendering layer doesn't touch it at all.
To answer your original question, here's information from another similar question I answered.
As you've found, by default when you specify resources :things, the POST path for creating a new thing is at /things. Here's the output for rake routes:
things GET /things(.:format) {:action=>"index", :controller=>"things"}
POST /things(.:format) {:action=>"create", :controller=>"things"}
new_thing GET /things/new(.:format) {:action=>"new", :controller=>"things"}
edit_thing GET /things/:id/edit(.:format) {:action=>"edit", :controller=>"things"}
thing GET /things/:id(.:format) {:action=>"show", :controller=>"things"}
PUT /things/:id(.:format) {:action=>"update", :controller=>"things"}
DELETE /things/:id(.:format) {:action=>"destroy", :controller=>"things"}
It sounds like you want something more like this:
create_things POST /things/new(.:format) {:action=>"create", :controller=>"things"}
things GET /things(.:format) {:action=>"index", :controller=>"things"}
new_thing GET /things/new(.:format) {:action=>"new", :controller=>"things"}
edit_thing GET /things/:id/edit(.:format) {:action=>"edit", :controller=>"things"}
thing GET /things/:id(.:format) {:action=>"show", :controller=>"things"}
PUT /things/:id(.:format) {:action=>"update", :controller=>"things"}
DELETE /things/:id(.:format) {:action=>"destroy", :controller=>"things"}
Although not recommended, you can get this result with the following route:
resources :things, :except => [ :create ] do
post "create" => "things#create", :as => :create, :path => 'new', :on => :collection
end
You would also need to modify your forms to make them POST to the correct path.
You could hook into rails routing by adding this in an initializer:
https://gist.github.com/903411
Then just put the regular resources in your routes.rb:
resources :users
It should create the routes and behaviour you are looking for.
You can set up the routing manually, if you're that concerned about what URL is going to show. For what you want, you can have a GET to /foos/new render your form, and a POST to the same URL do the creation:
map.with_options :controller => :foos do |foo|
foo.new_foo '/foos/new', :conditions => {:method => :get}, :action => :new
foo.create_foo '/foos/new', :conditions => {:method => :post}, :action => :create
foo.foos '/foos', :conditions => {:method => :get}, :action => :index
end
This should work without requiring any changes to your controller (yay!) - all three actions from your example are taken care of. The few disclaimers:
This is based on my routing for a 2.3.8 app - some syntax (semantics?) changes are probably required to get it into Rails 3 routing style.
My attempts to mix this style of routing with map.resources have failed horribly - unless you're more familiar with this than me, or Rails 3 routing is better (both easily possible), you'll have to do this for every route to the controller.
And finally, don't forget to add /:id, (.:format), etc. to the routes that need them (none in this example, but see #2).
Hope this helps!
Edit: One last thing - you'll need to hard-code the URL in your form_for helper on /foos/new.html.erb. Just add :url => create_foo_path, so Rails doesn't try to post to /foos, which it will by default (there might be a way to change the creation URL in the model, but I don't know of it, if there is one).
You could use Rack::Flash to store the parameters you wanted in the user's session and then redirect to your form url.
def create
#foo = Foo.new(params[:foo])
if #foo.save
redirect_to foos_path, :notice => 'Created a foo.'
else
flash[:foo] = params[:foo]
flash[:errors] = #foo.errors
redirect_to new_foo_path #sorry - can't remember the Rails convention for this route
end
end
def new
# in your view, output the contents of flash[:foo]
#foo = Foo.new(flash[:foo])
end

Rails 3 - Missing Index Paths?

I'm having a pretty weird problem with one of my rails apps. I think I'm probably doing something really silly that I just haven't been able to identify. My problem is that, I seem to be missing about half of my index paths.
For example, if my controller is "foos" for a model foo, I'll have the:
foos POST /foos(.:format) {:action=>"create", :controller=>"foos"}
But no GET option which would usually be as:
foos GET /foos(.:format) {:action=>"index", :controller=>"foos"}
Below I'll show you my actually code, to help me recover my missing index routes.
routes.rb:
resource :announcements, :controller => "announcements" do
resources :comments
member do
post 'vote'
end
end
routes for the announcements part:
announcements POST /announcements(.:format) {:action=>"create", :controller=>"announcements"}
new_announcements GET /announcements/new(.:format) {:action=>"new", :controller=>"announcements"}
edit_announcements GET /announcements/edit(.:format) {:action=>"edit", :controller=>"announcements"}
GET /announcements(.:format) {:action=>"show", :controller=>"announcements"}
PUT /announcements(.:format) {:action=>"update", :controller=>"announcements"}
DELETE /announcements(.:format) {:action=>"destroy", :controller=>"announcements"}
As you can see there is no get / index. In my controller, I have the simply index method defined...
def index
#announcements = Announcement.all
respond_to do |format|
format.html
format.xml { render :xml => #announcements }
end
end
I really don't understand why I don't have this index path. It's happening on several other controllers as well. Any help would be appreciated.
Edit: In the console, app.announcements_path returns a method missing error, in addition to the others that have missing index paths.
This is because you're using the singularized version of resources (resource). There is no index action route generated for these. You should change this to be the pluralized version, and remove :controller from the line too.

Rails Nested Singular Resource Routing

I have a simple User model with a singular nested Profile resource so in my routes.rb I have:
resources :users do
resource :profile, :only => [:edit, :update, :show]
end
This generates the expected routes:
edit_user_profile GET /users/:user_id/profile/edit(.:format) {:action=>"edit", :controller=>"profiles"}
user_profile GET /users/:user_id/profile(.:format) {:action=>"show", :controller=>"profiles"}
user_profile PUT /users/:user_id/profile(.:format) {:action=>"update", :controller=>"profiles"}
I've created a simple controller update method that updates the model and then redirects upon successful update:
def update
#profile = Profile.find_by_user_id(params[:user_id])
#user = User.find_by_id(params[:user_id])
respond_to do |format|
if #profile.update_attributes(params[:profile])
format.html { redirect_to( user_profile_path(#user, #profile), :notice => 'Profile was successfully updated.') }
else
# ...
end
end
end
The problem is that once the form is submitted, the form redirects to mydomain.com/users/4/profile.22 where 22 happens to be the id of the profile. Clearly this confuses the controllers since the routing interprets the '22' as the format.
My question is, how do I get this to redirect to mydomain.com/users/4/profile instead? I've tried the following variations on the redirect_to statement to no effect, they all result in the same incorrect url:
redirect_to( user_profile_path(#user), ... )
redirect_to( user_profile_path(#user, #profile), ... )
redirect_to([#user, #profile], ... )
redirect_to( #profile, ... )
What's more, using 'user_profile_path(#user)' elsewhere produces the correct url.
Any ideas? Oh, and I'm using Rails 3.0.0 and Ruby 1.9.2 if that helps.
After looking around, it appears that the form generating the update had an incorrect url. If anyone is seeing this issue, it's because I had my form set up as:
form_for [#user, #profile] do |f| ...
This caused the form action to have the incorrect url (of the offending form above). Instead, I used
form_for #profile, :url => user_profile_path(#user) do |f| ...
and everything seemed to work.
You should redirect to user_profile_path(#user) since as your routes says it is:
/users/:user_id/profile(.:format)
If you look at it closely, then you will see, that there is only :user_id parameter needed, thou it is only #user in a path.
/users/:user_id/profile/:id(.:format)
It would be correct if you had resource*s* :profiles in your routes.rb, then as well you could use your path as in your example.
user_profile_path(#user) should be correct. You're sure that one is returning mydomain.com/users/4/profile.22?

ActiveRecord::RecordNotFound in MembersController#show

I am sorry for my bad english first.
I just installed ruby and rails few hours ago (you wouldn't believe it took me 3 days to install ruby,rvm,rails and etc, on this ubuntu 10.04 machine) and I am trying to implement basic Member scaffold. My version of rails is 3.0.0 and my ruby is 1.9.2.
When I #rails generate scaffold Member email:string password:string it created various files. I also did #rake db:migrate to implement database in mysql.
So within member controller, I saw that I have to go through 127.0.0.1:3000/members/ to get to the basic scaffold setup.
I just changed
def new
#member = Member.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #member }
end
end
above statements in member controller into
def register
#member = Member.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #member }
end
end
U see, I just changed the new into register, and now, when I try to get into
127.0.0.1:3000/members/register
The ActiveRecord::RecordNotFound error shows up.
How can I resolve this problem?
I just want to make
127.0.0.1:3000/members/register
to be a page where user can register..
btw, this RoR seems to be very complicated, and api documents seems to be too broad to be understood for beginners. I ordered a RoR book last week, so I will see how it goes...
By using the scaffold generator members gets mapped as a resource. Look in the config/routes.rb
resources :members
When entities are mapped as resources they get a set of default routes. You can see all your mapped routes by doing rake routes
members GET /members(.:format) {:action=>"index", :controller=>" members"}
members POST /members(.:format) {:action=>"create", :controller=> "members"}
new_member GET /members/new(.:format) {:action=>"new", :controller=>"members"}
edit_member GET /members/:id/edit(.:format) {:action=>"edit", :controller=>"members"}
member GET /members/:id(.:format) {:action=>"show", :controller=>"members"}
member PUT /members/:id(.:format) {:action=>"update", :controller=>"members"}
member DELETE /members/:id(.:format) {:action=>"destroy", :controller=>"members"}
When you rename the new action to register there no longer is a valid route for that mapping.
What you could do is to leave the action as new and just add the following route in your routes.rb
match 'members/register' => 'members#new'
This way you do not break other things in the scaffold. If you really want to rename the action to register I would suggest not using scaffolds.
You need to add 'register' method to routes, like:-
map.connect '/members/register', :controller => 'members', :action => 'register'.
After adding the above to routes.rb restart the server.
Thanks, Anubhaw
I had the very same problem when creating a new html.erb.
Even my routes.rb match 'controller/action' => 'controller#action' were correct.
Later I found that the problem was that the resources :controller were above the match.
This is the correct order that worked for me:
match 'controller/action' => 'controller#action' resources :controller
Thanks to pavlo for asking this question, and to maz because his answer gave me the hint that the resources were involved in the error.

Resources