How to handle avatar upload in REST manner - ruby-on-rails

I have User model, where user should be able to upload one picture as an avatar. But I don't really know how to implement this in correct REST way. I see two options
First I think of avatar as of member resource that belongs to user, so I add two actions to my Users Controller, one avatar for the upload form, and second action for handling the actual upload avatar_upadte, so my routes look like
map.resources :users, :member => { :avatar => :get, :avatar_update => :post }
Second I can think of avatar as of separate resource and create Avatars Controller instead, which would be like
map.resources :users
map.resources :avatars, :only => [ :new, :create ]
I don't want to handle avatar upload in edit_user action, since it is already pretty complex.
The third option could be to have only avatar action to display the form and then user REST user update action as upload target, where form would be something like this
<% form_for #user, :url => user_path(#user), :html => { :multipart => true } do |f| %>
<%= f.label :avatar, 'Avatar' %>
<%= f.file_field :avatar %>
<%= f.submit 'Upload' %>
<% end %>
But I don't like this this attitude either, because since my update action already handles another form, it would be pretty ugly to handle redirects etc.

For the app I work on we do something like this:
map.resources :users do |u|
u.resources :avatars # You can also make this a singleton resource
end
This gives you a route like: /users/1/avatars/new
This might not make sense for your app, if not I would choose the option of having a separate AvatarsController.

If I understand correctly:
Option 1 adds a custom action to the User resource, which is best avoided if at all possible.
Option 2 splits User and Avatar into separate resources, but doesn't make it easy to connect the two (for example, when creating an upload form).
Option 3 looks like you'd view the Avatar as a User attribute.
The most important thing to figure out at this point is: what's the cardinality of the relationship between User and Avatar. Does a User have one Avatar or many? If there's a one-to-many relationship, then a solution like the one Jonnii proposes might make sense.
If a User will only have one Avatar (which seems likely), I would consider going with the simplest solution and make the Avatar a User attribute.
Although I haven't gotten around to trying it out, I've heard good things about Paperclip. The example usages even discuss using Paperclip with avatars.
For an even more lightweight solution, you might consider Gravatar, which would eliminate the need for a file upload entirely and simplify things a lot.

Related

Remove nested attribute 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

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!

Ruby on Rails: form and radio button question

I have a form around a list of items. I would like to check a radio button on the list and then, after clicking 'Submit', go to the 'Edit' page of the selected item.
The problem is that if I am writing something like
<%= form_tag edit_item_path %>
but Rails complains that I didn't provided a proper id, which in turn is being set on my radio buttons as the following:
<%= radio_button 'item', 'id', item.id %>
So - how would I send a form with a selected id to a given action?
Doing it the way you describe would not work, since edit_item_path by default RESTful path definitions is a GET request. You are trying to make it a POST request. If you would like to stick to the RESTful way of doing things I would recommend simply looping through your items and provide links to edit, since you are planning to edit them one at a time anyways. Otherwise, you would have to define a new route if you would prefer doing things within the form with radio buttons. If you are, than you would add something like this to your config/routes.rb: (assuming you are using rails 2.3.x)
map.resources :items, :member =>{:edit_radio_form => :post}
Than your form would look like this:
<%= form_tag edit_radio_form_item_path do |form| %>
And it should work, but it not the right way of doing things for several reasons, the most anoying one of which is that you won't get a full URL on your edit page because you got there with a POST request, so if you refresh the page, your item id param will be gone and will produce an error.

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.

Ruby on Rails: Confirmation Page for ActiveRecord Object Creation

Using Ruby on Rails I want a confirmation page before creating an ActiveRecord object. The user will see a preview of the item they are creating before submitting and the object being saved in the database
A common pattern;
User visits /entry/new
User enters details and clicks submit
User is redirected to /entry/confirm which displays the entry and clicks submit or edit to correct mistakes
Object is saved
How would you implement it?
Another option to solve this issue adding by a virtual confirmation attribute to your model. This way, there is no need to create a separate action for this:
class MyRecord < ActiveRecord::Base
attr_accessor :confirmation
validates_acceptance_of :confirmation, :on => :create
end
Now, your new object will not save correctly because the validation will fail on the confirmation field. You can detect this situation and present something like this:
<% form_for(#my_record) do |form| %>
...
<%= form.check_box :confirmation %> Really create this record.
<%= submit_tag('Confirm') %>
<% end %>
I would probably add a "preview" action to the routes.rb file for that model:
map.resource :objects, :new => { :preview => :post }
You would get to this preview action by POSTing the preview_object_url named route. You would need to essentially create the Object in the same way you would in your create action, like this:
def preview
#object = Object.new(params[:object])
end
This page would then POST to the create action, which would then create the Object. It's pretty straight forward.
http://api.rubyonrails.org/classes/ActionController/Resources.html
A few options
1- store the object you want to create in the session until you hit the confirm page, then just save it
2- pass around the object w/ each Post/submit from new -> details -> confirm
I would probably go with 2, since I am not prone to saving state with the session.
I'm not sure how to do this (RoR is new to me) but you could just specify the action for /new as /confirm, and then it calls create.
Right?

Resources