I'm using multiple file uploads from the master branch and PostgreSQL
My product model hast a string field called "images" and I can attach multiple images just fine.
What I can't figure out though, how can I remove one image from the products?
I can remove all images as described in the docs:
product.remove_images!
product.save
but didn't find a way how to remove a single image.
Have you considered using a nested form for to try to delete an image?
Here is a piece of code from carrierwave's github site...
<%= form_for #product, html: { multipart: true } do |f| %>
.
.
.
<label>
<%= f.check_box :remove_images %>
Remove images
</label>
<% end %>
...which I am sure you have seen. Although, of course, calling remove_images! would not work in your case, since that implies a unified action on all images.
Try the following modification of the above and see if it helps you sort this problem and target each image individually...
<%= nested_form_for #product, :html=>{ :multipart => true } do |f| %>
.
.
.
<%= f.fields_for :images do |product_form| %>
<%= product_form.link_to_remove "Remove this image" %>
<% end %>
<% end %>
To make this work make sure to include gem 'nested_form', '~> 0.3.2' in your Gemfile.
Hopefully this helps you out.
I learn from #Cris and use the following code snippet when working with S3.
def remove_image_at_index(index)
remain_images = #product.images # copy the array
deleted_image = remain_images.delete_at(index) # delete the target image
deleted_image.try(:remove!) # delete image from S3
#product.images = remain_images # re-assign back
end
I wrote a blog post about this , and create a sample project with more concrete codes.
You should be able to use remove_image on the specified image (from the array). So you'll need to somehow identify the image first (ex. images[0].remove_image!)
adding something like this to the product model does the trick.
def delete_image(idx)
remaining_images = []
self.images.each_with_index { |img,ii| remaining_images<<File.open(img.path) unless idx == ii}
self.images=remaining_images
end
Carrierwave also takes care of removing the file after saving the record.
Related
So I have a model called Photo and obviously I don't want to upload one photo at a time, so I replaced the new photo form with multi-file uploading. I am not quite sure how I can make these file uploads turn into unique photo models.
<%= bootstrap_form_with(:model => photo, :local => true) do |form| %>
<%= form.file_field :image, :multiple => true, :direct_upload => true %>
<%= form.submit %>
<% end %>
Edit: I am using Active Storage on Rails 6.0.0 rc2
You can't make multiple photo models. You probally mean multiple records. A model is a blueprint of the table in your database.
Check your terminal logs when you submit the form and you will probally see that in the params you will have something like: photo => [ files here ]
So in your controller create you have to loop through the array and create a photo record for each photo, something like this:
def create
params[:photo].each do |photo|
Photo.create(file: photo.file)
end
For those that come across this problem, here was my solution:
def create
photo_params[:images].each do |image|
#photo = current_user.photos.build
#photo.image = image
#photo.save
end
end
I need to get the path to the file on disk which is using ActiveStorage. The file is stored locally.
When I was using paperclip, I used the path method on the attachment which returned the full path.
Example:
user.avatar.path
While looking at the Active Storage Docs, it looked like rails_blob_path would do the trick. After looking at what it returned though, it does not provide the path to the document. Thus, it returns this error:
No such file or directory # rb_sysopen -
Background
I need the path to the document because I am using the combine_pdf gem in order to combine multiple pdfs into a single pdf.
For the paperclip implementation, I iterated through the full_paths of the selected pdf attachments and load them into the combined pdf:
attachment_paths.each {|att_path| report << CombinePDF.load(att_path)}
Use:
ActiveStorage::Blob.service.path_for(user.avatar.key)
You can do something like this on your model:
class User < ApplicationRecord
has_one_attached :avatar
def avatar_on_disk
ActiveStorage::Blob.service.path_for(avatar.key)
end
end
I'm not sure why all the other answers use send(:url_for, key). I'm using Rails 5.2.2 and path_for is a public method, therefore, it's way better to avoid send, or simply call path_for:
class User < ApplicationRecord
has_one_attached :avatar
def avatar_path
ActiveStorage::Blob.service.path_for(avatar.key)
end
end
Worth noting that in the view you can do things like this:
<p>
<%= image_tag url_for(#user.avatar) %>
<br>
<%= link_to 'View', polymorphic_url(#user.avatar) %>
<br>
Stored at <%= #user.image_path %>
<br>
<%= link_to 'Download', rails_blob_path(#user.avatar, disposition: :attachment) %>
<br>
<%= f.file_field :avatar %>
</p>
Thanks to the help of #muistooshort in the comments, after looking at the Active Storage Code, this works:
active_storage_disk_service = ActiveStorage::Service::DiskService.new(root: Rails.root.to_s + '/storage/')
active_storage_disk_service.send(:path_for, user.avatar.blob.key)
# => returns full path to the document stored locally on disk
This solution feels a bit hacky to me. I'd love to hear of other solutions. This does work for me though.
You can download the attachment to a local dir and then process it.
Supposing you have in your model:
has_one_attached :pdf_attachment
You can define:
def process_attachment
# Download the attached file in temp dir
pdf_attachment_path = "#{Dir.tmpdir}/#{pdf_attachment.filename}"
File.open(pdf_attachment_path, 'wb') do |file|
file.write(pdf_attachment.download)
end
# process the downloaded file
# ...
end
I have a edit form that prepopulates with the current values. Its a custom edit screen (not the default one that rails uses) and what Im using it for is for users to submit changes that will get voted on and might eventually get applied to the record. However, in the time it takes to be voted on something else might have changed and I dont want to overwrite the changes if they didnt submit a change.
EDIT: Changing to my more specific case so hopefully answers will work for it...
I have the following tables: Recipes, RecipeIngredients, RecipeSteps, RecipeChanges. On the show view of my recipes it displays all the ingredients/steps and there is a tab that then changes just the ingredients/steps to forms as to allow the user to submit changes. I dont want these changes applied though. Instead Im trying to create a voting system where people can vote on them. So what I have decided on is to convert the parameters from the form into a json string and save it in the RecipeChanges table under a single column (instead of using two table for ingredient changes and step changes). Heres the code for the form (html removed to make it easier to see the rails stuff):
<%= form_for #recipe, url: recipe_recipe_changes_path(#recipe), html: {method: "post"}, remote: true do |f| %>
<%= f.fields_for :recipe_ingredients, f.object.recipe_ingredients.order(:order) do |ff| %>
<%= ff.hidden_field :order, class: "position" %>
<%= ff.text_field :ingredient, placeholder: "Add Ingredient (e.g. 3 cups cooked rice)" %>
<label><%= ff.check_box :_destroy %>Remove</label>
<% end %>
<%= f.fields_for :recipe_steps do |ff| %>
<%= ff.hidden_field :order, class: "position"%>
<%= ff.text_area :step %>
<label><%= ff.check_box :_destroy %>Remove</label>
<% end %>
<%= submit_tag "Submit", class: "button" %>
<% end %>
So this sends a recipe object to my RecipeChange controller and there I handle the params to save them as the json string like so:
def create
#change = RecipeChange.new
#change.recipe_id = params[:recipe_id]
#change.new_recipe = recipe_change_params.to_json
#if #change.save
#add alert for successfully adding
#else
# add code for error handling
#end
end
This works like I want except for it saves all the ingredients/steps and I would like to only save what they have changed. I had two thoughts on how to do this but not sure how to accomplish it.
Check if the fields have changed when they click the submit button and only send the ones that have been edited (not sure if possible)
In the controller grab the original recipe (I have the id so that would be easy) and loop through the ingredients/steps and compare them and remove any that are identical....this is the method I think would be better but not sure how to loop through the hashes to accomplish this
Have a look at ActiveModel::Dirty. http://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-changed
You can do something like:
changes = bag.changed_attributes and get a hash of that attributes that changed, and then save those with bag.update_attributes(changes), for example.
This is a bit old now but I've come across the same or similar scenario and wanted to share for others.
In my case I populate some nested form fields based on an existing object in my #new action. However, in my #create action I did not want to save these nested form params unless they were actually modified compared to the original existing object.
In this case, ActiveModel::Dirty would always be true as it would compare [nil, "value"].
I first tried to modify the params in my #create action and compare them to the original existing object similar to this discussion but this got messy and felt wrong.
I ended up saving all records then doing a cleanup with an instance method in my model that I call after save in my controller's #create action. Still feels a bit dirty but it's working.
Example:
# controllers/changes_controller.rb
# ChangeController#create
def create
# ... shortened for example ...
if #instance.save
#instance.remove_clean_changes
format.html
end
end
# models/change.rb
# Change#remove_clean_changes
# Loop over all original objects and compare the necessary attributes
# to the changes. If they match, they are clean and should be deleted.
def remove_clean_changes
original_objects = self.original_objects
changes = self.changes
original_objects.each do |original_object|
changes.each do |change|
change.destroy if (change.attribute_one == original_object.attribute_one &&
change.original_object_id == original_object.id)
end
end
end
I want to write a condition in ruby to display default image if no user image is present , if present display the present image.
This is my index.html.slim page:
#product.reviews.each do |r|
a href="#"
= image_tag r.user.image_url :thumbnail`
I recommend you add the code for that in the helper or model, to keep the code clean.
helper method:
def user_avatar user
if user.image.present?
image_tag user.image_url :thumbnail
else
# Assuming you have a default.jpg in your assets folder
image_tag 'default.jpg'
end
end
view page:
#product.reviews.each do |r|
a href="#"
= user_avatar r.user
Also, from the code i feel like you are using a library like paperclip or carrierwave.
If so in paperclip you can set the default image by placing this code in the model.
:default_url => "/assets/:style/missing_avatar.jpg"
if you are using carrierwave place this method in your uploader file.
def default_url
"/images/fallback/default.png"
end
Assuming that you are using paperclip or something to save your images to the database, you can check if image exists by having
if r.user.image.present?
= image_tag r.user.image_url :thumbnail
else
= image_tag 'your default image path'
end
you should check your image gem to get find out whether this is valid as I don't know what kind of setup you have.
I managed to take care of this by using erb inside of the view used to display a user.
One nice thing about this approach is you won't need to save a default photo multiple times for multiple users. You can keep one inside of assets/images and just rely on it.
I was using Active Storage and the following gems to take care of uploading and resizing of photos:
gem 'image_processing', '1.9.3'
gem 'mini_magick', '4.9.5'
gem 'active_storage_validations', '0.8.9'
and wrote the following code inside of the necessary view:
<% if user.image.attached? %>
<%= image_tag user.image.variant(resize_to_limit: [200, 200]) %>
<% else %>
<%= image_tag("default.jpg", alt: "A stern frog", width: "200") %>
<% end %>
Just make sure you have a photo named default.jpg inside of assets/images
Im having a problem transferring an SQLlite Rails 3 app over to a Mongoid Rails 3 app. In the SQLlite version, I am easily able to include an image upload form (using Paperclip) from one model ('image') within a nested form from another model ('product'). Here's my 'new' product form:
<%= form_for #product, :html => {:multipart => true} do |f| %>
<% f.fields_for :images do |image_form| %>
<%= f.label :productphoto %>
<%= f.file_field :productphoto %><br />
<% end %>
<% end %>
And here's the 'show' view:
<% #product.images.each do |image| %>
<%= image_tag image.productphoto.url(:gallerythumb) %><br />
<% end %>
When I try to use the same product views in my Mongoid Rails 3 app (using Carrierwave), I get the following error:
TypeError in Stores#show:
can't convert nil into String
<%= image_tag product.image.url(:gallerythumb) %>
Im pretty sure my models in the Mongoid version are correct because if I add a string (like 'name') to my 'image' model and nest that in the 'Product' form, it works. Also, Im able to upload an image into a non-nested model form.
Any help would be greatly appreciated!
I just had a similar problem myself. The problem is not the image upload I think, but the problem is that Rails doesn't recognize :images as being an Array. If you look into the Rails source of the fields_for helper you see that it checks for a method "_attributes=". If that's not there the form will be posted as normal fields and not as an array (params will be "images" instead of "images[0]")
You have to add the following line to your model:
accepts_nested_attributes_for :images
It is carrierwave or mongoid bug
https://github.com/jnicklas/carrierwave/issues#issue/81
This is most likely the issue that Lewy linked to -- that problem is specific to arrangements where your Carrierwave uploader is mounted on a child document in an embedded association and you are saving the parent, and though you don't explicitly show if this is how your data is modeled, I suspect that's the case since you noted that it works with a non-nested form (presumably saving the child document then, not the parent).
If you dig around in the discussions linked from that issue, you'll find some proposed workarounds. Here's what I ended up with to get Carrierwave working in this situation for me:
https://gist.github.com/759788
Full credit is to due to zerobearing2 whose gist I forked, I just made minor changes to get it working in Rails 3.0.3 and commented on my gist with summary info on the relevant discussions.