Creating new rails action doesn't work? - ruby-on-rails

i have a controller "Apps". It consists of one action "index". Now I want to add a new action called "buy":
def buy
respond_to do |format|
format.html
end
end
i added a buy.html.erb to the views, but when browsing to /apps/buy, i get following message:
Unknown action - The action 'show' could not be found for AppsController
in the routes I added this:
match '/apps/buy', :controller => 'apps', :action => 'buy'
thanks in advance!

The url is being caught by the standard /apps/:id route, I assume you also have resources :apps in your routes?
Simply place the buy route first:
match '/apps/buy', :controller => 'apps', :action => 'buy'
resources :apps
Bear in mind that routes are executed in the order they are defined, so the specific ones need to precede the general.
A simpler approach as #Ryan suggests is adding a collection route to the resource:
resources :apps, :collection => { :buy => :get }

Related

How to declare a rails resource with a parameter for new action?

I have a model named Entree for which the new action needs a parameter, the id of another model named Cave. I don't want to nest Entree in Cave since Cave is already nested.
What I did was declaring the resource Entree as follow in routes.rb:
resources :entrees, :except => [:new]
match "/entrees/new/:id", :to => "Entrees#new", :as => 'new_entree'
That works, but the problem is when there's an error in the create action, I want to display the page again with the invalid input. But since there's no new action, I must do a redirect_to new_entree_path, which does not keep the user input.
I have tried the following (simplest) route:
resources :entrees
But then the path http://localhost:3000/entrees/new/32 returns an error:
No route matches [GET] "/entrees/new/32"
The question is, how can I declare the Entree resource in the routes file with a parameter for the new action ?
I'm not sure if that's a hack or not, but the following works and seems cleaner than 2-levels nesting.
resources :entrees, :except => [:new] do
collection do
get 'new/:id', :to => "entrees#new", :as => 'new'
end
end
Now I can do a render "new" instead of a redirect_to.
I must say that I must have asked my question wrongly, my bad.
Rails has a route helper called path_names that does this:
resources :entrees, path_names: { new: 'new/:id' }
To improve gwik 's solution (which in fact didn't work for me):
resources :things, except: [:new] do
new do
get ':param', to: 'things#new', as: ''
end
end
It gets you new_thing_* helpers (instead of new_things_*) for free.
If you want to use Rails resource routes, you will have to nested them according to how they work
resources :caves do
resources :entrees
end
to get the route /caves/70/entrees/new
Otherwise, you are in a world of creating manual match routes.
match "/entrees/new/:id", :to => "entrees#new", :as => 'new_entrees'
I do not understand why you are forced to use a redirect? The new_entrees route is valid. You will not be able to use the form_for helper, since it is not a resource, but the form_tag helper will work.
UPDATE: Render and Route
The Route does not directly change what view is rendered in the Controller. That is determined by the controller itself. Render examples:
render :new will render the new action's view
render 'entrees/new' will render the entrees/new template
I found this generates the correct new_thing_path method not new_things_path as Antoine's solution.
resources :things, :except => [:new] do
with_scope_level(:new) do
get 'new/:param', :to => "things#new", :as => ''
end
end

Get SEO friendly URLS with Rails without method_missing?

Currently we are using method_missing to catch for calls to SEO friendly actions in our controllers rather than creating actions for every conceivable value for a variable. What we want are URLS like this:
/students/BobSmith
and NOT /students/show/342
IS there a cleaner solution than method_missing?
Thank you!
You can define a route for that particular format fairly easily.
map.connect "/students/:name", :controller => :students, :action => :show, :requirements => {:name => /[A-Z][A-Z]+/}
Then in your show action you can find by name using params[:name].
You can create a catch-all route. Put this at the bottom of config/routes.rb with whatever controller and action you want:
map.connect '*path', :controller => '...', :action => '...'
The segments of the route will be available to your controller in the params[:path] array.

Nested resources without controller names in url

