#35 Custom REST Actions
May 23, 2007 | 8 minutes | Controllers, Routing
REST adds many constraints. It restricts your controllers to seven actions. Normally this is okay, but sometimes you need to add your own custom actions. Learn how in this episode.
I've always wondered if I can use custom identifiers for members. For example, I have an articles controller. The show action needs the id of the article, so I have urls like /articles/1. But I want urls like /articles/some-article-title. Is this possible somehow?
For friendy URLs you can use this plugin http://sargon.interinter.net/acts_as_sluggable/
Ralph:
AFAIK you could implement that as a custom action (find_by_post_title) mapped more or less like that:
map.connect '/articles/:title', :controller => 'articles', :action => 'find_by_post_title'
I'm not sure about the RESTful way, so a big guess would be:
map.resources :articles, collection => {:find_by_post_title => :get}
--
BTW great screencasts ^^
Thanks for this great screencast! However I'm still not convinced about REST. It's to limiting without bringing too much benefit I think. Maybe in the future...
@Ralph, @Merehost:
If you want URLs like "/articles/some-article-title" you can add something like this to the Article model (article.rb):
def to_param
"#{id}-#{name.downcase.gsub(/[^a-z]+/, "-")}"
end
and link to it with article_path(@article)
# do not use @article.id as this will bypass to_params
@Ralph,
Zubin is correct. Also, if you want to learn more, you should buy Peepcode's RESTful tutorial. It's $9, 85 minutes long, and comes with all the source code. It's an excellent resource for REST!
@Manuel, I hear you. I wasn't convinced of REST initially either. I don't think it's possible to convince someone, it's just something you need to try for yourself. Then compare the code before/after - that's what convinced me.
Ironically as it may sound, the biggest benefit of REST is the limitations it brings. It's what keeps your code clean and design normalized.
Thats great practical information on REST. I think it should be noted that you dont need to create a new model if you want to make those actions RESTful.
If you find yourself creating CRUD like actions to manage your "completed" column. Create only a new (RESTful) controller. This has often worked well for me.
Something I've noticed with REST is that having a shared find_by_params method saves a bit of repetition. So, you could have:
before_filter :find_task :except => [:index, :new, :create]
def find_task
@task = Task.find(params[:id])
end
@Sintaxi, so right! This is something I often forget.
Another great screencast. I had one question, though. Is the determination of the plurality of the link generation method (i.e. action_task_path vs action_tasks_path) made by whether you've added it to the collection hash vs. the member hash in the routes.rb file?
@Todd, yep, exactly. "collection" makes a plural named route (action_tasks_path), "member" makes a singular named route (action_task_path).
Hi Ryan, these screencasts are great. Thanx a lot for all the effort.
I'm a little confused about collections in named routes. Can you pass a parameter to define the collection?
eg /products/tagged/interesting
from map.resources.products, :collection => {:tagged => :get}
but where would :id (tag) go? Or is this not possible using REST?
@Zubin, a "collection" route doesn't accept an :id parameter. It is only for predefined collections. You can still pass the name/id of a tag and search based off of that, but it will show up at the end of the url:
/products;tagged?tag=interesting
If you don't want this behavior you'll have to create a custom route outside of REST - or display the products from inside the tags show page.
Thanks Ryan, I presume you mean it has to be a :member in order to pass the :id?
Any reason not to fall back on standard routes? So the path would look like this: /products/tagged/interesting (calls "tagged" method in listings_controller).
This was a great railscast. There is one thing, however, that I'd recommend with screencasts of this nature. Expand on the existing theme. For instance give us information on what we need to do if we have more than one action to add of a certain type:
map. resources :items, :collection => {:recent => :get}
. . .how do I add more than one? Do I need another "collection" declaration or can I put each one inside the same set of brackets?
As these screencasts are already amazing, expounding in this manner would be priceless.
Thanks!
@Zubin, right, it has to be a :member to pass "id". The reason being is "id" should represent one task (or whatever the resource is). It should be unique to that task.
You could fall back on the standard route, but this is going away from REST.
@Michael, you would supply it in the same :collection hash, no need to specify it twice.
:collection => { :recent => :get, :completed => :get }
Ok, so how would you go about creating a RESTful controller while not creating the model.. I'm brand new to REST, and the only way I know of generating a resource is scaffold_resource? Any tips?
Great espisode!
I'm pretty sure it's better to add a specialzed controller for this "CompletedTasksController" which has the default REST actions. DHH also mentiod something simlar in his World of Resources presentation.
@Ben and jeroen, right, I should have mentioned this in the episode. I may revisit this in a future episode because I think it's a pretty important point.
Hey Ryan. Great screen casts! I've watched them all, and have recommended them to all my Rails developers.
Anyway, I thought it would be a great series of screen casts to talk through using RESTful designs for those non-obvious resources, like you mentioned in this screen cast. Such as, the "completed" resource. Maybe, a simple one would be to use Users and Sessions as an example of RESTful User login.
Hi Ryan,
I, too, enjoy your screencasts. Two questions, one about this episode specifically:
1) is it possible to get the named routing of
map.resources projects
without getting the REST (ha, no pun intended). I'm very new to REST and will have to give some thought to how to handle some actions that I have (such as forgot_password and things like that)
2) what's the chances of the iTunes podcast getting the iPod compatible m4v files? I subscribed to the podcast and was merrily watching on my notebook, but then disappointed when the videos wouldn't go the iPod. I just downloaded all the m4v version manually and imported them into iTunes. I'd like to get the new episodes automatically if I could, which is why I subscribed to the podcast.
Thanks for contributing to the Rails community!
Phillip
Nevermind on #2. I just revisited iTunes and see the other podcast.
Thanks!
Phillip
@Phillip, the named routes generated by map.resources is where the RESTful design is specified as well, so there's no way to separate it. You'll have to create your own named routes if you don't want to use REST. You may want to look into abstracting this into a custom route method (like map.resources) so it's easy to generate the named routes. I'm not sure on the details though.
Ryan, thank you very much for sharing wisdom clouds :). I can't stop watching it :).
Btw you mentioned that you'd revisit creating a RESTful controller instead of creating the model?
Did you do that?:) I can't find it... the next episode is about SVN.
Peace.
@macovsky, I haven't done it yet. I made one attempt but wasn't satisfied so I didn't publish it. Still planning to talk more about this in the future.
Ok, Ryan, will be waiting patiently :).
Hi Ryan,
Thanks for these screencasts - they are brilliant. Everybody seems to be talking about REST, but this is one of the only resources I can find that introduces RESTful Rails. More on the topic would be great.
I have one question though - how come you need to specify the fact that the complete action was a :put in the routes.rb file and then again in the link_to method? I would have thought that once it has been set up in the routes.rb file that should be enough?
thanks again,
DAZ
The named route mentioned in the link_to just returns the URL to the action, it doesn't include the HTTP method used to access it. I suppose it would be nice if you could embed the HTTP verb inside of the URL, but that's not really possible.
Hi, Ryan.
I'm new to Rails, and I'm glad I found your site!
I've heard about the PeepCode podcast about RESTful development, but I was wondering if you could point me to any other resources about it.
Thanks!
Hey, great site, thanks so much. This episode got me over the hump. One idea for a podcast might be just a simple search function by something other than the default integer ID. Most legacy systems do not have that kind of ID.
Good luck
Hi! It,s possible to send two or more parameters in RESTful action? For example
some_action_path(@news, @comment) ??
Btw. Great screencast!
I have an Items model and a Categories model. Item belongs_to Category and Category has_many Items.
I would like to filter Items by Category. As I understand it, it would have to be something like '...items/filter/[cat_id]'.
modifying the routes file like this:
map.resources :items, :member => {:filter => :get}
Here is my first question, I am filtering Items by category_id, I feel I need to use :collection but then I would not be able to use an id. Am I doing something I am not supposed to?
Also, instead of getting the reoute I want when using filter_items_path I am getting: .../items/[category_id]/filter
Appreciate if you could help.
Hi Ryan, I searched for hours on how to do custom actions (outside the standard 7) but with no success. Until I found your podcast - thank you!
I have a question about your archive path.
why and how do you use the semicolon between episodes and archive in the path?
rc.com/episodes*;*archive
Hi,
thanks, this is really a great screencast.
I can use the :collection and :member but still dont know the exact difference between them ? Could anybody tell me ?
Thanks !
Clearly explains the usage of custom controller action along with :collection & :member routing resources.
Thank you Ryan
This railscast was still useful to me today, over 2 years later. Thanks :)
Hello, many thanks for great screen cast!
Maybe someone could help with my problem. I have model called wiw_user and action detach mapped with "map.resource :wiw_users, :member => {:detach => :put}", but the link "link_to 'Detach', detach_wiw_users_path(wiw_user)" does not work, reports that I have a nil when I didn't expect it! And that's true, however wiw_user is not nil and without passing the parameter, the URL helper generates correct URL.
Ryan, this screencast is awesome like any other screencast. I am a java/flex programmer, want to do rails. Learning a lot of tips/tricks from these posts. I wait for your posts every week. Thanks a lot again.
I'd loved to see a revised version of this!
Me too!
Me too!
Me three!
Zubin is right I guess.Peepcode's REST tutorial which is just $9 and is a hefty 85 minutes long has all the required source codes.
This has been updated to Rails 5 as a blog post. Custom REST Actions in Rails 5