I need to mount picture uploader after some verification function.
But if I call as usual mounting uploader in model:
mount_uploader :content, ContentUploader
carrierwave first download content, and then Rails start verification of model.
Specifically, I don't want to load big files at all! I want to check http header Content-length and Content-type and then, if it's OK, mount uploader.
Maybe something like that:
if condition
mount_uploader :content, ContentUploader
end
How can I do it?
P.S. Rails version 3.2.12
This is not the way to go if you just want avoid loading big files! That said, one can have conditional mount overriding content=.
As CarrierWave v1.1.0 there is still no conditional mount. But note that mount_uploader first includes a module in the class and then overrides the original content= to call the method content= defined in the included module. So, a workaround is just to redefine the accessors after you have called mount_uploader:
class YourModel < ActiveRecord::Base
mount_uploader :content, ContentUploader
def content=(arg)
if condition
super
else
# original behavior
write_attribute(:content, arg)
end
end
def content
# some logic here
end
end
Related
I have a Rails model with:
has_many_attached :files
When uploading via Active Storage by default if you upload new files it deletes all the existing uploads and replaces them with the new ones.
I have a controller hack from this which is less than desirable for many reasons:
What is the correct way to update images with has_many_attached in Rails 6
Is there a way to configure Active Storage to keep the existing ones?
Looks like there is a configuration that does exactly that
config.active_storage.replace_on_assign_to_many = false
Unfortunately it is deprecated according to current rails source code and it will be removed in Rails 7.1
config.active_storage.replace_on_assign_to_many is deprecated and will be removed in Rails 7.1. Make sure that your code works well with config.active_storage.replace_on_assign_to_many set to true before upgrading.
To append new attachables to the Active Storage association, prefer using attach.
Using association setter would result in purging the existing attached attachments and replacing them with new ones.
It looks like explicite usage of attach will be the only way forward.
So one way is to set everything in the controller:
def update
...
if model.update(model_params)
model.files.attach(params[:model][:files]) if params.dig(:model, :files).present?
else
...
end
end
If you don't like to have this code in controller. You can for example override default setter for the model eg like this:
class Model < ApplicationModel
has_many_attached :files
def files=(attachables)
files.attach(attachables)
end
end
Not sure if I'd suggest this solution. I'd prefer to add new method just for appending files:
class Model < ApplicationModel
has_many_attached :files
def append_files=(attachables)
files.attach(attachables)
end
end
and in your form use
<%= f.file_field :append_files %>
It might need also a reader in the model and probably a better name, but it should demonstrate the concept.
The solution suggested for overwriting the writer by #edariedl DOES NOT WORK because it causes a stack level too deep
1st solution
Based on ActiveStorage source code at this line
You can override the writer for the has_many_attached like so:
class Model < ApplicationModel
has_many_attached :files
def files=(attachables)
attachables = Array(attachables).compact_blank
if attachables.any?
attachment_changes["files"] =
ActiveStorage::Attached::Changes::CreateMany.new("files", self, files.blobs + attachables)
end
end
end
Refactor / 2nd solution
You can create a model concern that will encapsulate all this logic and make it a bit more dynamic, by allowing you to specify the has_many_attached fields for which you want the old behaviour, while still maintaining the new behaviour for newer has_many_attached fields, should you add any after you enable the new behaviour.
in app/models/concerns/append_to_has_many_attached.rb
module AppendToHasManyAttached
def self.[](fields)
Module.new do
extend ActiveSupport::Concern
fields = Array(fields).compact_blank # will always return an array ( worst case is an empty array)
fields.each do |field|
field = field.to_s # We need the string version
define_method :"#{field}=" do |attachables|
attachables = Array(attachables).compact_blank
if attachables.any?
attachment_changes[field] =
ActiveStorage::Attached::Changes::CreateMany.new(field, self, public_send(field).public_send(:blobs) + attachables)
end
end
end
end
end
end
and in your model :
class Model < ApplicationModel
include AppendToHasManyAttached['files'] # you can include it before or after, order does not matter, explanation below
has_many_attached :files
end
NOTE: It does not matter if you prepend or include the module because the methods generated by ActiveStorage are added inside this generated module which is called very early when you inherit from ActiveRecord::Base here
==> So your writer will always take precedence.
Alternative/Last solution:
If you want something even more dynamic and robust, you can still create a model concern, but instead you loop inside the attachment_reflections of your model like so :
reflection_names = Model.reflect_on_all_attachments.filter { _1.macro == :has_many_attached }.map { _1.name.to_s } # we filter to exclude `has_one_attached` fields
# => returns ['files']
reflection_names.each do |name|
define_method :"#{name}=" do |attachables|
# ....
end
end
However I believe for this to work, you need to include this module after all the calls to your has_many_attached otherwise it won't work because the reflections array won't be fully populated ( each call to has_many_attached appends to that array)
In my Rails 5 app I am using Carrierwave to upload images.
I have to model that uses the same uploader:
account.rb:
mount_uploader :logo, ImageUploader
image.rb:
mount_uploader :image, ImageUploader
This uploads the file to:
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
The strange this now is that I can use:
#account.logo&.url(:thumb) // works!
#account.logo&.image_url(:thumb) // error!
But on the image model (one product has many images):
#product.images.first&.image_url(:thumb) // works!
#product.images.first&.url(:thumb) // error!
So in the first case I have to use .url and in the second one .image_url
An I have no idea why...any help?
The instance method image_url is defined dynamically based on the column that is passed to mount_uploader and simply calls url on the column. The definition looks like this...
def #{column}_url(*args)
#{column}.url(*args)
end
So, I would suspect that logo_url would work on #account (although I have not tested this)
source
I have several different fields that store different phone numbers in my rails app as a string. I can use the built-in rails format validator with a regex expression. Is there a way to place this expression in a helper method for all my phone number validations instead of having to open each model file and paste the expression string.
You can require any file from application.rb:
# application.rb
require Rails.root.join "lib", "regexes.rb"
# lib/regexes.rb
PHONE_NUMBER_REGEX = /your regex/
Then you simply use the constant wherever needed
You can alternatively make use of the built in autoload functionality of Rails, for example with the concern approach the other commenter laid out - the concern file is autoloaded by default, as are models, controllers, etc
Loading custom files instead of using the Rails' defaults might not seem idiomatic or the "rails way". However, I do think it's important to understand that you can load any files you want. Some people autoload the entire lib/ folder and subfolders (see Auto-loading lib files in Rails 4)
Another alternative is to place your code somewhere in the config/initializers folder, these files are automatically loaded at startup and you can define shared classes/modules/constants there
Add a custom validator app/validators/phone_validator.rb
class PhoneValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value.to_s =~ /YOUR_REGEX_HERE/
record.errors.add attribute, 'must be a valid phone number'
end
end
end
Then in your models
class MyModel < ApplicationRecord
#phone: true tells it to use the PhoneValidator defined above
validates :phone_number, presence: true, phone: true
end
One way to do this is to create a concern that will be included in each model that has a phone number. In models/concerns, create a new file called something like phonable.rb.
# models/concerns/phonable.rb
module Phonable
extend ActiveSupport::Concern
VALID_PHONE_REGEX = /\A\+?\d+\z/ # Use your regex here
end
Now include the concern like this:
# models/my_model.rb
class MyModel < ApplicationRecord
include Phonable
validates :phone, format: {with: VALID_PHONE_REGEX}
...
end
Now that you have a Phonable concern, you can put any other phone-number-related logic here as well, such as parsing and the like. The advantage of this approach is that all your logic related to phone numbers will be available for use in the models that need it, and none of that logic will be available in models that don't need it.
You can put it in ApplicationRecord (application_record.rb)
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
VALID_PHONE_NUMBER_REGEX = /\d{10}/ # your regex here
end
And then you can use it in any model that inherits fro ApplicationRecord
I set paperclip to My "Document" model, with a fairly standard config. It works well, but I want to separate the addtitional styles file generation inside a background job (using Resque).
(I would like to stop the creation of the :orginal style to to assign a file manually, but it doesn't seem to be possible)
So, to extract styles inside a background job, I first stop paperclip styles processors.
Then I call the reprocess! inside an after_save callback to generate them.
Doing this put the update action inside an infinite loop, and it is exactly when I call the reprocess!
Here is my Document model (simplified for understanding purpose)
class Document < ActiveRecord::Base
has_attached_file :attachment
before_post_process :stop_process
after_save :process_styles #same result with after_update (since user can only add attachment when updating profile)
# Kill all paperclip styles
#still generate the :orginal style. Block that too to copy it manually from remote folder would be nice
def stop_process
false
end
#call for the generation of the additional styles (:medium, :thumb)
def process_styles
Profile.processDocumentJob(id)
end
#will be a background job
def self.processDocumentJob(id)
document = Document.find(id)
document.attachment.reprocess!
document.save(validate: false)
end
end
Despite my document.save(validate: false), the process loop during the updating.
I tried after_update callback, tweak my code and conditions, with no success.
Thank you in advance for your precious help
I know this is an old question, but happened to me today and solved this way....a bit hacky btw... I'm not proud of my solution. This works in Rails 5.
Added a virtual attribute to my model "skip_callbacks"
Added a method to check if the attribute is true "should_skip_callbacks?"
In my callback added "unless: :should_skip_callbacks?" condition
Before the paperclip reprocess, set the virtual attribute "skip_callbacks" to true
Here's a simplified version of my model:
class Organization < ApplicationRecord
attr_accessor :skip_callbacks
has_attached_file :logo, styles: { header: "380x160>" }
validates_attachment :logo, content_type: {content_type: ['image/png','image/jpg','image/gif']}
after_save :reprocess_logo, unless: :should_skip_callbacks?
def should_skip_callbacks?
self.skip_callbacks
end
def reprocess_logo
self.skip_callbacks = true
self.logo.reprocess!
end
end
first of all, i am using rails 3.1.3 and carrierwave from the master
branch of the github repo.
i use a after_init hook to determine fields based on an attribute of
the page model instance and define attribute accessors for these field
which store the values in a serialized hash (hope it's clear what i am
talking about). here is a stripped down version of what i am doing:
class Page < ActiveRecord::Base
serialize :fields, Hash
after_initialize :set_accessors
def set_accessors
case self.template
when 'standard'
class << self
define_method 'image' do
self.fields['image']
end
define_method 'image=' do |value|
self.fields['image'] = value
end
end
mount_uploader :image, PageImageUploader
end
end
end
end
leaving out the mount_uploader command gives me access to the
attribute as i want. but when i mount the uploader a get an error
message saying 'undefined method new for nil class'
i read in the source that there are the methods read_uploader and
write_uploader in the extensions module.
how do i have to override these to make the mount_uploader command
work with my 'virtual' attribute.
i hope somebody has an idea how i can solve this problem. thanks a lot
for your help.
best regard. dominik.
Same problem but solved in your model you should override read_uploader(column) and write_uploader(column, identifier) instance methods. I also have a problem with #{column}_will_change! and #{column}_changed? for a virtual column so I had to define them too:
class A < ActiveRecord::Base
serialize :meta, Hash
mount_uploader :image, ImageUploader
def image_will_change!
meta_will_change!
#image_changed = true
end
def image_changed?
#image_changed
end
def write_uploader(column, identifier)
self.meta[column.to_s] = identifier
end
def read_uploader(column)
self.meta[column.to_s]
end
end
Now there's also an add-on to carrierwave which provides the exact functionality as described by Antiarchitect:
https://github.com/timsly/carrierwave-serializable