I've got a very simple Rails 3 app with a User model run by Devise and a Notes model. Right now the url and routing looks like this:
# url
/users/MEMBERNAME/notes/TITLE-OF-NOTE
# routing
resources :users do
resources :notes
end
But I would like the urls to look like this, how would the routing look like in this case?
# url
/MEMBERNAME/TITLE-OF-NOTE
Update:
Thanks, now I discovered a new problem though. In my forms I have this code:
<%= form_for([#user, #note]) do |f| %>
and in my controller I redirect like this:
format.html { redirect_to([#user, #note], :notice => 'Note was successfully created.') }
In both those cases when I use #user, #note the old urls are still present. Do you know how to translate the form and the redirects to use the member/title structure?
Thanks in advance!
You can use a custom route here:
get "/:user_id/:id", :to => "notes#show", :as => :short_user_note
Hope this helps!
Update:
To use the newly created named route:
# => /USER_NAME/NOTE_NAME
redirect_to short_user_note_path(#user, #note)
# => /user/USER_NAME/note/NOTE_NAME
redirect_to user_note_path(#user, #note)
# OR
redirect_to url_for([#user, #note])
# OR
redirect_to [#user, #note]
So, the general rule is if you pass an array of active_record objects like below to #redirect_to, #url_for or #form_for methods, the #polymorphic_url method is called internally, and generates the standard RESTful route.
You can the routes behaviour you're after with working route helpers in Rails 3 with like the following:
resources :users, :path => ''
resources :users, :path => '', :only => [] do
resources :notes, :path => '', :except => [:index]
end
See my blog post for the details on how I arrived at this solution.

Can controller names in RESTful routes be optional?

With a standard map.resource routing mechanics and several nested resources the resultant routes are unnecessarily long. Consider the following route:
site.org/users/pavelshved/blogs/blogging-horror/posts/12345
It's easy to create in routes.rb, and I'm sure it follows some kind of beneficial routing logic. But it's way too long and also seems like it's not intended to be human-readable.
A nice improvement would be to drop controller names, so it looks like:
site.org/pavelshved/blogging-horror/12345
Clear, simple, short. It may become ambiguous, but in my case I'm not going to name any user "users", for instance.
I tried setting :as => '', but it yields routes like this: site.org//pavelshved//blogging-horror//12345 when generating them by standard helpers.
Is there a way to map resources in such a way, that controller names become optional?
You're looking for the :path_prefix option for resources.
map.resources :users do |user|
user.resources :blogs do |blog|
blog.resources :posts, :path_prefix => '/:user_login/:blog_title/:id'
end
end
Will produce restful routes for all blogs of this form: site.org/pavelshved/bogging-horror/posts/1234. You'll need to go to a little extra effort to use the url helpers but nothing a wrapper of your own couldn't quickly fix.
The only way to get rid of the posts part of the url is with named routes, but those require some duplication to make restful. And you'll run into the same problems when trying to use route helpers.
The simplest way to get what you want would be to create a route in addition to your RESTful routes that acts as a shorthand:
map.short_blog ':user_id/:blog_id/:id', :controller => 'posts', :action => 'show'
You'll have to change the URL bits to work with how you're filtering the name of the user and the name of their blog. But then when you want to use the shorter URL you can use all the short_blog_* magic.
Straight out of the default routes.rb:
map.connect 'products/:id', :controller => 'catalog', :action => 'view'
You could write:
map.connect ':user_id/:blog_id/:id', :controller => 'posts', :action => 'show'
But be sure to include that in the very end of the file, or it will try to match every three levels deep url to it.
Try this
map.pavelshved '/pavelshved/', :controller => :users, :action => view or
map.pavelshved '/:id', :controller => :users, :action => show do | blogs|
blogs.bloging '/:id', :controller => :blogs, :action => show do | post|
post.posting '/:id', :controller => :posts, :action => show
end
end
I hope it work :)
Google "rails shallow routes" for information about this.

What's the difference between :new, :collection and :member routes?

I've read the documentation, but I'm still not sure I understand everything.
Especially why there's a :new parameter. As far as I understand, it could be replaced with the :collection parameter.
So what's the difference between those three types of routes?
The difference is the URL generated.
Let's guess three resources :
map.resources :users, :collection => { :rss => :get }
map.resources :users, :member => { :profile => :get }
map.resources :users, :new => { :draft => :get }
The first route will create :
/users/rss
With nothing between the controller name and the action name. We don't need any other parameter to get the user's list rss feed.
The second one will create the action "profile" as a member of the object. So we'll have :
/users/1/profile
The "1" is the user's to_param. We need a user's id to display a profile.
The third one will create the action "draft" as a member of the new action. So we'll have :
/users/new/draft
The "draft" action displays a draft of the user before accepting its creation.
So that's the difference between :collection, :member and :new. Every of them creates different routes, each one with their own purpose.
:member creates path with pattern /:controller/:id/:your_method
:collection creates path with the pattern /:controller/:your_method
:new creates path with the pattern /:controller/:your_method/new (please note that the last element of the path i.e. new is constant)
New differs from Collection mainly on the ideological layer. That's how REST gurus see the creation of the REST "subresource" within the bigger resource.
Damiens explanation is mostly right except for the section about :new
Have a really good read of the ruby on rails routing guide at http://guides.rubyonrails.org/routing.html It explains routing from the inside out, and then back again. Section 3.11.3 (Adding New Routes) describes what :new does, and it is very different to :member and :collection.
Basically map.resources :photos, :new => { :upload => :post } will create /photos/upload using the POST HTTP verb.

Resources