How to get url of Active Storage image - ruby-on-rails

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

Related

how to retrieve an active storage attachment url as json in ruby

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

Rails API Image Upload with Active Storage / S3, React and ActiveAdmin

I have created an app using a Rails API with a React front-end and ActiveAdmin as the CMS back-end, which is based off of this tutorial. I am adding on a model which includes an image using ActiveStorage for file uploads, as well as S3 for storage on production.
app/models/photo.rb
class Photo < ApplicationRecord
has_one_attached :image
end
app/controllers/photos_controller.rb
class PhotosController < ApplicationController
include ActionController::Serialization
before_action :set_photo, only: [:show, :update, :destroy]
# GET /photos
def index
#photos = Photo.all
render json: #photos.to_json
end
...
def photo_params
params.require(:photo).permit(:image, :location_name, :region, :url)
end
end
app/serializers/photo_seralizer.rb
class PhotoSerializer < ActiveModel::Serializer
attributes :id, :image, :location_name, :region, :url
def image
rails_blob_path(object.image, only_path: true) if object.image.attached?
end
end
When I view the API endpoint, the attached image is not showing in the photo object, here is an example return. Is there something I'm missing in the Serializer, that isn't adding in the related image? Any help would be greatly appreciated, thanks!
[{
"id":1,
"location_name":"Acreage Ciderhouse",
"region":"Northern Metro",
"url":"https://example.com/acreage-ciderhouse/",
"created_at":"2020-01-09T15:18:33.298-07:00",
"updated_at":"2020-01-09T15:27:40.594-07:00"
}]
The activestorage saves data different, I think your index is calling data from your model table, but images are saved in relationship to other tables. So following is way to check if image is really saved use rails console to check.
rails c
#photo = Photo.find(1)
puts #photo.image.attached?
If you get true that image is saving properly now you can do following to add all image urls in your request
def index
#photos = Photo.all
#photos.each do |p|
if #photo.present?
image_url = { :image => (url_for p.image) }
p.attributes.merge(image_url)
end
end
render json: #photos.to_json
end
I did not test above code, just wrote, but I used it a lot for my apis.

Rails paperclip static files with absolute url

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

Ruby on Rails - Paperclip and dynamic parameters

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)

Uploading File into Server and store the path in database in Ruby on Rails

I am very new to ROR. I have a task to finish:
Here's the Model:
class File::DataImport < ActiveRecord::Base
attr_accessible :created_by, :file_name, :file_source, :updated_at, :updated_by
end
Here's the Controller:
class Files::DataImportsController < ApplicationController
def index
end
def new
end
end
And the views I have are index and new.
I want a field to upload data. The data should be stored in the server and save the filepath into the database in a specified column file_name. The path should be default to all uploading files.
I am stuck with how to start. Please help me to find the solution and I will learn from this.
Thanks in advance.
db/migrate/20110711000004_create_files.rb
class CreateFiles < ActiveRecord::Migration
def change
create_table :files do |t|
t.string :name
# If using MySQL, blobs default to 64k, so we have to give
# an explicit size to extend them
t.binary :data, :limit => 1.megabyte
end
end
end
app/controllers/upload_controller.rb
class UploadController < ApplicationController
def get
#file = File.new
end
end
app/views/upload/get.html.erb
<% form_for(:file,
url: {action: 'save'},
html: {multipart: true}) do |form| %>
Upload your file: <%= form.file_field("uploaded_file") %><br/>
<%= submit_tag("Upload file") %>
<% end %>
app/models/file.rb
class File < ActiveRecord::Base
def uploaded_file=(file_field)
self.name = base_part_of(file_field.original_filename)
self.data = file_field.read
end
def base_part_of(file_name)
File.basename(file_name).gsub(/[^\w._-]/, '')
end
end
app/controllers/upload_controller.rb
def save
#file = File.new(params[:file])
if #file.save
redirect_to(action: 'show', id: #file.id)
else
render(action: :get)
end
end
app/controllers/upload_controller.rb
def file
#file = File.find(params[:id])
send_data(#File.data,
filename: #File.name,
disposition: "inline")
end
app/controllers/upload_controller.rb
def show
#file = File.find(params[:id])
end
app/views/upload/show.html.erb
<h3><%= #file.name %></h3>
<img src="<%= url_for(:action => 'file', :id => #file.id) %>"/>
you should consider using one of the already available solutions like paperclip: https://github.com/thoughtbot/paperclip or carrierwave: https://github.com/jnicklas/carrierwave
Besides the Readmes there are also good tutorials out there:
http://railscasts.com/episodes/134-paperclip
http://railscasts.com/episodes/253-carrierwave-file-uploads
edit: As you want to implement it yourself I recommend examining the sources of the above on Github and try to understand what their code is doing. Also I would not bother implementing it myself, but if you have your reasons this might get you going..
You might want to look into a solution such as carrierwave.
The Github page provides a good explanation on how to use it, but this is also a nice guide.

Resources