Remove nested attribute rails - ruby-on-rails

I have a Post model that
has_many photos
The Post model has
accepts_nested_attributes_for :photos
In my Post edit form i show all the images that are currently associated with the post
<% for photo in #post.photos %>
<%= image_tag(photo.avatar.url(:thumb)) %> <%= link_to "Remove Photo", post_photo_path(#post, photo), :method => :delete %>
<% end %>
When hovering over over the Remove Photo link the path looks like so
http://localhost:3000/posts/17/photos/45
I get uninitialized constant PhotosController, but that's expected as there isn't one.
My question is do i create one and have a destroy action in there that will just delete that photo.. Is this the way to go about it or is there a better way?
EDIT
Photos Controller
def destroy
#photo = Photo.find(params[:id])
#photo.destroy
redirect_to edit_post_path, :notice => "Successfully deleted Photo"
end
Routes
resources :posts do
resources :photos
resources :departments
end
Rake Routes
edit_post GET /posts/:id/edit(.:format)
Thanks

The option you described, invoking the destroy action of a PhotosController, would certainly do the trick and would probably be the simpler and quicker solution. Be careful that if a user makes changes to other aspects of the Post and then clicks a link to delete the Photo in the same form, their changes would probably get lost. However, using AJAX might help alleviate this concern.
Another option would be to leverage rails existing support for nested forms. Because you are already using a nested form with accepts_nested_attributes_for you're about halfway there. If you haven't seen this yet, check out Ryan Bates' podcast on Nested Forms:
http://railscasts.com/episodes/196-nested-model-form-part-1
http://railscasts.com/episodes/197-nested-model-form-part-2
And if you're willing to pay (trust me, it's worth it), the revised solution is a little nicer:
http://railscasts.com/episodes/196-nested-model-form-revised
The meat and potatoes of what you're interested in is towards the middle to the end of the podcast where Ryan uses the hidden _destroy field in the nested element's params to indicate to the controller than when the parent object is updated, the nested association should be destroyed.
You can also find an off the shelf gem that wraps up this same Rails/Javascript functionality that Ryan implemented from scratch. I've used cocoon in the past and have been quite pleased.
This technique is a little more complicated but I tend to prefer it in this case because the focus of the form is to modify a Post, not a Photo. In the first solution, you would have to make the PhotosController#destroy action redirect back to the form. Or if you use AJAX, then you would have to write javascript that was tailored to the layout of your Post HTML to properly hide/remove the element after it was destroyed. This may be ok for small applications, but as the project grows you may find yourself trying to use the same PhotosController for other work-flows and that could become brittle to maintain.

My opinion is that the best thing to do would be to create a PhotosController to handle the destroy action. That way you will preserve the Single Responsibility Principle, and have less workaround with adding custom routes.
Also, there is the option to put that one action in the PostsController if you are really sure you won't be adding any additional actions connected to Photos (like tagging photos, or whatever).
It's really up to you, but i believe the first one is easier.
If your going with the second option, be sure to create a route for the destroy photo action in the PostsController and the link should be pointing at that exact action.
Your routes.rb:
resources :posts do
resources :photos, :only => [:destroy]
resources :departments
end

Related

How to call a custom method in a Rails controller from a View

