:any option for rails 3 routes - ruby-on-rails-3

In rails 2 you can use the :any option to define a custom route that responds to any request method e.g.
map.resources :items, :member => {:erase => :any}
rails 3 doesn't seem to support the :any option
resources :items do
get :erase, :on => :member # works
any :erase, :on => :member # doesn't work
end
does anyone know if this option has been removed or just renamed?

From digging around and seeing what the get, post, put, and delete actions actually do in ActionDispatch, I think all you need to do is match. So:
resources :items do
get :erase, :on => :member
match :erase, :on => :member
end
I don't think that syntax for match is actually documented, but the routes it constructs are, atleast for me, what you'd expect from an all method

Good question.
Looking at the Edge Rails routing guide and the Rails 3 source it doesn't look like it's supported. You could raise a ticket in the Rails Lighthouse (I couldn't find an existing one for this).

Match will work, but not inside a resources definition unfortunately. I rather wish they'd bring back a way to define get/post at least together..

Related

Singular route serving parameter :object_id instead of :id

Say I have an object called invoice. In routes.rb I have
resources :invoices do
get "pay"
end
When I run rake routes, the route is generated as
invoice_pay GET /invoices/:invoice_id/pay(.:format) invoices#pay
and the parameter is :invoices_id instead of :id
If I use a match statement:
match "invoices/:id/pay" => "invoices#pay", :via => :get
I get:
GET /invoices/:id/pay(.:format) invoices#pay
It seems to me that the route should be pay_invoice_path(#invoice), however, I have not found suitable documentation on this. Any suggestions?
i think what you are trying to do is
resources :invoices do
get "pay", :on => :member
end
have a look at the guides: http://guides.rubyonrails.org/routing.html

Questions about rails3 routes

I'm upgrading my app to rails 3, and I am a bit confused about some of the routes. The resourceful ones are easy enough, but how can I set a generic rule for all actions in a specific controller. I tried something like this:
get 'custom/:action/' => {:controller => :custom}
But that didn't work. It seems the new format is "controller#action", but how can I specify the action to be variable?
Also, other than using named routes or resources, is it possible to do shorthand notation to name routes in a specific controller?
i.e. rather than:
get '/tasks', :controller => :home, :action => :tasks, :as => 'tasks_home'
get '/accounts', :controller => :home, :action => :accounts, :as => 'accounts_home'
is it possible to do something a little cleaner, like:
controller => :home do
get :tasks
get :accounts
end
And that would automatically created the named routes?
You can use action as a variable like this:
resource :custom do
match ':action'
end
This will generate
/custom/:action(.:format) customs#:action
custom POST /custom(.:format) customs#create
new_custom GET /custom/new(.:format) customs#new
edit_custom GET /custom/edit(.:format) customs#edit
GET /custom(.:format) customs#show
PUT /custom(.:format) customs#update
DELETE /custom(.:format) customs#destroy
So it will handle your action as a variable URL-s and will add some default CRUD actions as well.
Note that the controller name here is in plural. If you would like to use a route for a controller which name is in singular, use resources instead of resource.
The answer to the second question is almost identical to the first one, use resource:
resource :home do
get :tasks
get :accounts
end
generates:
tasks_home GET /home/tasks(.:format) homes#tasks
accounts_home GET /home/accounts(.:format) homes#accounts
home POST /home(.:format) homes#create
new_home GET /home/new(.:format) homes#new
edit_home GET /home/edit(.:format) homes#edit
GET /home(.:format) homes#show
PUT /home(.:format) homes#update
DELETE /home(.:format) homes#destroy
Note that the matched controller names are in plural again, because of the convention.
Looks like this is related to the persisted field being set to false on nested ActiveResource objects: https://github.com/rails/rails/pull/3107

Rails 3 Routing: Using 2 dynamic segments in path for one model

What I am trying to achieve is something similar to Github's way for routes. E.g. I have a project with the name 'question' results in the URL /hjuskewycz/question. So my goal is to have routes where the first segment is the username and the second the project's name.
I tried a couple of different approaches, this is the one I am stuck with right now:
scope ":username" do
resources :projects, :path => "" do
resources :pictures
end
end
Using
project_path :username => project.owner.username, :id => project.to_param
works as expected. However, it's tedious to always specify the username although it's always the owner's username. I would very much prefer
project_path(:id => project.to_param)
I know about default_url_options and url_for and I digged in the code. However, polymorphic_url doesn't use default_url_options.
I tried in routes.rb:
resources :projects, :path => "", :defaults => {:username => Proc.new { "just_testing" }}
since you can use a proc for constrains, but haven't got it working either.
I tried in project.rb
def to_param
"#{owner.username"/#{project.title}"
end
I spent already too much time on this problem and my current approach uses a convenience method to add the :username parameter. Nevertheless, I think using this method all over the place just to add an entry stinks (bad code smell). I wonder if there is a more elegant solution to this problem.
I think you should not make things complicated here, just use something like this:
In Routes.rb
match ':username/:projectname/' => 'projects#show_project' , :as => :show_project
and in project_controller, just define this
def show_project
#user =User.find_by_username(params[:username])
#project =Project.find_by_slug(params[:projectname])
end
Simpler is better, it saves time and easy to understand for others
You want to do something like this in your controller:
before_filter :set_username
def set_username
Rails.application.routes.default_url_options[:username] = #user.name
end

Rails 3 routing

resources :users do
get :save_delete, :on => :member
get :question_form, :on => :member
post :question_send, :on => :member
resources :comments
end
After send and POST to my server I got this error in the log:
#Started POST "/users/1/question_send"
#ActionController::RoutingError (No route matches "/users/1/question_send")
rake routes # works fine, no errors
You most likely haven't restarted your server. The routing code looks fine.
However, I agree with #mathepic - your resource seems rather odd. There may be something we don't know, but unless you have a really really really good reason for these question_form and question_send member routes, you should probably think about doing this some other (and more Rails-y) way.

Does anybody have any tips for managing polymorphic nested resources in Rails 3?

In config/routes.rb:
resources :posts do
resources :comments
end
resources :pictures do
resources :comments
end
I would like to allow for more things to be commented on as well.
I'm currently using mongoid (mongomapper isn't as compatible with Rails 3 yet as I would like), and comments are an embedded resource (mongoid can't yet handle polymorphic relational resources), which means that I do need the parent resource in order to find the comment.
Are there any elegant ways to handle some of the following problems:
In my controller, I need to find the parent before finding the comment:
if params[:post_id]
parent = Post.find(params[:post_id]
else if params[:picture_id]
parent = Picture.find(params[:picture_id]
end
which is going to get messy if I start adding more things to be commentable.
Also url_for([comment.parent, comment]) doesn't work, so I'm going to have to define something in my Comment model, but I think I'm also going to need to define an index route in the Comment model as well as potentially an edit and new route definition.
There might be more issues that I have to deal with as I get further.
I can't imagine I'm the first person to try and solve this problem, are there any solutions out there to make this more manageable?
I had to do something similar in an app of mine. I took what I came up with and changed it around a bit, but I haven't tested it, so use with care. It's not pretty, but it's better than anything else I was able to think of.
In routes.rb:
resources :posts, :pictures
controller :comments do
get '*path/edit' => :edit, :as => :edit_comment
get '*path' => :show, :as => :comment
# etc. The order of these is important. If #show came first, it would direct /edit to #show and simply tack on '/edit' to the path param.
end
In comment.rb:
embedded_in :commentable, :inverse_of => :comments
def to_param
[commentable.class.to_s.downcase.pluralize, commentable.id, 'comments', id].join '/'
end
In a before filter in comments_controller.rb:
parent_type, parent_id, scrap, id = params[:path].split '/'
# Security: Make sure people can't just pass in whatever models they feel like
raise "Uh-oh!" unless %w(posts pictures).include? parent_type
#parent = parent_type.singularize.capitalize.constantize.find(parent_id)
#comment = #parent.comments.find(id)
Ok, ugliness over. Now you can add comments to whatever models you want, and simply do:
edit_comment_path #comment
url_for #comment
redirect_to #comment
And so on.
Edit: I didn't implement any other paths in my own app, because all I needed was edit and update, but I'd imagine they'd look something like:
controller :comments do
get '*path/edit' => :edit, :as => :edit_comment
get '*path' => :show, :as => :comment
put '*path' => :update
delete '*path' => :destroy
end
The other actions will be trickier. You'll probably need to do something like:
get ':parent_type/:parent_id/comments' => :index, :as => :comments
post ':parent_type/:parent_id/comments' => :create
get ':parent_type/:parent_id/comments/new' => :new, :as => :new_comment
You'd then access the parent model in the controller using params[:parent_type] and params[:parent_id]. You'd also need to pass the proper parameters to the url helpers:
comments_path('pictures', 7)
Ryan Bates covered polymorphic associations in Railscasts #154, but the example was for Rails 2 and Active Record. I managed to get his example working using Rails 3 and Mongoid by making a few changes.
In the Post and Picture models, add the following line:
embeds_many :comments, :as => :commentable
According to the Mongoid associations documentation, all embedded_in associations are polymorphic. You don't need the commentable_id and commentable_type columns mentioned in the Railscast when using Mongoid, because the comment is a child of the commentable. In the Comment model, add the following line:
embedded_in :commentable, :inverse_of => :comment
Setup the routes in config/routes.rb like this:
resources posts do
resources comments
end
resources pictures do
resources comments
end
Add the following method to your comments controller as a private method. This is identical to Ryan's method:
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
In each of your comments controller actions where you need to find the comment, call the find_commentable method first to get the parent. Once the parent has been found, you can find the comment by ID, by searching through the commentable's comments. For example, in the edit action the code to find the comment would look like this:
#commentable = find_commentable
#comment = #commentable.comments.find(params[:id])
To reduce the repetition of calling find_commentable at the start of every action, you could put a before filter at the top of the controller like this:
class CommentsController < ApplicationController
before_filter :find_commentable
...
And then change the return call in the find_commentable method to:
return #commentable = $1.classify.constantize.find(value)
I haven't encountered any problems using this method, but if you come across any issues please point them out.
Drawing on Uriptical's answer, I found the relationships to work but named routes still did not.
I'm still pretty new at rails 3 but I found a simple solution using eval.
For instance, in my project, the polymorphic parents (represented in my app as the mongoid objects Product and Category) are defined as #imagable using a modification of find_comentable and the child being edited is referred to as #image.
a url such as product_image_path(#imagable, #image) which does
GET => products/:product_id/images/ can be replaced with:
send("#{#imagable.class.name.downcase}_image_url", #imagable, image )
This works for all named paths. For instance:
link_to 'Edit', send("edit_#{#imagable.class.name.downcase}_image_path", #imagable, image )
link_to 'Destroy', send("#{#imagable.class.name.downcase}_image_url", #imagable, image), :confirm => 'Are you sure?', :method => :delete
The downside to this is it leaves sends all over your views and in controllers wherever you have redirects.
Is there a more elegant solution to do this via routes?
*replaced eval with send