Rails let user upload photo with carrierwave - ruby-on-rails

I have entity Lead. I want to be able to upload a picture and save its path to status_photo field. I use carrierwave gem for photo uploads.
In my model I've mounted mount_uploader :status_photo, PhotoUploader (My photouploader definitely works so the problem with code in my controller or view)
Right now I have a form for editing my lead.
<%= form_for(#lead) do |f| %>
phone: <%= #lead.phone %>
.
.
.
status: <%= select_tag(:status, options_for_select([['0', 0], ['1', 1]], selected: #lead.status )) %>
photo of car repair process:
<%= f.file_field :status_photo %><br/>
<%= f.submit "Save changes" %>
<% end %>
In my controller I have:
def update
#lead = Lead.find(params[:id])
if #lead.update_attributes(status: params[:status], status_photo: params[:status_photo] )
flash[:success] = "Information updated"
redirect_to leads_url
else
render 'edit'
end
end
As you can see I also update status field which is updated fine but status_photo doesn't. What do I do?

Related

Rails 5 ActiveStorage: Why does my form forget the selected file when the page is re-rendered?

I am using active storage to implement file uploads in a form.
<%= form_for #listing do |form| %>
# ...
<%= form.text_field :title, placeholder: "Title" %>
<%= form.file_field :image, direct_upload: true, accept: "image/png,image/gif,image/jpeg" %>
# ...
<% end %>
I handle the form submission in the controller like so:
def create
#listing = Listing.new(safe_params)
if #listing.save
flash[:success] = "Your posting has been submitted"
redirect_to root_url
else
flash.now[:warning] = #listing.errors.full_messages
render 'new'
end
end
If validations fail, the new page is re-rendered. If I've already entered a title, the previously entered title is present in the re-rendered form. However, if I've already selected a file, that file is not selected in the re-rendered form.
I select a file:
On submission, the file is uploaded but validation fails and the page is re-rendered. The file is no longer selected:
I'm not quite sure what's going on here. Why isn't the file remembered when the new page is re-rendered? Why doesn't the form display the selected file? What do I need to do to ensure it's still selected when the page re-renders?
You could try something like this:
<%= form_for #listing do |form| %>
# ...
<%= form.text_field :title, placeholder: "Title" %>
<%= form.hidden_field :image, value: form.object.image.signed_id if form.object.image.attached? %>
<%= form.file_field :image, direct_upload: true, accept: "image/png,image/gif,image/jpeg" %>
# ...
<% end %>
This is a common issue with browser
you can think it as -
File field as a one-way field. Browser to server, but not server to
browser.
That's not how a file field works in any browser.
So on edit page you would have to explicitly preview file or file name at any other place just to let user know that there is already a file present unless he want to update that with another file.
<%= form_for #listing do |form| %>
#...
<div>
<%if form.object.image.present?%>
#name of file
<%end%>
</div>
<%= form.file_field :image, direct_upload: true, accept: "image/png,image/gif,image/jpeg" %>
#..
<%end%>

Uploading model attributes with file using AWS SDK Rails 4

What I'm looking to do is save my Song model attributes in the same form that I use to upload my mp3 files. I'm currently using the AWS-SDK gem.
As of now, I can successfully upload mp3 files to AWS. However, when I attempt to save the "song_title" and "album_id" attributes as well, the data does not save, but the file is uploaded.
Song attributes:
"id", "url", "name", "created_at", "updated_at", "album_id", "song_title"
songs_controller:
class Albums::SongsController < ApplicationController
before_filter :set_user_friendships
def new
#song = Song.new
end
def create
# Make an object in your bucket for your song
obj = S3_BUCKET.objects[params[:file].original_filename]
# Upload the file
obj.write(
file: params[:file],
acl: :public_read
)
# Create an object for the song
#song = Song.new(
url: obj.public_url,
name: obj.key
)
# Save the upload
if #song.save
redirect_to songs_path, success: 'File successfully uploaded'
else
flash.now[:notice] = 'There was an error'
render :new
end
end
def index
#user_friendships = current_user.user_friendships.all
#songs = Song.all
end
def song_params
params.require(:song).permit(:id, :url, :name, :song_title, :album_id)
end
def set_user_friendships
#user_friendships = current_user.user_friendships.all #this is here because of partial UGHHH
end
end
songs/_upload_song.html.erb:
<p>Upload a song</p>
<%= form_tag songs_path, enctype: 'multipart/form-data' do %>
<%= file_field_tag :file %>
<%= submit_tag 'Upload song' %>
<% end %>
I tried changing the create method within the songs_controller to the following. (I added "song_params"):
def create
# Make an object in your bucket for your song
obj = S3_BUCKET.objects[params[:file].original_filename]
# Upload the file
obj.write(
file: params[:file],
acl: :public_read
)
# Create an object for the song
#song = Song.new(song_params,
url: obj.public_url,
name: obj.key
)
# Save the upload
if #song.save
redirect_to songs_path, success: 'File successfully uploaded'
else
flash.now[:notice] = 'There was an error'
render :new
end
end
and the songs upload form to the following:
<p>Upload a song</p>
<%= form_tag songs_path, enctype: 'multipart/form-data' do %>
<%= file_field_tag :file %>
<%= text_field_tag :song_title %>
<%= submit_tag 'Upload song' %>
<% end %>
However, I get the following error:
param is missing or the value is empty: song
I suspect that the issue has something to do with the "form_tag" vs the "form_for" difference in forms.
After taking a look around, I found that the form_tag creates a basic form, and form_for creates a form for a model object.
So when changing the upload form to the one below:
<%= form_for Song.new, enctype: 'multipart/form-data' do |f| %>
<%= f.label :file %>
<%= f.file_field :file %>
<%= f.label :song_title %>
<%= f.text_field :song_title %>
<%= f.label :album_id %>
<%= f.number_field :album_id %>
<%= f.submit "Save", class: "btn btn-primary" %>
<% end %>
I get the following error:
undefined method `original_filename' for nil:NilClass
And the error references the following lines within the "create" method of my songs_controller:
obj = S3_BUCKET.objects[params[:file].original_filename]
I looked through the AWS-SDK documentation, but I find this information surprisingly hard to find.
Would someone mind shedding some light on how I would go about saving my Song attributes as well as uploading my Song mp3 file?
You created the method song_params, but I do not see you using it. You must explicitly set your object with the params returned by song_params in order to save the attributes upon song.save.
#song = Song.new(song_params)

Ruby on Rails: Deleting multiple attachments in paperclip on updates

Paperclip's document says to set attribute to nil for deleting attachments (in my case - photos).
I want users to be able to edit their projects, and delete the photos they've attached. When they're ready to delete the photos, they'll click on the "Update" button, because they're editing the projects, not just removing photos. I'm using checkboxes because each project can contain multiple photos:
<%= simple_form_for #project, html: { multipart: true } do |f| %>
<% #project.project_images.each do |pic| %>
<%= f.simple_fields_for :project_images, pic do |image_fields| %>
<%= image_tag pic.photo.url(:thumb) %>
<%= image_fields.check_box :_destroy %>
<% end %>
<% end %>
<%= f.button :submit %>
<% end %>
This is what it looks like:
So, how do I use this to delete my photos within my update method in the controller?
Current:
def update
#project = current_user.projects.find(params[:id])
respond_to do |format|
if #project.update(project_params)
if params[:photos]
params[:photos].each { |image|
#project.project_images.create(photo: image)
}
end
end
end
end
You can see that I'm still creating new images into the database if user decides to do so.
In my log, I got this
"project_images_attributes"=>{
"0"=>{"_destroy"=>"0", "id"=>"25"},
"1"=>{"_destroy"=>"1", "id"=>"26"},
"2"=>{"_destroy"=>"1", "id"=>"27"}}
This shows that I did indeed checked off photo id's 26 and 27 to be deleted. So now, I just want to know how to pass this to my update method to be destroyed? I would have to loop through this, check the ids that have _destroy => 1 to it, and then add nil and then save?
And lastly, my project params (which includes :_destroy)
def project_params
params.require(:project).permit(
project_images_attributes: [:id, :project_id, :photo, :_destroy])
end
Just add:
accepts_nested_attributes_for :project_images, allow_destroy: true
in your project.rb and pass the parameters as it is, as it seems you are doing it the right way. The rest rails will handle to delete or create a new record. You don't need to loop on the params[:photos].
Hope this helps.

rails carrierwave edit/update form

In my rails project I use Carrierwave to upload images to S3 via fog. So far I have the Create Read and Delete portions of the CRUD spectrum working.
My problem is the edit/update portion. Im use the same _form.html.erb to edit that I used for creating records. When I click the edit link the form loads all of my data into the form fields for editing with the exception of the image. The form field is blank as though there is no image associated with the record.
How do I update an image that is already saved to S3?
Models/listing.rb
class Listing < ActiveRecord::Base
attr_accessible :body, :price, :title, :image
mount_uploader :image, ListingUploader
end
Controllers/listings_controller.rb (edit/update portion)
def edit
#listing = Listing.find(params[:id])
end
def update
#listing = Listing.find(params[:id])
if #listing.update_attributes(params [:listing])
redirect_to :action => 'show', :id => #listing
else
render :action => 'edit'
end
end
_form.html.erb
<%= form_for #listing, :html => { :multipart => true } do |l| %>
<p>
<%= l.label :title %>
<%= l.text_field :title %>
</p>
<p>
<%= l.label :price %>
<%= l.text_field :price %>
</p>
<p>
<label>Upload a Picture</label>
<%= l.file_field :image %>
<%= l.hidden_field :image_cache %>
</p>
<div class="image-pre">
<%= image_tag(#listing.image_url(:thumb)) if #listing.image? %>
</div>
<p>
<%= l.label :body %>
<%= l.text_area :body, :class => "tinymce" %>
<%= tinymce %>
</p>
<%= l.submit %>
<% end %>
In your listings_controller.rb, try something like:
def edit
#listing = Listing.find(params[:id])
#listing.image.cache!
end
Haven't tested this out myself, but I think it might work, given that image_cache field is used to circumvent this problem normally.
I had upgraded my app to Rails 6 sometime ago, then started experiencing this issue.
tldr; you need a carrierwave gem version 2.1.0 or higher for Rails 5.0 and up.
I used bundle outdated --strict to see if carrierwave could be upgraded. Check Gemfile to see if the requested version is set correctly. Also check Gemfile.lock to see if other gems are holding it back; you'll need to upgrade them as well. Then I used bundle update --strict to perform the actual upgrade.

The better way to pass the foreign_key value to the Rails controller

It's been almost a week since I've began to dig deeper in forms , associations , hashes , symbols... But it seems I cannot solve the puzzle without your help .
I am working on a project for displaying different galleries content . The basic idea is when the user sees the names of galleries (names are links ) to be able to click on chosen one. Then all the images ,that belong to this gallery , are displayed . On the bottom there should be a link "Add image in this gallery" .
My models :
class Gallery < ActiveRecord::Base
attr_accessible :name
has_many :pictures
end
class Picture < ActiveRecord::Base
attr_accessible :image
belongs_to :gallery
end
I have created index on gallery_id for the 'pictures' table .
My big problem appears here , how to pass the gallery_id to the controller's action 'new' . As I've seen in "Agile web development with Rails" it could be :
<%= link_to 'Add a picture here...',new_picture_path(:gallery_id=>#gallery.id) %>
As it seems in this case the foreign_key :gallery_id is exposed in the URL bar of the browser . The second problem is that :gallery_id is available for the controller 'new' function , but "disappears" for the 'create' function (causing an error " Couldn't find Gallery without an ID ") .
The problem is gone when I add a hidden field in the _form for pictures , in my case :
<%= form_for(#picture) do |f| %>
<div class="field">
<%= f.hidden_field :gallery_id , :value=>params[:gallery_id] %>
<%= f.label :image %><br />
<%= f.file_field :image %>
</div>
<div class="actions">
<%= f.submit "Create" %>
</div>
<% end %>
Here are my definitions in the 'pictures' controller :
def new
#gallery=Gallery.find(params[:gallery_id])
#picture=#gallery.pictures.build
end
def create
#gallery = Gallery.find(params[:gallery_id])
#picture = #gallery.pictures.new(params[:picture])
if #picture.save
redirect_to(#picture, :notice => 'Picture was successfully created.')
else
redirect_to(galleries ,:notice => 'Picture was NOT created.')
end
end
And finaly the link_to definition in show.html.erb for galleries:
<% for picture in selpics(#gallery) %>
<div id= "thumb" >
<%= image_tag picture.image %>
</div>
<% end %>
<%= link_to 'Add a picture here...',new_picture_path(:gallery_id=>#gallery.id) %>
Here is the debug output before submitting the image :
--- !map:ActiveSupport::HashWithIndifferentAccess
gallery_id: "6"
action: new
controller: pictures
and after submitting the 'create' button (with exception raised ) :
{"utf8"=>"✓",
"authenticity_token"=>"IGI4MfDgbavBShO7R2PXIiK8fGjkgHDPbI117tcfxmc=",
"picture"=>{"image"=>"wilsonblx.png"},
"commit"=>"Create"}
As you see , there is nothing like "gallery_id" in the "pictures" hash .
Summarizing my questions to you :
Is there a way to pass the foreign_key without hidden_field ?
Could I hide somehow passing the foreign key form showing in the URL bar ?
Is there an alternative on passing arguments using 'link_to' ?
Thank you .
You may want to consider reading the Rails Guide on nested resources:
http://guides.rubyonrails.org/routing.html#nested-resources
In a nutshell:
routes.rb
resources :galleries do
resources :pictures do
end
# Generates the routes: /galleries/:gallery_id/pictures
pictures_controller.rb
def new
#gallery = Gallery.find(params[:gallery_id])
#picture = Picture.new
end
def create
#gallery = Gallery.find(params[:gallery_id]) # gallery_id is passed in the URL
#picture = #gallery.build(params[:picture])
if #picture.save
# success
else
# fail
end
end
pictures/new.html.erb
<%= form_for [#gallery, #picture] do |f| %>
<div class="field">
<%= f.hidden_field :gallery_id , :value=>params[:gallery_id] %>
<%= f.label :image %><br />
<%= f.file_field :image %>
</div>
<div class="actions">
<%= f.submit "Create" %>
</div>
<% end %>
Ok, so the gallery_id is still passed through the URL, but I don't really see anything wrong with that. You have to pass it somewhere, right? You really only have 3 sane choices on where to pass it: a hidden field, as a querystring parameter, or tucked away inside the URL (nested resource). Of the 3, the latter is IMHO the cleanest method.
If you want to make things even easier on yourself, I highly recommend looking into Jose Valim's Inherited Resources gem that takes care of a lot of this boilerplate nastiness for you:
https://github.com/josevalim/inherited_resources
You need not use the numeric ID's in your RESTful routes. Look at permalink_fu, and use the :permalink field rather than the :id to refer to each gallery resource.
/galleries/louvre
/galleries/moma/382
And
... new_picture_path(:gallery_id => #gallery.permalink)
The key here is using a symbolic, unique key that's not the ID, permalink's are pretty good for that.
You can choose to pass the permalink in as :id and update your controller actions to expect that.

Resources