I have set up a site that is correctly using basic CRUD functionality succesfully. However, when I try to add a custom method to my controller I cannot seem to hook it up to a link_to call. I keep getting a method not found error.
The Controller method looks like this:
def complete
return render :text => "Complete"
end
and my call in the View looks like this:
<%= link_to 'Complete', complete_list_task_path(#list,#task) %>
This same call works for my Edit method, so I'm not sure what I'm doing wrong. Do I need to do anything special when the method is not a basic CRUD call?
The only relevant part of my route file looks like this (List and Task are nested resources. List has many tasks, and task belongs to a list):
resources :lists do
resources :tasks
end
I have also tried adding post "complete" => "lists/:id/tasks/:id#complete", :as => "complete" to my route to see if it would help to implicitly try to call it, but I still got a "method not found error".
Any help in figuring out how to make this call would be greatly appreciated. Thank you!
See Adding More RESTful Actions in the Rails Routing guide for details; the nutshell is that if you want routing to recognize anything other than the standard methods, you need to add it.
You need to declare the method in the router, 'resources' refers to the 7 crud actions (index, new, create, edit, update, delete, show).
Off the top of my head, I think you'd need:
resources :lists do
resources :tasks do
member do
post :complete
end
end
end
The nesting makes me less confident, but that's the general thing you need to do.

Rails Routing question

I'm not very familiar with routing, but here is my dilemma:
I have a photos controller with the usual show, edit, etc. views. I am trying to build a view to moderate photos. I have a moderate.html.erb view under my photos views. I have also defined moderate method in my photos_controller. If I try to access this view like /photos/moderate I get Couldn't find Photo with ID=moderate.
Am I building this the correct way, or does moderate need to have its own separate controller and view? Seems silly to me for that to be the case. Is this just something I need to configure in my routes?
UPDATE:
I've added this to my routes:
resources :photos do
resources :comments
collection do
get 'moderate'
end
end
Still getting the same Couldn't find Photo with ID=moderate message when I go to /photos/moderate...
UPDATE:
Crap! Just figured out the problem. I had a before_filter running that needed to ignore the moderate action... It's now working fine. Sorry for the trouble.
You can add a further restful action. Take a look at the docs
So you're trying to split out the concern of being able to edit and view photos?
If you wanted to keep your actions RESTful, you could create a "moderate" namespace and put all the actions that require authentication for photos in that namespace.
For instance,
#routes.rb
namespace :moderate do
resources :photos # will create paths like '/moderate/photos/', '/moderate/photos/1'
end
#controllers/moderate/photos_controller.rb
class Moderate::PhotosController < ActionController
before_filter :authorize_moderator!
#your standard restful actions like 'index', 'show', 'edit, 'update' would go in here.
end

One model and Many edit views

I have a model I named User, and I want use two different Views to edit it: the usual edit view and another view I called edit_profile.
I had no problem in creating routing, controller and views: I added edit_profile and update_profile views, and I added on routes.rb the line:
map.resources :users ,:member => {:edit_profile => :get, :update_profile => :put}
The problem is: when I submit the form in edit_profile and some error occur in some input fields, rails reload the edit_path page instead of edit_profile_path page !
This is the form on edit_profile.html.erb
form_for(:user, #user, :url => {:action => :update_profile}, :html => { :method => :put} ) do |f|
f.text_field :description
f.text_area :description
f.error_message_on :description
....
....
f.submit 'Update profile'
After clicking Update profile, if input errors occur I want to show edit_profile view instead of edit view
Where is the problem ?
Do You have some ideas ?
many thanks
Adding extra actions to a RESTful controller is often a code smell, an indication that there's a better way to model what you're trying to do. In this case, profile is really a sub-resource of user:
map.resources :users, :has_one => :profile
making your profile routes like
GET /users/1/profile # show
GET /users/1/profile/edit #edit
PUT /users/1/profile # update
DELETE /users/1/profile #destroy
You will have a separate ProfilesController for these actions... much cleaner.
How you model the data is up to you, (you don't have to have a one-to-one correlation between your models and your controllers!), but in this case I'd probably use ActiveRecord's aggregations to model the relationship between User and Profile. Think of it as an embedded has_one: http://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html
Note that adding additional actions to RESTful controllers isn't always "wrong" ... its up to you to determine when its appropriate to split off the new actions into a separate resource. In this case, however, I think it's very clear-cut!
Your controller's action (the edit action, I assume) will need to know whether it has been reached via the normal edit page or the edit_profile page. You can use a hidden field named, perhaps, profile to post a breadcrumb that will tell it that. By doing this, you can redirect conditionally based on the existence of a profile param.
A cleaner way is to create a new action called edit_profile and extract the editing code to a common method that is called from both edit and edit_profile let the public methods handle any redirects.
Take a look in your user_controller file's update method. That's where submitting the edit form takes you. You'll see there that if the record can't be updated, it redirects back to the edit method.
One way to do what you want is to make your edit_profile form point to a new method, perhaps called update_profile, which is the same as the edit method but redirects to edit_profile when the record can't be saved.
Perhaps a better and DRY-er way to do it would be to pass a parameter from the edit_profile form that you can detect in your existing update method to differentiate between update attempts coming from edit / edit_profile.
Good luck!

Understanding Routes in Rails

I actually have two questions. I've read the Rails guide and a couple of other articles, but I haven't been able to translate what I read into working routes. I have an application that allows the uploading of images from several different contexts. I'd like the URI to express the proper context so that the following URIs access the same page:
/images/upload
/photos/upload
In this example, I've overridden the new_image_path to use upload for descriptive purposes. I have the override working, but using :as to map images to photos only appears to work one way (with :as => 'photos' in place, the /images routes don't work). Is there a way to make multiple routes point to the same place?
I also have a couple of different ways to upload images/photos/etc. The standard method with a single image per form or a batch method where the user uploads a zip file and that archive is extracted and each of its images is saved.
It seems like the most semantic way to do this is by adding a handler component to the URI (e.g. /images/upload/batch), but I'm not sure how to handle this. The default route path seems pretty general for something that would only be required for images, but I also don't want to be so specific with a named route for the entire bit. What's the best way to do something like this?
Thanks.
Update: Based on jonnii's answer to my first question, I've added the following to my routes.rb file:
map.resources :images, :path_names => { :new => 'upload' }
map.resources :photos, :controller => 'Images', :path_names => { :new => 'upload' }
That seems to do the trick for allowing me to use /images/ and /photos/ interchangeably.
I'm assuming you're doing your photos routes using resources:
map.resources :photos
If that's the case you should be able to define a second resource pointing to the same controller as the photos resource:
map.resources :uploads, :controller => 'PhotosController'
I haven't tested this, but I don't see why something like this wouldn't work..
Question 2:
There are a few different ways you can do batch uploads, I think the best way is to have a separate resource as you're most likely going to have a different UI for it. For example you might do:
map.resources :batch_uploads
This would probably be enough if you were going to take batch uploads as a zip.
An option that's slightly more complicated but takes advantage of the rails niceties (and lets be honest, who doesn't want to take advantage of that??) is something with nested child forms and accepts_nested_attributes_for. This would be useful if you wanted to allow a user to attach more than one image to a form at time.
For example, if your model was something like:
class User < AR:B
has_many :photos
end
You could have a route like:
map.resources :users do |u|
u.resources :photos, :collection => {:get => :new_batch, :post => create_batch}
end
In your new_batch view you would have a form_for #user with a user_form.fields_for :photos. You can add a new form using ajax, whatever and post it all at once.
If you wanted to keep the same semantics as you have now and didn't want to add any more routes you could extend your model to do something different based on the filename of what is being uploaded.
For example if you were using paperclip for attachments you could stop processing the attachment if the filename ends with .zip (this code is not guaranteed to work, I'm doing it from memory):
def is_zip?
attachment.filename.ends_with?('.zip')
end
before_attachment_process do |attachment|
false if is_zip?
end
before_filter :process_bulk_attachment, :if => :is_zip?
def process_bulk_attachment
... extract the zip and save each image in it ...
false
end
The beauty of this is that it's part of the model. You should always aim for fat models and skinny controllers!
I hope this gives you a few ideas and/or points you in the right direction.
I've gotten a little closer to what I'm going for:
map.resources :images, :path_names => { :new => 'upload' }
map.resources :images, :new => { :batch => :get }
The former allows me to use /images/upload instead of /images/new, as shown in jonnii's answer to my first question. The latter allows me to specify a second route to "new" functionality via /images/new/batch which calls ImagesController#batch. I was hoping to be able to use /images/upload/batch, but this may have to do.
Clearly, I still have a long way to go before I really understand routing in Rails. jonnii, if I'm just rehashing part of what you've already said, I apologize. I may have to plead ignorance with respect to much of your answer to question 2.

Rails - RESTful Routing - Add a POST for Member i.e(tips/6)

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
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.
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.

Resources