I want to store an image in my Rails app, hand out the direct URL for it and capture the requests for it.
I have a created a controller/action images_controller#show, a layout file with only yield and a view file with one single image_tag.
The rendered direct link to the image: https://example.com/assets/a-3883fc3a19a59c2cac8e30c827e81ec6f67538dd0213393d08a425bc800a06a3.jpg
How would I capture requests for this image as they don't pass the controller? My server logs only shows
Started GET "/assets/a-3883fc3a19a59c2cac8e30c827e81ec6f67538dd0213393d08a425bc800a06a3.jpg"
Since the "static" assets are placed in the web root of the web server they are served without Rails intervening in production.
In development config.serve_static_assets = true is used to get rails to serve assets so that the are compiled on demand. However that would be prohibitively slow in production.
There are two basic alternatives here and which to use depends on your use case:
1. Do it on the web server:
This is mostly applicable if uploading the images is a developer concern and you want metrics for technical or marketing reasons.
Both NGinx and Apache can generate metrics for the number of unique visits on a URI. Exactly how to do this depends on your web server / sass provider and the rest of infrastructure in place.
If you decide to go down this route I recommend you start a new question with the details of your setup.
https://www.digitalocean.com/community/tutorials/how-to-target-your-users-with-nginx-analytics-and-a-b-testing
https://devcenter.heroku.com/articles/shogun
2. Handle images as any other resource
If you intend have users upload images you and want tracking or any form of access control you would have the user upload images into a non public directory and serve them through a run of the mill rails controller.
Gems like paperclip or carrierwave can make this far easier.
# config/routes.rb
resources :images
# app/models/image.rb
class Image < ActiveRecord::Base
belongs_to :user
validates_presence_of :file
end
# app/controllers/images_controller.rb
class ImagesController < ApplicationController
def new
#image = Image.new
end
def create
#image = Image.new(image_params)
#image.save
respond_with(#image)
end
def index
#images = Image.all
end
def show
#images = Image.find(params[:id])
respond_to do |f|
format.html {}
format.jpg { send_file #image.file , type: 'image/jpeg', disposition: 'inline' }
end
end
# ...
end
Related
My RoR app is on Heroku and active storage is configured correctly. However, when I'm in the app and fetch the uploaded document, the url is something like https://cremers.s3.eu-west-1.amazonaws.com/cx9xy0pmbieagvuw8a0vzcnfhvcc?response-content-disposition=inline%3B filename%3D"Digeste_9.1.pdf"%3B....
How to change this to a "normal" url, like https://www.cremers.fr/documents/digest_9.1.pdf ?
You can use a route/controller to act as a sort of proxy. I've done exactly this, below is roughly the code I used, edited for your specifics.
I did not test this with your settings, obviously, and I wasn't using ActiveStorage in my case, so you might need/want to adjust, but this should get you started:
# config/routes.rb
Rails.application.routes.draw do
get '/documents/:filename.:format.:compression', to: 'documents#show'
end
# app/controllers/documents_controller.rb
require 'open-uri'
class DocumentsController < ApplicationController
def show
bucket_name = 'cremers'
aws_region = 'eu-west-1'
filename = params[:filename]
s3_url = "https://s3-#{aws_region}.amazonaws.com/#{bucket_name}/#{filename}"
data = open(s3_url)
send_data data.read, type: data.content_type
end
end
How can I get url of my has_one model attachment stored in active storage in my rails controller. So, that I would be able to send it as full link as api in json.
So far, I have tried following methods but each of them are giving various issues:
current_user.image.service_url ---- undefined method `service_url' for #<ActiveStorage::Attached::One:0x....
Rails.application.routes.url_helpers.rails_disk_blob_path(current_user.image, only_path: true), it gives me an output like:
"/rails/blobs/%23%3CActiveStorage::Attached::One:0x007f991c7b41b8%3E"
but this is not a url, right? I am not able to hit and get image on browser.
url_for ----
undefined method `active_storage_attachment_url' for #<Api::V1::UsersController:0x007f991c1eaa98
Use the method rails_blob_path for attachements in a controller and models
For example, if you need to assign a variable (e.g. cover_url) in a controller, first you should include url_helpers and after use method rails_blob_path with some parameters. You can do the same in any model, worker etc.
Complete example below:
class ApplicationController < ActionController::Base
include Rails.application.routes.url_helpers
def index
#event = Event.first
cover_url = rails_blob_path(#event.cover, disposition: "attachment", only_path: true)
end
end
Sometimes, e.g. an API needs to return the full url with host / protocol for the clients (e.g. mobile phones etc.). In this case, passing the host parameter to all of the rails_blob_url calls is repetitive and not DRY. Even, you might need different settings in dev/test/prod to make it work.
If you are using ActionMailer and have already configuring that host/protocol in the environments/*.rb you can reuse the setting with rails_blob_url or rails_representation_url.
# in your config/environments/*.rb you might be already configuring ActionMailer
config.action_mailer.default_url_options = { host: 'www.my-site.com', protocol: 'https' }
I would recommend just calling the full Rails.application.url_helpers.rails_blob_url instead of dumping at least 50 methods into your model class (depending on your routes.rb), when you only need 2.
class MyModel < ApplicationModel
has_one_attached :logo
# linking to a variant full url
def logo_medium_variant_url
variant = logo.variant(resize: "1600x200>")
Rails.application.routes.url_helpers.rails_representation_url(
variant,
Rails.application.config.action_mailer.default_url_options
)
end
# linking to a original blob full url
def logo_blob_url
Rails.application.routes.url_helpers.rails_blob_url(
logo.blob,
Rails.application.config.action_mailer.default_url_options
)
end
end
I didn't have used rails active storage but what i have read in documentation this might help you
Try rails_blob_url(model.image)
For more http://edgeguides.rubyonrails.org/active_storage_overview.html
I was able to view the image in the browser using the following:
<%= link_to image_tag(upload.variant(resize: "100x100")), upload %>
Where upload is an attached image.
As a beginner to rails, I'm finding the generation of sitemaps on Heroku to be extremely daunting due to its read-only limitations. However, a sitemap is fundamental to my website as its success is based on SEO.
I have tried dynamic_sitemaps gem however soon removed it as I realised it had no documentation for heroku use. I then used the sitemap_generator gem which had coverage of heroku integration using several gems and external platforms such as Amazon S3. The problem however is that as a beginner I'm running into issues and finding it hard to get past them.
Is there a solution I can use easily generate sitemaps for consistent content such as blog posts on the heroku platform? I really want to get up and running and feel this could take a while to configure if I have to use the methods I've already attempted.
Thanks!
I figured out a small trick that makes it possible to dynamically generate the sitemap file but persist it for later calls on Heroku.
It works great for small\medium size projects, if you have a big\huge project and thousand of pages that changes endlessly , please consider using S3 to store the sitemap file.
Those are the steps:
use the sitemap_generator gem as instructed here https://github.com/kjvarga/sitemap_generator
after bundle, run rake sitemap:install, it will create a config/sitemap.rb file for you
edit the config/sitemap.rb file to look like this
SitemapGenerator::Sitemap.default_host = [your host name goes here]
SitemapGenerator::Sitemap.public_path = File.join(Rails.root, 'tmp').to_s
SitemapGenerator::Sitemap.compress = false
SitemapGenerator::Sitemap.create do
[all your site pages add commands goes here]
end
regarding the host name, I suggest it to be "#{ENV['HOST_PROTOCOL']}://#{ENV['HOST_NAME']}" (and of course add the appropriate environment variables) so you could change it on different environments.
regarding compress, start with false, make sure all is working great for you and change it later if it is a big file.
create your sitemap controller file - app/controllers/sitemap_controller.rb
Edit the sitemap controller file to look like this
require 'rake'
class SitemapController < ApplicationController
def index
file_name = File.join(Rails.root, 'tmp', 'sitemap.xml').to_s
unless File.exist?(file_name)
Rails.application.load_tasks
Rake::Task['sitemap:refresh:no_ping'].invoke
end
# it's better to be safe than sorry
if File.exist?(file_name)
respond_to do |format|
format.xml { render file: file_name }
end
else
render file: 'public/404.html', status: :not_found, layout: false
end
end
end
Add the index action to your routes.rb file
resources :sitemap, only: %i[index], constraints: ->(req) { req.format == :xml }
restart/deploy you server and go to /sitemap.xml
Enjoy 😊
Have taken a look to Dynamic Site Maps Gem this is really simple to set up just read the read me in the Github for more features you can also look at Site Map Generator gem
Wish you the best of luck
try like this
Controller
class SitemapController < ApplicationController
layout nil
def index
#static_pages = [jobs_url, advertising_url, join_url]
#offers = Offer.all
respond_to do |format|
format.xml
end
end
end
View
xml.instruct!
xml.urlset(
) do
#static_pages.each do |page|
xml.url do
xml.loc "#{page}"
xml.changefreq("monthly")
end
end
#offers.each do |offer|
xml.url do
xml.loc
xml.changefreq("daily")
end
end
end
Route
get 'sitemap.xml', :to => 'sitemap#index', :defaults => { :format => 'xml' }
My DBs of production and development are somewhat in sync, so development can read images from production paths (S3).
The problem is when I delete, update or create records on development, it affects the S3 image.
I don't want this behavior to happen on development but it should happen on production.
Is there an option to turn paperclip into readonly mode? I still want to see the images from S3 (and not 404 images).
I saw the :preserve_files option which is good to protect delete. Is there an option to protect overwrite / disable upload?
Well, patchy, ugly and unsafe for future versions, but does the job for the meantime.
config/initializers/paperclip.rb
if Rails.env.development?
module Paperclip
class Attachment
def assign uploaded_file
end
def save
end
def clear(*)
end
def destroy
end
private
def post_process(*)
end
def post_process_styles(*)
end
def post_process_style(*)
end
def queue_some_for_delete(*)
end
def queue_all_for_delete
end
def after_flush_writes
end
end
end
end
Assuming you need to use production data in development, I think it would make a lot more sense to create a "User Policy" where a user can only read certain S3 resources. Then change your environment variables accordingly
https://docs.aws.amazon.com/AmazonS3/latest/userguide/example-policies-s3.html
Then, you can handle errors in development (S3 client should fail if you try to update with read only privileges). This ensures you can't touch anything in production
For example (pseudocode),
if Rails.env.development?
// do not update
else
Model.attachment.assign()
end
Is there a way to upload an app, but only have it accessible by me? Or perhaps by a specific set of IP's?
Reason being, we want to run a few private online tests before opening the app up to the general public. So far I have come up with the following code:
class ApplicationController < ActionController::Base
before_filter :restrict_access
def restrict_access
whitelist = ['127.0.0.123', '10.0.1.7', '10.0.1.8'].freeze
unless( whitelist.include? request.env['REMOTE_ADDR'] )
render :file => "#{Rails.public_path}/500.html", :status => :unauthorized
return
end
end
end
However, the above code still renders the main layout file (app/views/layouts/application.html.erb) which exposes the logo and footer. For un-authorised access we want to display a page that says something like "Ooops, we are still doing a few tests and will be public soon!". No logo of the site, no nothing. Just a simple message.
We are using devise as our authentication gem. We don't want to add authentication functionality just to restrict access for private beta testing. We want to do it by IP instead.
Is such a thing possible? Perhaps the code above just needs working on? Or is there a gem that we can use solely for this requirement?
If you're deploying on Apache or Nginx, this should be easy enough to configure in the relevant site config files. Doesn't need to be in the app itself, in that case.
I'm not totally sure what the issue is with your existing code. Are you saying that the filter seems to be ignored, or that the file renders within the layout? If it's the latter, specifying :layout => false as a render option should take care of that.
class ApplicationController < ActionController::Base
before_filter :check, :except=>:unauth
def unauth
render(:layout=>false)
end
private
def check
whitelist = ['127.0.0.123', '10.0.1.7', '10.0.1.8'].freeze
if !(whitelist.include? request.env['REMOTE_ADDR'])
redirect_to('/unauth')
return
end
end
end
untested but should do it, now you can place a plain error msg or whatever in unauth.rhtml
I will suggest of use some Rack middelware a.e. rack-rewrite
require 'rack-rewrite'
#in your environment
config.middleware.insert_before(Rack::Lock, Rack::Rewrite) do
r301 %r{.*}, "YOUR REDIRECTPAGE$&" ,:if => Proc.new {|rack_env|
#puts here your conditions basing on rack_env
}
end
NOT TESTED