I have a model with one paperclip attachment :image
Model:
class HomeScreen < ActiveRecord::Base
before_create { !HomeScreen.has_record? }
validates :image, :attachment_presence => true
attr_accessible :image
has_attached_file :image
def self.has_record?
if HomeScreen.last
true
else
false
end
end
end
show method of my conttroller should return image with relative path but json should return absolute url with domain, how can I do that?
Controller:
class HomeScreenController < ApplicationController
# GET /home_screen
def show
#home_screen = HomeScreen.last
respond_to do |format|
format.html
format.json { render json: #home_screen }
end
end
According to a github issue, you can do the following:
image_uri = URI.join(request.url, #home_screen.image.url)
This will be an URI object, which can be converted to a String with its .to_s
According to the same github issue, it is cleaner to use ActionController::Base.asset_host so it would result the helper:
def add_host_prefix(url)
URI.join(ActionController::Base.asset_host, url)
end
This supposes you have in every /config/environments/<environment>.rb file the following:
Appname::Application.configure do
# ....
config.action_controller.asset_host = 'http://localhost:3000' # Locally
# ....
end
Related
I'm trying to retrieve the URL of an active storage attachment as JSON response but i didn't quite get the correct respond here is my code:
#projet.rb
class Projet < ApplicationRecord
has_many :appartements, dependent: :destroy
has_one_attached :image_de_couverture
has_one_attached :situation
has_many_attached :images
end
#projet_serializer.rb
class ProjetSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
attributes :id, :nom, :gouvernorat, :localite, :surface, :description, :en_cours, :fini,
:situation, :images, :image_de_couverture
def images
object.images.map do |image|
rails_blob_path(image, only_path: true) if object.images.attached?
end
end
def image_de_couverture
polymorphic_url(object.image_de_couverture, only_path: true) if
object.image_de_couverture.attached?
end
def situation
polymorphic_url(object.situation, only_path: true) if object.situation.attached?
end
end
#projets_controller.rb
class ProjetsController < ApplicationController
# show all projects
def index
#projets = Projet.all
render json: #projets
end
def set_projet
#projet = Projet.find(params[:id])
end
def projet_params
params.require(:projet).permit(:nom, :gouvernorat, :localite, :surface, :description, :situation, images: [])
end
end
and here is the response i get with this code
[
{
id: 1,
nom: "Test",
gouvernorat: "Test",
localite: "Test",
surface: 30303,
description: "Test",
en_cours: true,
fini: true,
situation: "/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBFdz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--50589feef3ab662f5fb9877dbf4f0a21c79e2412/lame_rideau1.jpg",
images: [
"/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBEdz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--0550f31bd4e562c76dfd1aff9f0beac6483e1651/1.jpg",
"/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBFQT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--11ec06652e38fc43b54ef93187905f4591115d3c/2.jpg",
],
image_de_couverture: null
},
]
instead of
"/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBEdz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--0550f31bd4e562c76dfd1aff9f0beac6483e1651/1.jpg"
i want to the link to the attachment.
Thanks in advance.
Try changing the rails_blob_path helper by rails_blob_url in your ProjetSerializer#images. You also need to set the host config in your application file. source
# config/application.rb
class Application < Rails::Application
...
routes.default_url_options = { host: 'localhost:3000' }
end
def images
object.images.map do |image|
Rails.application.routes.url_helpers.rails_blob_url(image, only_path: true) if object.images.attached?
end
end
How can I download an internet image from a URL and save it in locally in Rails?
i give a form to add image url form link if i submit that form i want to download image to computer local disk (Home/Documents/food_img)
pls need help
in downloads controller
class DownloadsController < ApplicationController
def index
#downloads = Download.all
#download = Download.new
end
def create
#download = Download.create(user_params)
end
private
def user_params
params.require(:download).permit(:image,:image_url)
end
end
in view/index.html.erb
<%= form_for(#download, url: downloads_path) do |f| %>
<%= f.text_field :image_url %>
<%=f.submit%>
<% end %>
in model
class Download < ApplicationRecord
has_attached_file :image, styles: { medium: "300x300>", thumb: "100x100>" }, default_url: "/images/:style/missing.png"
validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/
require 'open-uri'
download = open('http://www.shopprod.com/assets/photos/ishop-718755d2bc62956994c867681b2622e77b4c9af3d1ecd6fa856127b704a459b2.png')
IO.copy_stream(download, "~/#{download.base_uri.to_s.split('/')[-1]}")
end
in db
t.string: image_url
If you are using ActiveStorage (which I would recommend) you can use .attach to attach an IO stream to a record.
given:
class Download < ApplicationRecord
has_one_attached :image
end
From the rails console you can download a file and attach it with:
require 'open-uri'
uri = 'http://www.shopprod.com/assets/photos/ishop-718755d2bc62956994c867681b2622e77b4c9af3d1ecd6fa856127b704a459b2.png'
download = Download.new
download.image.attach(io: open(uri), filename: uri.split('/').last)
However you can't just toss it whilly-nilly into your model class and expect it to work. The class body is executed when the class is read.
Instead you need to put it into a method and call it at the correct point.
class Download < ApplicationRecord
has_one_attached :image
def download_image!
require 'open-uri'
image.attach(
io: open(self.image_url),
filename: self.image_url.split('/').last
)
end
end
class DownloadsController < ApplicationController
def create
#download = Download.new(download_params)
if !#download.image.attached? && #download.image_url
#download.download_image!
# #todo handle errors
end
if #download.save
redirect_to #download
else
render :new
end
end
def download_params
params.require(:download)
.permit(:image, :image_url)
end
end
I want to get list of records with attached images as a links or files by api.
I have a simple model:
class Category < ApplicationRecord
has_one_attached :image
validates :name, presence: true, uniqueness: true
end
And next action:
def index
#categories = Category.all.with_attached_image
render json: #categories.to_json(include: { image_attachment: { include: :blob } })
end
That's the only way I can get image object.
And I see next results:
{"id":4,"name":"Cat1","description":""},
{"id":1,"name":"Cat2","description":"","image_attachment":
{"id":8,"name":"image","record_type":"Category","record_id":1,"blob_id":8,"created_at":"2018-06-09T13:45:40.512Z","blob":
{"id":8,"key":"3upLhH4vGxZEhhf3TaAjDiCW","filename":"Screen Shot 2018-06-09 at 20.43.24.png","content_type":"image/png","metadata":
{"identified":true,"width":424,"height":361,"analyzed":true},"byte_size":337347,"checksum":"Y58zbYUVOlZRadx81wxOJA==","created_at":"2018-06-09T13:45:40.482Z"}}},
...
I can see filename here. But files lives in different folders and it doesn't seems for me like a convenient way to get and link to the file.
I couldn't find any information about this.
Updated
Accordin to iGian solution my code become:
def index
#categories = Category.all.with_attached_image
render json: #categories.map { |category|
category.as_json.merge({ image: url_for(category.image) })
}
end
For my User which has_one_attached :avatar I can get the url in my views with <%= image_tag url_for(user.avatar) %>.
So, in controller I would use just url_for(user.avatar)
For Category which has_one_attached :image:
url_for(category.image)
Also try #object.image.service_url. This will give you the url where the image is saved. I.E. url to amazon s3 storage.
Please follow this for fetching images( in case if you are using has_many_attached)
Model.images.map{|img| ({ image: url_for(img) })}
This works for me with multiple images
class PostSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
attributes :id, :content , :images
def images
images = object.images.map do |image|
rails_blob_path(image , only_path: true) if object.images.attached?
end
end
end
I got it to work with rails_blob_url(#object.image). Notice I am calling _url not _path with the helper.
If you want get this url in front end, try this :
<%= url_for(category.image) %>
and for displaying image :
<%= image_tag url_for(category.image) %>
Here is how I use active storage with AWS S3 bucket and attach image urls with domain to JSON response:
activerecord
class Imageable < ApplicationRecord
has_many_attached :images
def image_urls
images.map(&:service_url)
end
def attributes
super.merge({
image_urls: image_urls
})
end
end
application_controller.rb
class ApplicationController < ActionController::Base
before_action :set_active_storage_current_host
def set_active_storage_current_host
ActiveStorage::Current.host = request.base_url
end
end
imageables_controller.rb
class ImageablesController < ApplicationController
include ImageablesHelper
def update
imageable = find_or_create
imageable.update imageables_params
imageable.images.purge
imageable.images.attach imageable_params[:images]
imageable.save!
render json: imageable
end
end
imageables_helper.rb
module ImageablesHelper
def imageables_params
params.require(:imageable).permit(:other_attributes, images: [])
end
end
I have the model that uses paperclip like this
has_attached_file :image, styles: { :medium => "50x50>" }
validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/
def image_url
image.url(:medium)
end
I need it Json, So in my controller,
respond_to do |format|
format.json { render json: #celebrity.to_json(:methods => [:image_url])}
end
And the result is
"image_url":"/system/celebrities/images/000/000/003/medium/Designed___developed_by_wd_ApS.png?1430926991"
but, I need to include the domain name, localhost:3000 ,
So what I have to do here
Try this.
Create module:
module WithDomain
def domain
#domain
end
def domain=(val)
#domain = val
end
def domain?
#domain.present?
end
end
Change you model accordingly:
class Celebtiry < ActiveRecord::Base
include WithDomain
# existing staff
def image_url
if domain?
URI.join(domain, image.url(:medium)).to_s
else
image.url(:medium)
end
end
end
and in your controller:
respond_to do |format|
format.json {
#celebrity.domain = request.base_url
render json: #celebrity.to_json(:methods => [:image_url])
}
end
Solution 1: (with existing code)
You can use asset_url from ActionView::Helpers::AssetUrlHelper module which will give you the absolute url of your image. Just include ActionView::Helpers::AssetUrlHelper this in your model so that asset_url becomes available inside your model.
So, your method inside the model would be:
include ActionView::Helpers::AssetUrlHelper
def image_url
asset_url(image.url(:medium))
end
This is the easiest solution for you with your current code.
Solution 2: (inside the controller)
In your controller request is available, so you can do:
URI.join(request.url, #celebrity.image.url(:medium))
which will give you the absolute url of the image. This will be an URI object, which can be converted to a String with .to_s method.
Here is the issue from paperclip from where this solution is derived. Hope this helps.
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)