views:

587

answers:

2

Hello there,

I'm trying to create some nice RESTful structure for my app in rails but now I'm stuck on a conception that unfortunately I'm not sure if its correct, but if someone could help me on this it would be very well appreciated.

If noticed that for RESTful routes we have (the uncommented ones)

collection
    :index   => 'GET'
    :create  => 'POST'
   #:?       => 'PUT'
   #:?       => 'DELETE'

member 
    :show    => 'GET'
   #:?       => 'POST'
    :update  => 'PUT'
    :destroy => 'DELETE'

in this case I'm only talking about base level action or the ones that occur directly inside i.e http://domain.com/screename/tips or http://domain.com/screename/tips/16

but at the same time I notice that there's no POST possibility for the members, anybody knows why?

What if I'm trying to create a self contained item that clones itself with another onwer?

I'm almost sure that this would be nicely generated by a POST method inside the member action, but unfortunately it looks like that there's no default methods on the map.resources on rails for this.

I tried something using :member, or :new but it doesn't work like this

 map.resources :tips, :path_prefix => ':user', :member  => {:add => :post}

so this would be accessed inside http://domain.com/screename/tips/16/add and not http://domain.com/screename/tips/16.

So how would it be possible to create a "default" POST method for the member in a RESTful route?

I was thinking that maybe this isn't in there because it's not part of REST declaration, but as a quick search over it I found:

POST

for collections : Create a new entry in the collection where the ID is assigned automatically by the collection. The ID created is usually included as part of the data returned by this operation.

for members : Treats the addressed member as a collection in its own right and creates a new subordinate of it.

So this concept still the same if you think about the DELETE method or PUT for the collection. What if I want to delete all the collection instead just one member? or even replace them(PUT)?

So how could I create this specific methods that seems to be missing on map.resources?

That's it, I hope its easy to understand.

Cheers

A: 

I'm not quite following, but to answer your last question there, you can add collection routes for update_multiple or destroy_multiple if you want to update or delete multiple records, rather than a single record one at a time.

I answered that question earlier today actually, you can find that here.

The reason that there's no POST to a particular member is because that member record already exists in the database, so the only thing you can do to it is GET (look at), PUT (update), or DELETE (destroy). POST is designed only for creating new records.

If you were trying to duplicate an existing member, you would want to GET the original member in a "duplicate" member action and POST to the resource root with its contents.

Please let me know if I'm missing what you're asking.

bensie
Hi bensie, thanks a lot mate, I always lose my focus on REST because I'm all the time forgetting its existence with database records, so my question was how to put this destroy and update multiple methods at base level of collection as /screename/tips/ with PUT or DELETE. are those special names to use it the base level? because I tested and you have to use as /screename/tips/destroy_multiple if you add it as a collection method. so to understand my question its easy now: Why not /screename/tips with DELETE method instead? wouldn't be more correct? why the need to add the method name?
ludicco
regarding the POST I really see now :) and I see if I want in this case to add tips for that users I would have to do something like /screename/tips/users with POST that in this case the use of shallow routing would look better like /tips/users with POST instead so I see that every post have to create its destination on the route now, thanks again for the clarification. But one question still, that is what would reflect the method User.first << Tips.first its not update nor create its an insertion on a habtm association, what would be the right directive for it?
ludicco
I see what you're getting at. My best guess as to why DELETE to the root of the resource is not built-in is because it would be arbitrary from a standards perspective what should be deleted. Should ALL records be destroyed when DELETE is called? That'd be dangerous. So, you do need to add :collection => { :destroy_multiple => :delete, :update_multiple => :put } to get what you're looking for.
bensie
+1  A: 

The reason they aren't included by is that they're dangerous unless until secured. Member POST not so much as collection PUT/DELETE. The missing member POST is more a case of being made redundant by the default collection POST action.

If you still really want to add these extra default actions, the only way you're going to be able to do it, is it to rewrite bits of ActionController::Resources.

However this is not hard to do. Really you only need to rewrite two methods. Even then you don't have to rewrite them fully. The methods bits that you'll need to add to those methods don't really on complex processing of the arguments to achieve your goal. So you can get by with a simple pair of alias_method_chain.

Assuming I haven't made any errors, including the following will create your extra default routes as described below. But do so at your own risk.

module ActionController
  module Resources
    def map_member_actions_with_extra_restfulness(map, resource)
      map_member_actions_without_extra_restfulness(map, resource)
      route_path = "#{resource.shallow_name_prefix}#{resource.singular}"
      map_resource_routes(map, resource, :clone, resource.member_path, route_path, :post, :force_id => true)
    end

    alias_method_chain :map_member_actions, :extra_restfulness


    def map_default_collection_actions_with_extra_restfullness(map, resource)
      map_default_collection_actions_without_extra_restfullness(map,resource
      index_route_name = "#{resource.name_prefix}#{resource.plural}"

      if resource.uncountable?
        index_route_name << "_index"
      end

      map_resource_routes(map, resource, :rip, resource.path, index_route_name, :put)
      map_resource_routes(map, resource, :genocide, resource.path, index_route_name, :delete)
    end

    alias_method_chain :map_default_collection_actions, :extra_restfulness
  end
end

You'll have to mess around with generators to ensure that script/generate resource x will create meaningful methods for these new actions.

Now that we've covered the practical part, lets talk about the theory. Part of the problem is coming up with words to describe the missing actions:

The member action described for POST in the question, although technically correct does not hold up when applied to ActionController and the underlying ActiveRecord. At best it is ambiguous, at, worst it's not possible. It makes sense for resources with a recursive nature (like trees,) or resources that have many of a different kind of resource. however this second case is ambiguous and already covered by Rails. Instead I chose clone for the collection POST. It made the most sense for default post on an existing record. Here are the rest of the default actions I decided on:

collection
    :index       => 'GET'
    :create      => 'POST'
    :rip         => 'PUT'
    :genocide    => 'DELETE'

member 
    :show    => 'GET'
    :clone   => 'POST'
    :update  => 'PUT'
    :destroy => 'DELETE'

I chose genocide for collection DELETE because it just sounded right. I chose rip for the collection PUT because that was the term a company I used to work for would describe the act of a customer replacing all of one vendor's gear with another's.

EmFi
That was really a class EMFi, I really appreciate this. :) Now its much easier to understand since your brought the both technical and theoretical sides of the situation. I will start to think this way before building the routing architecture for my apps. Thanks so much. +1 !!!
ludicco