Rails ActiveStorage File not found in sidekiq task - ruby-on-rails

I have image in my project with ActiveStorage.attach
if params[:avatar]
authorize! :update, current_user
type, base64_image = Utils::Attach.type_and_base64(params[:avatar], 'avatar')
current_user.avatar.attach(io: StringIO.new(base64_image), filename: "#{current_user.id}-#{Time.current.to_i}", content_type: type)
current_user.save!
end
And it actually work good.
But after I add sidekiq for mailer I started saw error
This error adds after attach avatar.

I don't found correct way to fix analyze job but monkey patching is work good for my case.
module ActiveStorage::Blob::Analyzable
def analyze_later
analyze
end
end

Related

Rails + ActiveStorage on S3: Set filename on download?

Is there a way to change/set the filename on download?
Example: Jon Smith uploaded his headshot, and the filename is 4321431-small.jpg. On download, I'd like to rename the file to jon_smith__headshot.jpg.
View:
<%= url_for user.headshot_file %>
This url_for downloads the file from Amazon S3, but with the original filename.
What are my options here?
The built-in controller serves blobs with their stored filenames. You can implement a custom controller that serves them with a different filename:
class HeadshotsController < ApplicationController
before_action :set_user
def show
redirect_to #user.headshot.service_url(filename: filename)
end
private
def set_user
#user = User.find(params[:user_id])
end
def filename
ActiveStorage::Filename.new("#{user.name.parameterize(separator: "_")}__headshot#{user.headshot.filename.extension_with_delimiter}")
end
end
Starting with 5.2.0 RC2, you won’t need to pass an ActiveStorage::Filename; you can pass a String filename instead.
I know this was already answered, but I would like to add a second way of doing this. You could update your file name when the user object is saved. Using OP's example of the user model and the headshot_file field, this is how you could solve this:
# app/models/user.rb
after_save :set_filename
def set_filename
file.blob.update(filename: "ANYTHING_YOU_WANT.#{file.filename.extension}") if file.attached?
end
The approach of #GuilPejon will work. The problem with directly calling the service_url is:
It is short-lived (not recommended by rails team)
It will not work if the service is disk in development mode.
The reason it does not work for disk service is that disk service requires ActiveStorage::Current.host to be present for generating the URL. And ActiveStorage::Current.host gets set in app/controllers/active_storage/base_controller.rb, so it will be missing when service_url gets called.
ActiveStorage as of now gives one more way of accessing the URL of the attachments:
Using rails_blob_(url|path) (recommended way)
But if you use this, you can only provide content-disposition and not the filename.
If you see the config/routes.rb in the `ActiveStorage repo you will find the below code.
get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob
direct :rails_blob do |blob, options|
route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
end
and when you look into blobs_controller you will find the below code:
def show
expires_in ActiveStorage::Blob.service.url_expires_in
redirect_to #blob.service_url(disposition: params[:disposition])
end
So it is clear that in rails_blob_(url|path) you can only pass disposition and nothing more.
I haven't played with ActiveStorage yet, so this is kind of a shot in the dark.
Looking at the ActiveStorage source for the S3 service, it looks like you can specify the filename and disposition for the upload. From the guides it seems that you can use rails_blob_path to access the raw URL of the upload and pass these parameters. Therefor you might try:
rails_blob_url(user.headshot_file, filename: "jon_smith__headshot.jpg")

Rewrite User-Agent to all Open URI Request

Using Rails 4.2.10
I would like to open image from URL thanks to mongoid papaerclip and open_uri
It perfectly works in 95% of use cases but some website send me a 404 when they see the user-agent of the request is Ruby.
The problem is with the lib paperclip=>
paperclip/io_adapters/uri_adapter.rb in download_content at line 48
def download_content
options = { read_timeout: Paperclip.options[:read_timeout] }.compact
open(#target, **options)
end
If I could add here an option it would be great but I don't think it's possible so I would like to add a default header with my user-agentto all request done by open_uri
Luckily for your use case there is no such thing as a class being closed against modification in ruby.
Add a patch to your rails app in an initializer. The structure is roughly as follows:
In config/initializers/some_arbitrary_name.rb
module UriAdapterPatch
def open(url, options)
# alter the objects however you want
super(altered_or_original_url, altered_or_original_options)
end
end
Paperclip::UriAdapter.prepend(UriAdapterPatch)
Solution for paperclip-3.5.4
module Paperclip
class UriAdapter < AbstractAdapter
def download_content
open(#target,"User-Agent" => "Your Custom User Agent")
end
end
end
# for example put it in config/initializers/paperclip_user_agent.rb
For other versions just write in project folder
gem which paperclip
and find in path from output file paperclip/io_adapters/uri_adapter.rb
There is function def download_content, it is your aim to rewrite

special-delivery gem rails configuration?

I can't get my EmailCallback::MailgunEmail < SpecialDelivery::Callback class to be included in execution. My problem is I'm not clear on where and how to create and call the new class mentioned in the readme. Currently when I fire of a POST request to the mount SpecialDelivery::Engine => "/email_events" endpoint with all correct params, it is processed by SpecialDelivery::EventsController#create. An object is instantiated but not saved.
For reference; the gem readme section: https://github.com/vigetlabs/special-delivery#your-custom-callback-class-file
I ran the undocumented rake task (special_delivery:install:migrations) and migrated to new migration file.
I currently have:
# /lib/email_callback/mailgun_email.rb
module EmailCallback
class MailgunEmail < SpecialDelivery::Callback
def opened
require 'pry'; binding.pry
end
def create
require 'pry'; binding.pry
end
end
end
Note: I'm trying to hit the pry debugger to confirm the request is hitting the appropriate method and to see what I have to work with.
Thanks in advance for any help.
The new class should be referenced in your invocation of the #special_delivery method that wraps your email sending.
def send_your_email(user)
special_delivery(
callback_class: EmailCallback::MailgunEmail, # Here!
callback_record: user
) do
mail(to: user.email, subject: 'Hello from Kit!')
end
end
Let me know if that works for you. If not we can dive in a bit deeper and get this sorted out for you.

How to avoid ActionMailer::Preview committing data to development database?

I'm using Rails 4.1.0.beta1's new Action Mailer previews and have the following code:
class EventInvitationPreview < ActionMailer::Preview
def invitation_email
invite = FactoryGirl.create :event_invitation, :for_match, :from_user, :to_user
EventInvitationMailer.invitation_email(invite)
end
end
This is all good until I actually try to preview my email and get an error saying that validation on a User object failed due to duplicate email addresses. Turns out that ActionMailer::Preview is writing to my development database.
While I could work around the validation failure or use fixtures instead of factories, is there any way to avoid ActionMailer::Preview writing to the development database, e.g. use the test database instead? Or am I just doing it wrong?
Cleaner/Easier (based on other answers) and tested with Rails 7: Do not change Rails' classes but create your own. Id addition to not change the controller but the call method of ActionMailer::Preview.
# app/mailers/preview_mailer.rb
class PreviewMailer < ActionMailer::Preview
def self.call(...)
message = nil
ActiveRecord::Base.transaction do
message = super(...)
raise ActiveRecord::Rollback
end
message
end
end
# inherit from `PreviewController` for your previews
class EventInvitationPreview < PreviewController
def invitation_email
...
end
end
OLD:
You can simply use a transaction around email previews, just put this inside your lib/monkey_mailers_controller.rb (and require it):
# lib/monkey_mailers_controller.rb
class Rails::MailersController
alias_method :preview_orig, :preview
def preview
ActiveRecord::Base.transaction do
preview_orig
raise ActiveRecord::Rollback
end
end
end
Then you can call .create etc. in your mailer previews but nothing will be saved to database. Works in Rails 4.2.3.
A cleaner way to proceed is to prepend a module overriding and wrapping preview into a transaction:
module RollbackingAfterPreview
def preview
ActiveRecord::Base.transaction do
super
raise ActiveRecord::Rollback
end
end
end
Rails.application.config.to_prepare do
class Rails::MailersController
prepend RollbackingAfterPreview
end
end
TL;DR -- The original author of the ActionMailer preview feature (via the MailView gem) provides three examples of different supported approaches:
Pull data from existing fixtures: Account.first
Factory-like pattern: user = User.create! followed by user.destroy
Stub-like: Struct.new(:email, :name).new('name#example.com', 'Jill Smith')
~ ~ ~ ~ ~ ~ ~ ~ ~ ~
To elaborate on the challenge faced by the OP...
Another manifestation of this challenge is attempting to use FactoryGirl.build (rather than create) to generate non-persistent data. This approach is suggested by one of the top Google results for "Rails 4.1" -- http://brewhouse.io/blog/2013/12/17/whats-new-in-rails-4-1.html?brewPubStart=1 -- in the "how to use this new feature" example. This approach seems reasonable, however if you're attempting to generate a url based on that data, it leads to an error along the lines of:
ActionController::UrlGenerationError in Rails::Mailers#preview
No route matches {:action=>"edit", :controller=>"password_resets", :format=>nil, :id=>nil} missing required keys: [:id]
Using FactoryGirl.create (rather than build) would solve this problem, but as the OP notes, leads to polluting the development database.
If you check out the docs for the original MailView gem which became this Rails 4.1 feature, the original author provides a bit more clarity about his intentions in this situation. Namely, the original author provides the following three examples, all focused on data reuse / cleanup / non-persistence, rather than providing a means of using a different database:
# app/mailers/mail_preview.rb or lib/mail_preview.rb
class MailPreview < MailView
# Pull data from existing fixtures
def invitation
account = Account.first
inviter, invitee = account.users[0, 2]
Notifier.invitation(inviter, invitee)
end
# Factory-like pattern
def welcome
user = User.create!
mail = Notifier.welcome(user)
user.destroy
mail
end
# Stub-like
def forgot_password
user = Struct.new(:email, :name).new('name#example.com', 'Jill Smith')
mail = UserMailer.forgot_password(user)
end
end
For Rails 6:
#Markus' answer worked for me, except that it caused a nasty deprecation-soon-will-be-real error related to how Autoloading has changed in Rails 6:
DEPRECATION WARNING: Initialization autoloaded the constants [many constants seemingly unrelated to what I actually did]
Being able to do this is deprecated. Autoloading during initialization is going to be an error condition in future versions of Rails.
[...]
Well, that's no good!
After more searching, this blog and the docs for
to_prepare helped me come up with this solution, which is just #Markus' answer wrapped in to_prepare. (And also it's in initializer/ instead of lib/.)
# /config/initializers/mailer_previews.rb
---
# Wrap previews in a transaction so they don't create objects.
Rails.application.config.to_prepare do
class Rails::MailersController
alias_method :preview_orig, :preview
def preview
ActiveRecord::Base.transaction do
preview_orig
raise ActiveRecord::Rollback
end
end
end
end
If you have a complicated object hierarchy, you can exploit transactional semantics to rollback the database state, as you would in a test environment (assuming your DB supports transactions). For example:
# spec/mailers/previews/price_change_preview.rb
class PriceChangeMailerPreview < ActionMailer::Preview
#transactional strategy
def price_decrease
User.transaction do
user = FactoryGirl.create(:user, :with_favorited_products) #creates a bunch of nested objects
mail = PriceChange.price_decrease(user, user.favorited_products.first)
raise ActiveRecord::Rollback, "Don't really want these objects committed to the db!"
end
mail
end
end
#spec/factories/user.rb
FactoryGirl.define do
factory :user do
...
trait :with_favorited_products do
after(:create) do |user|
user.favorited_products << create(:product)
user.save!
end
end
end
end
We can't use user.destroy with dependent: :destroy in this case because destroying the associated products normally doesn't make sense (if Amazon removes me as a customer, they don't remove all the products I have favorited from the market).
Note that transactions are supported by previous gem implementations of the preview functionality. Not sure why they aren't supported by ActionMailer::Preview.

How to make Paperclip read-only on development? (prevent upload/delete/overwrite)

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

Resources