In my Rails app, I enable users to upload images using Carrierwave and Amazon S3. I want to implement a feature that lets users edit existing images by rotating it 90 degrees.
I'm confused about where this code would go. Does it go in the image uploader file, or the image controller? And how is it called? I believe it should look something like this:
image = Image.find(params[:id])
image_obj = MiniMagick::Image.read(image.file)
image_obj.rotate(-90)
image_obj.write(image.file)
But I haven't been able to find examples to help me. If anyone can give me a pointer in the right direction, I would really appreciate it!
Edit
Thanks to deep for their thorough response! Here is what I ended up doing:
In my view:
# image.html.erb:
<%= link_to rotate_image_path(:id => image.id), :remote => true %>
In my controller:
# image_controller.rb:
def rotate
#image = Image.find(params[:id])
#image.rotated = true
#image.save
respond_to do |format|
format.js { render :nothing => true }
end
end
In my model:
# image.rb
attr_accessible :rotated
after_save :rotate_image, if: ->(obj){obj.rotated.present? && obj.rotated?}
def rotate_image
self.image_path.recreate_versions!
end
In my uploader:
# image_uploader.rb
process :rotate_img
def rotate_img
if model.rotated.present? && model.rotated?
manipulate! do |img|
img.rotate '-90'
img
end
end
end
The only real change I made was in the uploader, where I ran into errors trying to do a condition process. I put the conditional within the rotate_img method.
Here's my solution
First define a attribute accessor in your model and on update set it to true.
In your model
#image.rb
attr_accessor :rotate
In your controller
#images_controller.rb
def update
#image = Image.find(params[:id])
#image.rotate = true
#image.save
redirect_to root_path, :notice => "Bla bla bla"
end
Carrierwave provides a recreate_versions! method which will process and re-upload the image. In you case you can add a after_save callback that will trigger recreate_versions! method only if the rotate attribute is set to true.
In your model
#image.rb
after_save :rotate_image, if: ->(obj){ obj.rotate.present? and obj.rotate? }
def rotate_image
self.file.recreate_versions!
end
Now in your image uploader you can write the code to rotate a image.
#image_uploader.rb
.......
# It will replace the original image with rotated version
process :rotate_img, :if => model.rotate.present and model.rotate?
def rotate_img
manipulate! do |img|
img.rotate "90"
img #returns the manipulated image
end
end
If you don't want to replace the original image then all you have to do is to call process inside a version like
# Create different versions of your uploaded files:
version :rotated_img do
process :rotate_img
end
Related
Controller
Save the object
How to Use This Controller to insert image in any folder and image url store in database
def create_json
#user = User.new(userFirstName: params[:userFirstName], userLastName: params[:userLastName], userEmail: params[:userEmail], password: encrypted_password, userImage: params[:userImage])
if #user.save #if save succeeds, redirect to the index action
redirect_to(:action => 'show', id: User.last.id)
else
#if not succeeds, redirect to the index action
redirect_to(:action => 'new')
end end
User(Model) mount_uploader :userImage, AvatarUploader
UsersController -> #user = User.new(user_params)
if #user.save
redirect_to(:action => 'show', id: User.last.id)
else
render :json => data_hash2, :content_type => 'application/json'
end
class AvatarUploader < CarrierWave::Uploader::Base
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def extension_white_list
%w(jpg jpeg gif png)
end end
If You Using Web Service
<form action="http://ruby/controllername/create" lass="new_user" id="new_user" enctype="multipart/form-data" accept-charset="UTF-8" method="post">
View following method for uploading files to app/assets/images/people_profile_images folder
First upload Image like this:
#user =User.new(user_params)
#this will create user form paramaters given
uploaded_file = params[:user][:photo]
#this will add uploaded image or file in uplaoded_fie
if (uploaded_file)
File.open(Rails.root.join('app/assets', 'images/people_profile_images', uploaded_file.original_filename), 'wb') do |file|
file.write(uploaded_file.read)
#user.photo = "/assets/people_profile_images/"+uploaded_file.original_filename
end
else
#user.photo = "/assets/people_profile_images/default_avatar.png"
end
then save user
#user.save
I don't know of an approach that can cover both: image uploading and then saving the url on the database.
You can do one or the other:
1) Use a gem called paperclip that helps upload image files.
See this links:
https://github.com/thoughtbot/paperclip#ruby-and-rails
https://teamtreehouse.com/library/image-uploads-in-ruby-on-rails-41 (paperclip, carrierwave, and dragonfly)
I have used paperclip and it has worked well for me.
OR:
2) Include it as an additional field on your filename.html.erb for your image_url
One caveat, of using this though is that this can be changed and you have no control of the image that might show on your site. Thus, the image_url should be from a reputable CDN or at least from a reputable user uploading to your site.
Create a column url:string in your model. Add it as a hidden field in your form. Set its value to the same as your remote_url using .js
var urladdy = input.remoteurl.val()
input.url.val(urladdy)
This will upload image with carrier wave and also save its url as string.
I am using Carrierwave to upload an image from a remote url :
def save
uid = SecureRandom.uuid
storedir = "uploads/#{uid}"
image = Image.new
image.image.store_dir = storedir
image.remote_image_url = "a remote url here"
if image.save!
respond_to do |format|
format.js
format.json { render json: {:saved => "#{image.image_url}", :id => "#{image.id}"} }
end
end
end
the returned result is correct :
Object { saved="https://mybucket.s3-eu-west-1.amazonaws.com/uploads/58406672-c227-4cec-a846-04c443e70f33/thumb_yr169-ae2f3bc4-74f4-47ee-950c-2e06756525b9-v2.png", id="47"}
As you can see the image url is this :
https://mybucket.s3-eu-west-1.amazonaws.com/uploads/58406672-c227-4cec-a846-04c443e70f33/thumb_yr169-ae2f3bc4-74f4-47ee-950c-2e06756525b9-v2.png
and when I go to check the file, it's there in the right folder, but when I go to the Rails Console and look for that record, the result of image_url is wrong :
> Rails console
> Image.find(47).image_url
> https://mybucket.s3-eu-west-1.amazonaws.com/uploads/thumb_yr169-ae2f3bc4-74f4-47ee-950c-2e06756525b9-v2.png
The uid after /uploads/ is gone!!
any help please!!
UPDATE
Looks like carrierwave is go check store_dir in the uploader to show the image url, in my case I override it before saving the image, and I would like to use that custom store_dir
Yeah, that is because CarrierWave, like most similar libraries try to build your url dynamically.
Add this to your ImageUploader
#ImageUploader
def uid
'58406672-c227-4cec-a846-04c443e70f33'
end
def store_dir
"uploads/#{uid}"
end
#XController
def save
image = Image.new
image.remote_image_url = "a remote url here"
if image.save!
respond_to do |format|
format.js
format.json { render json: {:saved => "#{image.image_url}", :id => "#{image.id}"} }
end
end
end
UPDATE
Not totally sure of why you'd want to overide the store, but in order to do that, I think you may be better served by storing the additional details on the images table as such:
Add a column to your Image model, which may be the store_uid
In your ImageUploader, change your store_dir to use your builder or the field name, e.g if the column on Image is store_uid, with value: whatever-uid-has-been-stored-here, you could do:
def store_dir
# /uploads/whatever-uid-has-been-stored-here
"uploads/#{model.store_uid}"
end
A cleaner approach and one that's often advised is to create another uploader for each kind of images, for consistency within your application
So I'm working with carrier wave direct, and I'm at the point where I need to update the "key" attribute on the uploader object. Here's the relevant line on the docs:
After uploading to S3, You'll need to update the uploader object with the returned key in the controller action that corresponds to new_user_url:
#uploader.update_attribute :key, params[:key]
The problem is that my #uploader object doesn't have an update_attribute method. In fact, when I look at all of the methods on the #uploader object, I see methods like key() and key=(), but no update_attribute.
Any idea what's going on? Did I miss some setup step that I need to perform to make the update_attribute method available?
I got it to work by calling update_attribute on the model and not the uploader. In the below snipped the #uploader the subclass of CarrierWave::Uploader::Base and #video is the model.
def upload
#uploader = Video.new.asset
#uploader.success_action_redirect = videos_upload_successful_url
end
def upload_successful
#video = Video.new
#video.update_attribute :key, params[:key] # different than documentation!!
#video.save
end
This seems to be contrary to the documentation where it is documented they way you tried it.
I had an same issue, but I solved the problem to add new column.
I think you need to add another column like avatar_image_url aside from avatar which is used as mount_uploader :avatar, AvatarUploader.
And the controller is something like this:
class ProfilesController < ApplicationController
before_action :setup_context
def edit
#uploader = #profile.avatar
#uploader.success_action_status = '201'
end
def update
if #profile.update_attributes(profile_params)
redirect_to home_path
else
render :edit
end
end
private
def setup_context
#profile = current_user
end
def profile_params
params.require(:user).permit(:name, :avatar_image_url)
end
end
When you post the following parameters, you can save avatar_image_url.
Parameters: {"utf8"=>"✓", "user"=>{"avatar_image_url"=>"https://my-development.s3.amazonaws.com/uploads/220f5378-1e0f-4823-9527-3d1170089a49/foo.gif", "name"=>"Schneems"}}
You can refer the image like this.
<%= image_tag #user.avatar_image_url %>
And also you can check this out.
Direct to S3 Image Uploads in Rails | Heroku Dev Center
I am using tinymce-rails-imageupload plugin with dragonfly.
When the image is uploaded via separate form in popup window, it behaves as expected (save image in datastore).
But when the user drag-drop or paste image into TinyMCE, the imageupload plugin allows it. I tried to find a way to disable this behavior, but apparently there is no straightforward way to disable allowing image upload, while disallowing the past/drag-drop behavior. So I gave up on that..
Now, I'm trying to save BASE64 image in TinyMCE's content.
In controller:
def store_file
#image = Resource.new :res_image => params[:file]
#image.save
render json: {
image: {
url: #image.res_image.remote_url
}
}, content_type: "text/html"
end
def create
#entry = Entry.new(params[:entry])
# iterate through tinyMCE field params[:entry][:message]
# if image tag is found
# if value of src tag starts with "data:"
# then replace it with the output of
# Resource.create_image_from_base64(extracted_base64_value)
# end if
# end if
# end iteration
begin
#entry.save!
flash[:success] = "Entry was successfully created."
redirect_to entries_path
rescue Mongoid::Errors::Validations => e
render :action => "new"
end
end
In Resource model, I would have something like:
image_accessor :res_image
field :res_image_uid, type: String
field :res_image_name, type: String
def create_image_from_base64(base_64_encoded_data)
file = File.open('temp.png', 'wb') do|f|
f.write(Base64.decode64(base_64_encoded_data))
end
resource = # create Resource with temp file
file.close
resource.res_image.remote_url
end
Questions:
How to create "Entry with file"?
Is there a better approach for handling pasted/dragged-droped base64 images in TinyMCE with dragonfly?
Even if it is an old question:
look at this: https://groups.google.com/forum/#!topic/dragonfly-users/xNWIwZf5-_Y
I'm writing some image upload code for Ruby on Rails with Paperclip, and I've got a working solution but it's very hacky so I'd really appreciate advice on how to better implement it. I have an 'Asset' class containing information about the uploaded images including the Paperclip attachment, and a 'Generator' class that encapsulates sizing information. Each 'Project' has multiple assets and generators; all Assets should be resized according to the sizes specified by each generator; each Project therefore has a certain set of sizes that all of its assets should have.
Generator model:
class Generator < ActiveRecord::Base
attr_accessible :height, :width
belongs_to :project
def sym
"#{self.width}x#{self.height}".to_sym
end
end
Asset model:
class Asset < ActiveRecord::Base
attr_accessible :filename,
:image # etc.
attr_accessor :generators
has_attached_file :image,
:styles => lambda { |a| a.instance.styles }
belongs_to :project
# this is utterly horrendous
def styles
s = {}
if #generators == nil
#generators = self.project.generators
end
#generators.each do |g|
s[g.sym] = "#{g.width}x#{g.height}"
end
s
end
end
Asset controller create method:
def create
#project = Project.find(params[:project_id])
#asset = Asset.new
#asset.generators = #project.generators
#asset.update_attributes(params[:asset])
#asset.project = #project
#asset.uploaded_by = current_user
respond_to do |format|
if #asset.save_(current_user)
#project.last_asset = #asset
#project.save
format.html { redirect_to project_asset_url(#asset.project, #asset), notice: 'Asset was successfully created.' }
format.json { render json: #asset, status: :created, location: #asset }
else
format.html { render action: "new" }
format.json { render json: #asset.errors, status: :unprocessable_entity }
end
end
end
The problem I am having is a chicken-egg issue: the newly created Asset doesn't know which generators (size specifications) to use until after it's been instantiated properly. I tried using #project.assets.build, but then the Paperclip code is still executed before the Asset gets its project association set and nils out on me.
The 'if #generators == nil' hack is so the update method will work without further hacking in the controller.
All in all it feels pretty bad. Can anyone suggest how to write this in a more sensible way, or even an approach to take for this kind of thing?
Thanks in advance! :)
I ran into the same Paperclip chicken/egg issue on a project trying to use dynamic styles based on the associated model with a polymorphic relationship. I've adapted my solution to your existing code. An explanation follows:
class Asset < ActiveRecord::Base
attr_accessible :image, :deferred_image
attr_writer :deferred_image
has_attached_file :image,
:styles => lambda { |a| a.instance.styles }
belongs_to :project
after_save :assign_deferred_image
def styles
project.generators.each_with_object({}) { |g, hsh| hsh[g.sym] = "#{g.width}x#{g.height}" }
end
private
def assign_deferred_image
if #deferred_image
self.image = #deferred_image
#deferred_image = nil
save!
end
end
end
Basically, to get around the issue of Paperclip trying to retrieve the dynamic styles before the project relation information has been propagated, you can assign all of the image attributes to a non-Paperclip attribute (in this instance, I have name it deferred_image). The after_save hook assigns the value of #deferred_image to self.image, which kicks off all the Paperclip jazz.
Your controller becomes:
# AssetsController
def create
#project = Project.find(params[:project_id])
#asset = #project.assets.build(params[:asset])
#asset.uploaded_by = current_user
respond_to do |format|
# all this is unrelated and can stay the same
end
end
And the view:
<%= form_for #asset do |f| %>
<%# other asset attributes %>
<%= f.label :deferred_upload %>
<%= f.file_field :deferred_upload %>
<%= f.submit %>
<% end %>
This solution also allows using accepts_nested_attributes for the assets relation in the Project model (which is currently how I'm using it - to upload assets as part of creating/editing a Project).
There are some downsides to this approach (ex. validating the Paperclip image in relation to the validity of the Asset instance gets tricky), but it's the best I could come up with short of monkey patching Paperclip to somehow defer execution of the style method until after the association information had been populated.
I'll be keeping an eye on this question to see if anyone has a better solution to this problem!
At the very least, if you choose to keep using your same solution, you can make the following stylistic improvement to your Asset#styles method:
def styles
(#generators || project.generators).each_with_object({}) { |g, hsh| hsh[g.sym] = "#{g.width}x#{g.height}" }
end
Does the exact same thing as your existing method, but more succinctly.
While I really like Cade's solution, just a suggestion. It seems like the 'styles' belong to a project...so why aren't you calculating the generators there?
For example:
class Asset < ActiveRecord::Base
attr_accessible :filename,
:image # etc.
attr_accessor :generators
has_attached_file :image,
:styles => lambda { |a| a.instance.project.styles }
end
class Project < ActiveRecord::Base
....
def styles
#generators ||= self.generators.inject {} do |hash, g|
hash[g.sym] = "#{g.width}x#{g.height}"
end
end
end
EDIT: Try changing your controller to (assuming the project has many assets):
def create
#project = Project.find(params[:project_id])
#asset = #project.assets.new
#asset.generators = #project.generators
#asset.update_attributes(params[:asset])
#asset.uploaded_by = current_user
end
I've just solved a similar problem that I had.
In my "styles" lambda I am returning a different style depending on the value of a "category" attribute. The problem though is that Image.new(attrs), and image.update_attributes(attrs) doesn't set the attributes in a predictable order, and thus I can't be guaranteed that image.category will have a value before my styles lambda is called. My solution was to override attributes=() in my Image model as follows:
class Image
...
has_attached_file :image, :styles => my_lambda, ...
...
def attributes=(new_attributes, guard_protected_attributes = true)
return unless new_attributes.is_a?(Hash)
if new_attributes.key?("image")
only_attached_file = {
"image" => new_attributes["image"]
}
without_attached_file = new_attributes
without_attached_file.delete("image")
# set the non-paperclip attributes first
super(without_attached_file, guard_protected_attributes)
# set the paperclip attribute(s) after
super(only_attached_file, guard_protected_attributes)
else
super(new_attributes, guard_protected_attributes)
end
end
...
end
This ensures that the paperclip attribute is set after the other attributes and can thus use them in a :style lambda.
It clearly won't help in situations where the paperclip attribute is "manually" set. However in those circumstances you can help yourself by specifying a sensible order. In my case I could write:
image = Image.new
image.category = "some category"
image.image = File.open("/somefile") # styles lambda can use the "category" attribute
image.save!
(Paperclip 2.7.4, rails 3, ruby 1.8.7)