Giter Club home page Giter Club logo

igorkasyanchuk / active_storage_validations Goto Github PK

View Code? Open in Web Editor NEW
989.0 13.0 129.0 2.53 MB

Do it like => validates :photos, attached: true, content_type: ['image/png', 'image/jpg', 'image/jpeg'], size: { less_than: 500.kilobytes }, limit: { min: 1, max: 3 }, aspect_ratio: :landscape, dimension: { width: { in: 800..1600 }

Home Page: https://www.railsjazz.com/

License: MIT License

Ruby 97.98% HTML 1.89% Shell 0.13%
activestorage validations rails active-storage

active_storage_validations's Introduction

Active Storage Validations

MiniTest RailsJazz https://www.patreon.com/igorkasyanchuk Listed on OpenSource-Heroes.com

If you are using active_storage gem and you want to add simple validations for it, like presence or content_type you need to write a custom validation method.

This gems doing it for you. Just use attached: true or content_type: 'image/png' validation.

What it can do

  • validates if file(s) attached
  • validates content type
  • validates size of files
  • validates dimension of images/videos
  • validates number of uploaded files (min/max required)
  • validates aspect ratio (if square, portrait, landscape, is_16_9, ...)
  • validates if file can be processed by MiniMagick or Vips
  • custom error messages
  • allow procs for dynamic determination of values

Usage

For example you have a model like this and you want to add validation.

class User < ApplicationRecord
  has_one_attached :avatar
  has_many_attached :photos
  has_one_attached :image

  validates :name, presence: true

  validates :avatar, attached: true, content_type: 'image/png',
                                     dimension: { width: 200, height: 200 }
  validates :photos, attached: true, content_type: ['image/png', 'image/jpeg'],
                                     dimension: { width: { min: 800, max: 2400 },
                                                  height: { min: 600, max: 1800 }, message: 'is not given between dimension' }
  validates :image, attached: true,
                    processable_image: true,
                    content_type: ['image/png', 'image/jpeg'],
                    aspect_ratio: :landscape
end

or

class Project < ApplicationRecord
  has_one_attached :logo
  has_one_attached :preview
  has_one_attached :attachment
  has_many_attached :documents

  validates :title, presence: true

  validates :logo, attached: true, size: { less_than: 100.megabytes , message: 'is too large' }
  validates :preview, attached: true, size: { between: 1.kilobyte..100.megabytes , message: 'is not given between size' }
  validates :attachment, attached: true, content_type: { in: 'application/pdf', message: 'is not a PDF' }
  validates :documents, limit: { min: 1, max: 3 }
end

More examples

  • Content type validation using symbols or regex.
class User < ApplicationRecord
  has_one_attached :avatar
  has_many_attached :photos

  validates :avatar, attached: true, content_type: :png
  # or
  validates :photos, attached: true, content_type: [:png, :jpg, :jpeg]
  # or
  validates :avatar, content_type: /\Aimage\/.*\z/
end

Please note that the symbol types must be registered by Marcel::EXTENSIONS that's used by this gem to infer the full content type. Example code for adding a new content type to Marcel:

# config/initializers/mime_types.rb
Marcel::MimeType.extend "application/ino", extensions: %w(ino), parents: "text/plain" # Registering arduino INO files
  • Dimension validation with width, height and in.
class User < ApplicationRecord
  has_one_attached :avatar
  has_many_attached :photos

  validates :avatar, dimension: { width: { in: 80..100 }, message: 'is not given between dimension' }
  validates :photos, dimension: { height: { in: 600..1800 } }
end
  • Dimension validation with min and max range for width and height:
class User < ApplicationRecord
  has_one_attached :avatar
  has_many_attached :photos

  validates :avatar, dimension: { min: 200..100 }
  # Equivalent to:
  # validates :avatar, dimension: { width: { min: 200 }, height: { min: 100  } }
  validates :photos, dimension: { min: 200..100, max: 400..200 }
  # Equivalent to:
  # validates :avatar, dimension: { width: { min: 200, max: 400 }, height: { min: 100, max: 200  } }
end
  • Aspect ratio validation:
class User < ApplicationRecord
  has_one_attached :avatar
  has_one_attached :photo
  has_many_attached :photos

  validates :avatar, aspect_ratio: :square
  validates :photo, aspect_ratio: :landscape

  # you can also pass dynamic aspect ratio, like :is_4_3, :is_16_9, etc
  validates :photos, aspect_ratio: :is_4_3
end
  • Proc Usage:

Procs can be used instead of values in all the above examples. They will be called on every validation.

class User < ApplicationRecord
  has_many_attached :proc_files

  validates :proc_files, limit: { max: -> (record) { record.admin? ? 100 : 10 } }
end

Internationalization (I18n)

Active Storage Validations uses I18n for error messages. For this, add these keys in your translation file:

en:
  errors:
    messages:
      content_type_invalid: "has an invalid content type"
      file_size_not_less_than: "file size must be less than %{max_size} (current size is %{file_size})"
      file_size_not_less_than_or_equal_to: "file size must be less than or equal to %{max_size} (current size is %{file_size})"
      file_size_not_greater_than: "file size must be greater than %{min_size} (current size is %{file_size})"
      file_size_not_greater_than_or_equal_to: "file size must be greater than or equal to %{min_size} (current size is %{file_size})"
      file_size_not_between: "file size must be between %{min_size} and %{max_size} (current size is %{file_size})"
      limit_out_of_range: "total number is out of range"
      image_metadata_missing: "is not a valid image"
      dimension_min_inclusion: "must be greater than or equal to %{width} x %{height} pixel."
      dimension_max_inclusion: "must be less than or equal to %{width} x %{height} pixel."
      dimension_width_inclusion: "width is not included between %{min} and %{max} pixel."
      dimension_height_inclusion: "height is not included between %{min} and %{max} pixel."
      dimension_width_greater_than_or_equal_to: "width must be greater than or equal to %{length} pixel."
      dimension_height_greater_than_or_equal_to: "height must be greater than or equal to %{length} pixel."
      dimension_width_less_than_or_equal_to: "width must be less than or equal to %{length} pixel."
      dimension_height_less_than_or_equal_to: "height must be less than or equal to %{length} pixel."
      dimension_width_equal_to: "width must be equal to %{length} pixel."
      dimension_height_equal_to: "height must be equal to %{length} pixel."
      aspect_ratio_not_square: "must be a square image"
      aspect_ratio_not_portrait: "must be a portrait image"
      aspect_ratio_not_landscape: "must be a landscape image"
      aspect_ratio_is_not: "must have an aspect ratio of %{aspect_ratio}"
      aspect_ratio_unknown: "has an unknown aspect ratio"
      image_not_processable: "is not a valid image"

In several cases, Active Storage Validations provides variables to help you customize messages:

Aspect ratio

The keys starting with aspect_ratio_ support two variables that you can use:

  • aspect_ratio containing the expected aspect ratio, especially useful for custom aspect ratio
  • filename containing the current file name

For example :

aspect_ratio_is_not: "must be a %{aspect_ratio} image"

Content type

The content_type_invalid key has three variables that you can use:

  • content_type containing the content type of the sent file
  • authorized_types containing the list of authorized content types
  • filename containing the current file name

For example :

content_type_invalid: "has an invalid content type : %{content_type}, authorized types are %{authorized_types}"

Dimension

The keys starting with dimension_ support six variables that you can use:

  • min containing the minimum width or height allowed
  • max containing the maximum width or height allowed
  • width containing the minimum or maximum width allowed
  • height containing the minimum or maximum width allowed
  • length containing the exact width or height allowed
  • filename containing the current file name

For example :

dimension_min_inclusion: "must be greater than or equal to %{width} x %{height} pixel."

File size

The keys starting with file_size_not_ support four variables that you can use:

  • file_size containing the current file size
  • min containing the minimum file size
  • max containing the maximum file size
  • filename containing the current file name

For example :

file_size_not_between: "file size must be between %{min_size} and %{max_size} (current size is %{file_size})"

Number of files

The limit_out_of_range key supports two variables that you can use:

  • min containing the minimum number of files
  • max containing the maximum number of files

For example :

limit_out_of_range: "total number is out of range. range: [%{min}, %{max}]"

Processable image

The image_not_processable key supports one variable that you can use:

  • filename containing the current file name

For example :

image_not_processable: "is not a valid image (file: %{filename})"

Installation

Add this line to your application's Gemfile:

# Rails 5.2 and Rails 6
gem 'active_storage_validations'

# Optional, to use :dimension validator or :aspect_ratio validator
gem 'mini_magick', '>= 4.9.5'
# Or
gem 'ruby-vips', '>= 2.1.0'

And then execute:

$ bundle

Sample

Very simple example of validation with file attached, content type check and custom error message.

Sample

Test matchers

Provides RSpec-compatible and Minitest-compatible matchers for testing the validators. Only aspect_ratio, attached, content_type, dimension and size validators currently have their matcher developed.

RSpec

In spec_helper.rb, you'll need to require the matchers:

require 'active_storage_validations/matchers'

And include the module:

RSpec.configure do |config|
  config.include ActiveStorageValidations::Matchers
end

Matcher methods available:

describe User do
  # aspect_ratio:
  # #allowing, #rejecting
  it { is_expected.to validate_aspect_ratio_of(:avatar).allowing(:square) }
  it { is_expected.to validate_aspect_ratio_of(:avatar).rejecting(:portrait) }

  # attached
  it { is_expected.to validate_attached_of(:avatar) }

  # content_type:
  # #allowing, #rejecting
  it { is_expected.to validate_content_type_of(:avatar).allowing('image/png', 'image/gif') }
  it { is_expected.to validate_content_type_of(:avatar).rejecting('text/plain', 'text/xml') }

  # dimension:
  # #width, #height, #width_min, #height_min, #width_max, #height_max, #width_between, #height_between
  it { is_expected.to validate_dimensions_of(:avatar).width(250) }
  it { is_expected.to validate_dimensions_of(:avatar).height(200) }
  it { is_expected.to validate_dimensions_of(:avatar).width_min(200) }
  it { is_expected.to validate_dimensions_of(:avatar).height_min(100) }
  it { is_expected.to validate_dimensions_of(:avatar).width_max(500) }
  it { is_expected.to validate_dimensions_of(:avatar).height_max(300) }
  it { is_expected.to validate_dimensions_of(:avatar).width_between(200..500) }
  it { is_expected.to validate_dimensions_of(:avatar).height_between(100..300) }

  # size:
  # #less_than, #less_than_or_equal_to, #greater_than, #greater_than_or_equal_to, #between
  it { is_expected.to validate_size_of(:avatar).less_than(50.kilobytes) }
  it { is_expected.to validate_size_of(:avatar).less_than_or_equal_to(50.kilobytes) }
  it { is_expected.to validate_size_of(:avatar).greater_than(1.kilobyte) }
  it { is_expected.to validate_size_of(:avatar).greater_than_or_equal_to(1.kilobyte) }
  it { is_expected.to validate_size_of(:avatar).between(100..500.kilobytes) }
end

(Note that matcher methods are chainable)

All matchers can currently be customized with Rails validation options:

describe User do
  # :allow_blank
  it { is_expected.to validate_attached_of(:avatar).allow_blank }

  # :on
  it { is_expected.to validate_attached_of(:avatar).on(:update) }
  it { is_expected.to validate_attached_of(:avatar).on(%i[update custom]) }

  # :message
  it { is_expected.to validate_dimensions_of(:avatar).width(250).with_message('Invalid dimensions.') }
end

Minitest

To use the matchers, make sure you have the shoulda-context gem up and running.

You need to require the matchers:

require 'active_storage_validations/matchers'

And extend the module:

class ActiveSupport::TestCase
  extend ActiveStorageValidations::Matchers
end

Then you can use the matchers with the syntax specified in the RSpec section, just use should validate_method instead of it { is_expected_to validate_method } as specified in the shoulda-context gem.

Todo

  • verify with remote storages (s3, etc)
  • verify how it works with direct upload
  • add more translations

Tests & Contributing

To run tests in root folder of gem:

  • BUNDLE_GEMFILE=gemfiles/rails_6_1_3_1.gemfile bundle exec rake test to run for Rails 6.1
  • BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle exec rake test to run for Rails 7.0
  • BUNDLE_GEMFILE=gemfiles/rails_7_1.gemfile bundle exec rake test to run for Rails 7.0
  • BUNDLE_GEMFILE=gemfiles/rails_next.gemfile bundle exec rake test to run for Rails main branch

Snippet to run in console:

BUNDLE_GEMFILE=gemfiles/rails_6_1_3_1.gemfile bundle
BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle
BUNDLE_GEMFILE=gemfiles/rails_7_1.gemfile bundle
BUNDLE_GEMFILE=gemfiles/rails_next.gemfile bundle
BUNDLE_GEMFILE=gemfiles/rails_6_1_3_1.gemfile bundle exec rake test
BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle exec rake test
BUNDLE_GEMFILE=gemfiles/rails_7_1.gemfile bundle exec rake test
BUNDLE_GEMFILE=gemfiles/rails_next.gemfile bundle exec rake test

Tips:

  • To focus a specific test, use the focus class method provided by minitest-focus
  • To focus a specific file, use the TEST option provided by minitest, e.g. to only run size_validator_test.rb file you will execute the following command: bundle exec rake test TEST=test/validators/size_validator_test.rb

Known issues

  • There is an issue in Rails which it possible to get if you have added a validation and generating for example an image preview of attachments. It can be fixed with this:
  <% if @user.avatar.attached? && @user.avatar.attachment.blob.present? && @user.avatar.attachment.blob.persisted? %>
    <%= image_tag @user.avatar %>
  <% end %>

This is a Rails issue, and is fixed in Rails 6.

Contributing

You are welcome to contribute.

Contributors (BIG THANK YOU)

License

The gem is available as open source under the terms of the MIT License.

active_storage_validations's People

Contributors

animesh-ghosh avatar arr-dev avatar connorshea avatar cseelus avatar dependabot[bot] avatar dolarsrg avatar gr8bit avatar igorkasyanchuk avatar ivanelrey avatar kukicola avatar kwent avatar luiseugenio avatar maycowmeira avatar mth0158 avatar mvz avatar narkoz avatar ocarreterom avatar ohbarye avatar phlegx avatar randsina avatar reckerswartz avatar rstammer avatar stefschenkelaars avatar sudoremo avatar tagliala avatar tleneveu avatar uysim avatar victorbueno avatar vietqhoang avatar yarmiganosca avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

active_storage_validations's Issues

ActiveStorage accepts broken images and throws exceptions when creating variants

TL;DR There is no way to detect broken image files. So exceptions are thrown later when ActiveStorage tries to render variants.

I originally submitted this issue to rails/rails#40493 but was directed here.

Steps to reproduce

Create a controller action that receives uploaded image files. A part from my Rails application:

def upload_receive
  params[:file].each do |img|
    new_screenshot = @package.screenshots.new(image: img)
    
    if new_screenshot.valid?  
      new_screenshot.save
    end
  end
end

My model:

class Screenshot < ApplicationRecord
  has_one_attached :image
  validates :image, attached: true, content_type: [:png]

  def medium_image
    if self.image.attached?
      self.image.variant(
        resize_to_limit: [670, 600]
      ).processed
    end
  end
end

(The validation uses the 'active_storage_validations' gem but the problem is most certainly not coming from there.)

This works for intact PNG files. A deliberately broken PNG file will be accepted and saved though. My test file is 5 KB of zero bytes that I named "broken.png" and uploaded to Rails.

Expected behavior

One ofโ€ฆ

  1. ActiveStorage detect broken image files by trying to open them with Minimagick/Imagemagick as a validation.
  2. ActiveStorage offers a common validation to detect broken images. (I tried to write my own custom validator to run the file through Minimagick but it I could not find a way to access the blob's data in the model before a ".save".)
  3. Creating variants does not just throw an exception.

Actual behavior

The file gets saved by ActiveStorage with content_type='image/png'. I suspect that 'marcel' and 'mimemagick' (which I believe are used for the content type detection) can't figure out what this file is and just look at the '.png' suffix.

Once a template tries to render a variant using the Screenshot.medium_image method it will call Minimagick and fail:

convert /tmp/ActiveStorage-21670-โ€ฆ failed with error:
convert-im6.q16: improper image header `/tmp/ActiveStorage-21670-โ€ฆ' @ error/png.c/ReadPNGImage/4092.
convert-im6.q16: no images defined `/tmp/image_processingโ€ฆ.png' @ error/convert.c/ConvertImageCommand/3258.

System configuration

Rails version: 6.0.3.4

Ruby version: 2.6.5p114

Default error message for file sizes

Very nice work, especially the limit on the number of attached files comes in handy for the app I'm working on.

Any chance default error messages for size validations could be added or generally for all validation types listed in the README under What it can do?

This would make the validates :attachment โ€ฆ code in the models much cleaner than having to manually add a (localized) error message.

Specs not recognising the on method (thus can't scope validations to update in specs)

Hi :) great work on the gem!

I have a validation that I only want to run on update. The validation does run on update as I expect, however the specs don't look like they recognise the on method. So basically can't use the specs to check for validation on update, only on create (default).

Screenshot 2020-10-01 at 13 26 02

Screenshot 2020-10-01 at 13 25 52

I'm surprised no one brought this up yet as I'd imagine this would be fairly common. Unless there's some work around for this? But I haven't spotted any?

If there's a workaround, could you please let me know. Otherwise, could this be added to specs :)

Thanks.

Allow Procs as values for validator restrictions

Hi there!

Sometimes it is necessary to have dynamic values for validations. I have seen in some other validator gems also procs can be passed instead of integers. These are then called in the validator methods. Then it is possible for example to use ENV variables to set the dimensions or sizes.

That would be another super addition to make this extension even more useful.

'Aspect Ratio' validation

Related to #24 and #25, I'd like to be able to validate the aspect ratio of an image. For example, I want user avatars to be square, so the height and width have to match. I don't really care what size the image is in exact pixels (so, e.g. dimensions: { width: 200, height: 200 } wouldn't work for me).

For me, a simple is_square validation would work, like so:

validates :image, aspect_ratio: { :is_square }

I imagine you could alternatively have something like this, if we want to support more than just square images:

# Height and width must match.
validates :image, aspect_ratio: { :square }
# Width must be greater than height.
validates :image, aspect_ratio: { :landscape }
# Height must be greater than width.
validates :image, aspect_ratio: { :portrait }

Or potentially even have exact ratios, e.g. 16:9 or 16:10 for desktop wallpapers.

Thoughts?

Presence validation fails to account for marked_for_destruction

Hey, thanks for your work on the gem!

Consider the following example code:

# In model.rb
has_one_attached :image
accepts_nested_attributes_for :image_attachment, allow_destroy: true

validates :image, presence: true

If the image is now set as marked_for_destruction, via the _destroy attribute, the image_attachment is deleted (along with the blob) on saving the model.. This is unexpected, I'm looking for the presence validation to be triggered before any operation is performed with the attachment.

This was a bug in ActiveRecord that has been fixed in Rails 4: rails/rails#6827

Validate total size of attachments in case of has_many relationship

This is a great gem, thanks a lot.
As far as I understand, it's not possible to validate the total file size (summation of each file size) in case we have a has_many relationship. For example, I have the following contraint:

  • upload some documents summing up to 10MB in size, each document with max 1MB each.

Currently I'm validating manually in the Model, but it'd be useful to have this in the gem as well.

I can try to implement it with some guidance.

Error AttachedValidator

I have the issue when I build the class inside the module, look at the image, It will work if I comment validation code. Can you help me resolve this issue?
Rails 6.0.2.1

image
image

Limit validator not working for rails 6

The limit validation doesn't work for rails 6 active storage

The change of implementation of ActiveStorage::Attached::Many#attach bellow cause duplicate attachables which break the limit validation.

    def attach(*attachables)
-      attachables.flatten.collect do |attachable|
-        if record.new_record?
-          attachments.build(record: record, blob: create_blob_from(attachable))
-        else
-          attachments.create!(record: record, blob: create_blob_from(attachable))
-        end
+      if record.persisted? && !record.changed?
+        record.update(name => blobs + attachables.flatten)
+      else
+        record.public_send("#{name}=", blobs + attachables.flatten)
       end
     end

Merge this gem into Rails

Hey

This gem is really helpful and it could be very cool to see it in Rails core.

Do you plan to open PR in rails/rails to merge it?

Thanks ๐Ÿ™

Dimension validator not working

Hi! I am having a problem with the dimension validator where this line causes the exception NoMethodError (undefined method id' for nil:NilClass)`.

Any idea of how to fix? Thanks!

ps. I'm on Rails master if that's relevant.

Error on attached matcher

I have the following model:

class Payment < ApplicationRecord
  validates :voucher, attached: true, if: :paid?
end

And the following test:

context 'when is paid' do
  subject { build(:payment, status: :paid) }

  it { is_expected.to validate_attached_of(:voucher) }
end

context 'when is pending' do
  subject { build(:payment, status: :pending) }

  it { is_expected.to_not validate_attached_of(:voucher) }
end

it fails in the "when is paid" context. I was checking the code out and I think the error is here:
https://github.com/igorkasyanchuk/active_storage_validations/blob/master/lib/active_storage_validations/matchers/attached_validator_matcher.rb#L35

You are always creating a new instance of the subject class:
https://github.com/igorkasyanchuk/active_storage_validations/blob/master/lib/active_storage_validations/matchers/attached_validator_matcher.rb#L35

In order to support this it should be: @subject = subject instead of getting the class.

What do you think?

Raise exception when ImageMagick not installed

Hey Igor, thanks for your gem.
When I try to add dimension validation and no ImageMagick installed, I'm getting error ActiveRecord validation error with text Image is not a valid image. It's expectedly but not obvious to have ImageMagick installed.

I think it's better to throw exception when user tries to use dimension validator. What do you think about it? I can create PR if nesessary.

Thanks!

validate :attachment, attached: true, if: :some_procedure_to_check_data?

How can I do a conditional validation based on some arbitrary criterion?

I can do this with other validations e.g.

validates :some_field, numericality: true, if: :some_function_to_check_a_condition?

but this doesn't work

validates :attachment, attached: true, if: :some_function_to_check_a_condition?

Which is strange because it should check if: :some_function_to_check_a_condition? before it even attempts the validation.

Aspect ratio validation on Rails 6.1 results in NoMethodError

I have a User with an avatar, like this:

class User < ApplicationRecord
  validates :avatar,
    attached: false,
    content_type: ['image/png', 'image/jpg', 'image/jpeg'],
    size: { less_than: 3.megabytes },
    aspect_ratio: :square
end

When I try to upload a file for my avatar on Rails 6 it worked fine. On Rails 6.1, it's busted.

Full backtrace:

NoMethodError - undefined method `id' for nil:NilClass:
  active_storage_validations (0.9.0) lib/active_storage_validations/metadata.rb:26:in `read_image'
  active_storage_validations (0.9.0) lib/active_storage_validations/metadata.rb:10:in `metadata'
  active_storage_validations (0.9.0) lib/active_storage_validations/aspect_ratio_validator.rb:31:in `block in validate_each'
  active_storage_validations (0.9.0) lib/active_storage_validations/aspect_ratio_validator.rb:30:in `validate_each'
  activemodel (6.1.0) lib/active_model/validator.rb:153:in `block in validate'
  activemodel (6.1.0) lib/active_model/validator.rb:149:in `validate'
  activesupport (6.1.0) lib/active_support/callbacks.rb:427:in `block in make_lambda'
  activesupport (6.1.0) lib/active_support/callbacks.rb:198:in `block (2 levels) in halting'
  activesupport (6.1.0) lib/active_support/callbacks.rb:604:in `block (2 levels) in default_terminator'
  activesupport (6.1.0) lib/active_support/callbacks.rb:603:in `block in default_terminator'
  activesupport (6.1.0) lib/active_support/callbacks.rb:199:in `block in halting'
  activesupport (6.1.0) lib/active_support/callbacks.rb:512:in `block in invoke_before'
  activesupport (6.1.0) lib/active_support/callbacks.rb:512:in `invoke_before'
  activesupport (6.1.0) lib/active_support/callbacks.rb:105:in `run_callbacks'
  activesupport (6.1.0) lib/active_support/callbacks.rb:824:in `_run_validate_callbacks'
  activemodel (6.1.0) lib/active_model/validations.rb:406:in `run_validations!'
  activemodel (6.1.0) lib/active_model/validations/callbacks.rb:117:in `block in run_validations!'
  activesupport (6.1.0) lib/active_support/callbacks.rb:106:in `run_callbacks'
  activesupport (6.1.0) lib/active_support/callbacks.rb:824:in `_run_validation_callbacks'
  activemodel (6.1.0) lib/active_model/validations/callbacks.rb:117:in `run_validations!'
  activemodel (6.1.0) lib/active_model/validations.rb:337:in `valid?'
  activerecord (6.1.0) lib/active_record/validations.rb:68:in `valid?'
  activerecord (6.1.0) lib/active_record/validations.rb:84:in `perform_validations'
  activerecord (6.1.0) lib/active_record/validations.rb:47:in `save'
  activerecord (6.1.0) lib/active_record/transactions.rb:296:in `block in save'
  activerecord (6.1.0) lib/active_record/transactions.rb:352:in `block in with_transaction_returning_status'
  activerecord (6.1.0) lib/active_record/connection_adapters/abstract/database_statements.rb:318:in `transaction'
  activerecord (6.1.0) lib/active_record/transactions.rb:348:in `with_transaction_returning_status'
  activerecord (6.1.0) lib/active_record/transactions.rb:296:in `save'
  activerecord (6.1.0) lib/active_record/suppressor.rb:44:in `save'
  activerecord (6.1.0) lib/active_record/persistence.rb:628:in `block in update'
  activerecord (6.1.0) lib/active_record/transactions.rb:352:in `block in with_transaction_returning_status'
  activerecord (6.1.0) lib/active_record/connection_adapters/abstract/database_statements.rb:320:in `block in transaction'
  activerecord (6.1.0) lib/active_record/connection_adapters/abstract/transaction.rb:310:in `block in within_new_transaction'
  activesupport (6.1.0) lib/active_support/concurrency/load_interlock_aware_monitor.rb:26:in `block (2 levels) in synchronize'
  activesupport (6.1.0) lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `block in synchronize'
  activesupport (6.1.0) lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `synchronize'
  activerecord (6.1.0) lib/active_record/connection_adapters/abstract/transaction.rb:308:in `within_new_transaction'
  activerecord (6.1.0) lib/active_record/connection_adapters/abstract/database_statements.rb:320:in `transaction'
  activerecord (6.1.0) lib/active_record/transactions.rb:348:in `with_transaction_returning_status'
  activerecord (6.1.0) lib/active_record/persistence.rb:626:in `update'
  app/controllers/users_controller.rb:42:in `block in update'
  actionpack (6.1.0) lib/action_controller/metal/mime_responds.rb:205:in `respond_to'
  app/controllers/users_controller.rb:41:in `update'
  actionpack (6.1.0) lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action'
  actionpack (6.1.0) lib/abstract_controller/base.rb:228:in `process_action'
  actionpack (6.1.0) lib/action_controller/metal/rendering.rb:30:in `process_action'
  actionpack (6.1.0) lib/abstract_controller/callbacks.rb:42:in `block in process_action'
  activesupport (6.1.0) lib/active_support/callbacks.rb:117:in `block in run_callbacks'
  sentry-raven (3.1.1) lib/raven/integrations/rails/controller_transaction.rb:7:in `block in included'
  activesupport (6.1.0) lib/active_support/callbacks.rb:126:in `block in run_callbacks'
  activesupport (6.1.0) lib/active_support/callbacks.rb:137:in `run_callbacks'
  actionpack (6.1.0) lib/abstract_controller/callbacks.rb:41:in `process_action'
  actionpack (6.1.0) lib/action_controller/metal/rescue.rb:22:in `process_action'
  actionpack (6.1.0) lib/action_controller/metal/instrumentation.rb:34:in `block in process_action'
  activesupport (6.1.0) lib/active_support/notifications.rb:203:in `block in instrument'
  activesupport (6.1.0) lib/active_support/notifications/instrumenter.rb:24:in `instrument'
  activesupport (6.1.0) lib/active_support/notifications.rb:203:in `instrument'
  actionpack (6.1.0) lib/action_controller/metal/instrumentation.rb:33:in `process_action'
  actionpack (6.1.0) lib/action_controller/metal/params_wrapper.rb:249:in `process_action'
  activerecord (6.1.0) lib/active_record/railties/controller_runtime.rb:27:in `process_action'
  actionpack (6.1.0) lib/abstract_controller/base.rb:165:in `process'
  actionview (6.1.0) lib/action_view/rendering.rb:39:in `process'
  actionpack (6.1.0) lib/action_controller/metal.rb:190:in `dispatch'
  actionpack (6.1.0) lib/action_controller/metal.rb:254:in `dispatch'
  actionpack (6.1.0) lib/action_dispatch/routing/route_set.rb:50:in `dispatch'
  actionpack (6.1.0) lib/action_dispatch/routing/route_set.rb:33:in `serve'
  actionpack (6.1.0) lib/action_dispatch/journey/router.rb:50:in `block in serve'
  actionpack (6.1.0) lib/action_dispatch/journey/router.rb:32:in `serve'
  actionpack (6.1.0) lib/action_dispatch/routing/route_set.rb:842:in `call'
  warden (1.2.9) lib/warden/manager.rb:36:in `block in call'
  warden (1.2.9) lib/warden/manager.rb:34:in `call'
  rack (2.2.3) lib/rack/tempfile_reaper.rb:15:in `call'
  rack (2.2.3) lib/rack/etag.rb:27:in `call'
  rack (2.2.3) lib/rack/conditional_get.rb:40:in `call'
  rack (2.2.3) lib/rack/head.rb:12:in `call'
  actionpack (6.1.0) lib/action_dispatch/http/permissions_policy.rb:22:in `call'
  actionpack (6.1.0) lib/action_dispatch/http/content_security_policy.rb:18:in `call'
  rack (2.2.3) lib/rack/session/abstract/id.rb:266:in `context'
  rack (2.2.3) lib/rack/session/abstract/id.rb:260:in `call'
  actionpack (6.1.0) lib/action_dispatch/middleware/cookies.rb:689:in `call'
  activerecord (6.1.0) lib/active_record/migration.rb:601:in `call'
  actionpack (6.1.0) lib/action_dispatch/middleware/callbacks.rb:27:in `block in call'
  activesupport (6.1.0) lib/active_support/callbacks.rb:98:in `run_callbacks'
  actionpack (6.1.0) lib/action_dispatch/middleware/callbacks.rb:26:in `call'
  actionpack (6.1.0) lib/action_dispatch/middleware/executor.rb:14:in `call'
  actionpack (6.1.0) lib/action_dispatch/middleware/actionable_exceptions.rb:18:in `call'
  better_errors (2.9.1) lib/better_errors/middleware.rb:87:in `protected_app_call'
  better_errors (2.9.1) lib/better_errors/middleware.rb:82:in `better_errors_call'
  better_errors (2.9.1) lib/better_errors/middleware.rb:60:in `call'
  actionpack (6.1.0) lib/action_dispatch/middleware/debug_exceptions.rb:29:in `call'
  web-console (4.1.0) lib/web_console/middleware.rb:132:in `call_app'
  web-console (4.1.0) lib/web_console/middleware.rb:28:in `block in call'
  web-console (4.1.0) lib/web_console/middleware.rb:17:in `call'
  actionpack (6.1.0) lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
  railties (6.1.0) lib/rails/rack/logger.rb:37:in `call_app'
  railties (6.1.0) lib/rails/rack/logger.rb:26:in `block in call'
  activesupport (6.1.0) lib/active_support/tagged_logging.rb:99:in `block in tagged'
  activesupport (6.1.0) lib/active_support/tagged_logging.rb:37:in `tagged'
  activesupport (6.1.0) lib/active_support/tagged_logging.rb:99:in `tagged'
  railties (6.1.0) lib/rails/rack/logger.rb:26:in `call'
  sprockets-rails (3.2.2) lib/sprockets/rails/quiet_assets.rb:13:in `call'
  actionpack (6.1.0) lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
  actionpack (6.1.0) lib/action_dispatch/middleware/request_id.rb:26:in `call'
  rack (2.2.3) lib/rack/method_override.rb:24:in `call'
  rack (2.2.3) lib/rack/runtime.rb:22:in `call'
  activesupport (6.1.0) lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
  actionpack (6.1.0) lib/action_dispatch/middleware/executor.rb:14:in `call'
  actionpack (6.1.0) lib/action_dispatch/middleware/static.rb:24:in `call'
  rack (2.2.3) lib/rack/sendfile.rb:110:in `call'
  actionpack (6.1.0) lib/action_dispatch/middleware/host_authorization.rb:98:in `call'
  rack-cors (1.1.1) lib/rack/cors.rb:100:in `call'
  sentry-raven (3.1.1) lib/raven/integrations/rack.rb:51:in `call'
  webpacker (5.2.1) lib/webpacker/dev_server_proxy.rb:25:in `perform_request'
  rack-proxy (0.6.5) lib/rack/proxy.rb:57:in `call'
  railties (6.1.0) lib/rails/engine.rb:539:in `call'
  puma (5.1.1) lib/puma/configuration.rb:246:in `call'
  puma (5.1.1) lib/puma/request.rb:76:in `block in handle_request'
  puma (5.1.1) lib/puma/thread_pool.rb:337:in `with_force_shutdown'
  puma (5.1.1) lib/puma/request.rb:75:in `handle_request'
  puma (5.1.1) lib/puma/server.rb:431:in `process_client'
  puma (5.1.1) lib/puma/thread_pool.rb:145:in `block in spawn_thread'

This only seems to effect the aspect ratio validator, as far as I can tell? If I remove the aspect ratio attribute on that method, everything works fine.

This is the line that's broken:

tempfile = Tempfile.new(["ActiveStorage-#{blob.id}-", blob.filename.extension_with_delimiter])

My best guess is that this commit removed find_signed and broke things, but I'm honestly not sure: rails/rails@31148cd#diff-3c9b81c772d32ff6805463595e04d8d7e9031e875ebf2f9fb964ca1a78b5e56d

Rails dependency

With the dependency on Rails, and not its sub-components, it's not possible for an app that uses this gem to cut out parts of Rails to save memory. Can this be changed? Glad to come up with a PR if you can tell me what they should be (ActiveModel?).

TypeError (no implicit conversion of StringIO into String) - Decoding base64 file

Hi, first of all, thank you for your hard work in building a fantastic gem.
I got an issue with validating file, when I was trying to save the decoded file the error occurred:
TypeError (no implicit conversion of StringIO into String)
Here is my controller where I save the file:

   @video_template.video.attach(
      io: StringIO.new(Base64.decode64(params[:video].split(',')[1])),
      filename: "video#{Time.now.strftime('%Y%m%d%H%M%S')}.mp4"
    )
    @video_template.thumbnail.attach(
      io: StringIO.new(Base64.decode64(params[:thumbnail].split(',')[1])),
      filename: "thumbnail#{Time.now.strftime('%Y%m%d%H%M%S')}.jpg"
    )

    if @video_template.save # error comes from here
      render json: @video_template, serializer: VideoTemplateSerializer
    end

In my model, there is my validation:

  has_one_attached :video
  has_one_attached :thumbnail

  validates :video, attached: true,
                    size: { less_than: 10.megabytes , message: I18n.t('video_size_too_big') },
                    content_type: { in: 'video/mp4', message: I18n.t('video_type_wrong') }
  validates :thumbnail, attached: true,
                        size: { less_than: 1.megabyte, message: I18n.t('video_thumbnail_too_big') },
                        content_type: { in: ['image/jpg', 'image/jpeg'], message: I18n.t('video_thumbnail_type_wrong')},
                        dimension: { width: { min: 0, max: 240 },
                                     height: { min: 0, max: 240 }, message: I18n.t('video_thumbnail_wrong_demension') }

If I comment out the validates, then I can save successfully. I'm not sure that the root cause of this error comes from active_storage_validations or not, but the record can save normally if it has no validation made me think about this.
I'm using version: 0.9.0
ruby '2.7.1'
rails '6.0.3'

Content Type validator silently ignores undefined MIME types

Giving the content type validator a symbol that does not map to a supported MIME type will be silently ignored, leading to false negatives that are pretty hard to track down.

Example Code

has_one_attached :document

validates :document, content_type: %i[txt doc docx]

By default, neither Mime[:doc] or Mime[:docx] are defined (thus return nil).

Expected Behaviour

Supplying a symbol for an undefined MIME type raises an error.

Actual Behaviour

Supplying a symbol for an undefined MIME type is silently ignored.

Environment Info

Ruby Version: 2.6.6
Rails Version: 6.1.3
Active Storage Validations Version: 0.9.2

Rails 5.2: delete attachment if it's not valid

Hi! as you already know, in Rails 5.2 validations on ActiveStorage attachments are useless since they are saved as soon as they are assigned to a model. Then, it would be possible to add a custom behaviour for Rails 5.2 that deletes the attachment if the attachment is not valid?
If you have any idea that works outside of this gem it would be really appreciated as well ๐Ÿ™„

The only shitty solution I came up with is:

  has_many_attached :files

  validates :files, content_type: { in: ALLOWED_CONTENT_TYPES, message: 'is not a valid file' }
  after_validation :delete_invalid_attachments

  private

  def delete_invalid_attachments
    files.last.destroy if errors.include?(:files)
  end

But this would work only with a has_one_attached because you know beforehand that just one attachment could be invalid. With has_many_attached it gets worse since I don't know how many files were attached and I'm just deleting the latest one ๐Ÿ’ฉ

Rails 6.0.0.rc1: ActiveStorage::FileNotFoundError on model save

The following declaration throws ActiveStorage::FileNotFoundError in Rails 6.0.0.rc1 when I try to save a model:

validates :image, attached: true, content_type: IMAGE_CONTENT_TYPE,
  dimension: { width: IMAGE_WIDTH, height: IMAGE_HEIGHT }

with the following cause:

Errno::ENOENT: No such file or directory @ rb_sysopen - /home/oleksandri/Code/marketplace/storage/08/3z/083z1pphjimzuwty55kqvb7t1hhj

Is it supported in Rails 6?

Thank you for your valuable work

Ruby 2.7.2 deprecation warning for Ruby 3

I'm pretty sure this is a problem in active_storage_validations, not with activemodel, though I might be wrong.

From running my specs on Ruby 2.7.2 with the RUBYOPT='-W:deprecated' environment variable set:

/Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/bundler/gems/active_storage_validations-b84dfeaba49d/lib/active_storage_validations/content_type_validator.rb:17: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/activemodel-6.1.0/lib/active_model/errors.rb:404: warning: The called method `add' is defined here
/Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/bundler/gems/active_storage_validations-b84dfeaba49d/lib/active_storage_validations/size_validator.rb:28: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/Users/connorshea/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/activemodel-6.1.0/lib/active_model/errors.rb:404: warning: The called method `add' is defined here

(The reason that environment variable is necessary is because 2.7.2 made the deprecation warnings hidden by default).

The gem is on a commit SHA because it's on my branch for #94, but nothing has changed on master that should really make a difference for this, as far as I can tell.

With Ruby 3.0 coming out in a few days, I think it's a good idea to make sure the gem doesn't have any deprecation warnings like this.

See this article for more information on the kwargs changes: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/

Running validation alone doesn't seem to take options into account

I have a specific situation where I have to run the individual attached validation with validate_each and it doesn't seem to take into account the passed options.

Validation in my model:
validates :file, attached: true, if: Proc.new { |i| i.something.blank? }

Running validation individually:

ModelClass.validators_on(:file)
.select { |v| v.class == ActiveStorageValidations::AttachedValidator }.first
.validate_each(record, :file, nil)

where record's something attribute isn't blank and yet it returns the "file can't be blank" error.

To be clear: the if option seems to be taken into account in the actual validation in normal situations, such as using .valid? or on update, but not when running the validation like above. Perhaps I'm doing something wrong, but it all looks correct.

Attached message

When doing this:
validates :stored_image, attached: true

The error message is "Can't be blank"

Normally with rails I can do:
validates :stored_image, attached: {message: "Must provide an image"}

But that is not an option here. I was hoping one of the translation keys was for a missing image but doesn't appear that's the case. Unless I'm missing something?

ActiveAdmin and active_storage_validations

This Gem is great. i'm using it for a front end form which is perfect.

I'm also using it for the Admin part of my App using activeadmin gem.

I get validation error message in ActiveAdmin but it saves the file and ignores the validations

validates :images, content_type: ['image/png', 'image/jpg', 'image/jpeg'], limit: { min: 1, max: 2, message: 'You are allowed a maximum of 2 attachments' }, size: { less_than: 200.kilobytes , message: 'The attachment(s) must be less than 200kb' }

Would be great if you could give some pointers. Really stuck :-(
This is a similar issue with another gem

aki77/activestorage-validator#1

Thank you

AttachedValidator isn't known on ActiveModel / a form object

A typical form object might look like

class ModelName
  include ActiveModel::Model
  include ActiveAttr::TypecastedAttributes
  attribute :foo, type: Boolean
  attr_accessor :some_file
  validates :some_file, attached: true
end

And storing the file might happen later on.
So the question is: is there any possibility to use active storage validation on that model?
Because using the validation directly at the moment leads to

uninitialized constant ModelName::AttachedValidator.

Thank you in advance

Storing blob when validation fails

Rails version: 6.0.2.1
Gem version: 0.8.6

Validation:
validates :bill_kit, content_type: { in: ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'], message: 'is not a PDF or Word Document' }

If you upload a file other than a word doc or a PDF the validation fails (as it should) but the blob is still stored in the db. The attachment is not created.

How to run the test suite?

I'm trying to run the test suite, but it's failing with this error:

Connors-MacBook-Pro:active_storage_validations connorshea$ BUNDLE_GEMFILE=gemfiles/rails_6_0.gemfile bundle exec rake test

File does not exist: active_support

rake aborted!
Command failed with status (1)
/Users/connorshea/.rbenv/versions/2.6.5/bin/bundle:23:in `load'
/Users/connorshea/.rbenv/versions/2.6.5/bin/bundle:23:in `<main>'
Tasks: TOP => test
(See full trace by running task with --trace)

Any ideas?

Use Circle or GitHub Actions for CI

Or GitLab CI, which is my preference but isn't trivial to set up with a GitHub repo :)

Since Travis is pretty much dead and the repo currently doesn't have CI set up for it, now would be a good time to switch.

`require': cannot load such file -- mini_magick

Ruby: 2.5.3
Rails: 6.0.0rc1
This gem: 0.7.1

I tried to add some validations to the image field attached via active-storage. However, when I launch my rails server, I get next error

5: from /Users/abc/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/active_storage_validations-0.7.1/lib/active_storage_validations/dimension_validator.rb:8:in `initialize'
          4: from /Users/abc/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-6.0.0.rc1/lib/active_support/dependencies.rb:302:in `require'
          3: from /Users/abc/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-6.0.0.rc1/lib/active_support/dependencies.rb:268:in `load_dependency'
          2: from /Users/abc/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/activesupport-6.0.0.rc1/lib/active_support/dependencies.rb:302:in `block in require'
          1: from /Users/abc/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/zeitwerk-2.1.8/lib/zeitwerk/kernel.rb:23:in `require'
/Users/abc/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/zeitwerk-2.1.8/lib/zeitwerk/kernel.rb:23:in `require': cannot load such file -- mini_magick (LoadError)

As I see mini_magic comes as dev-dependency for this gem, but it's absent in my Gemfile.lock. So my question is mini_magic obligatory to use with this gem for image validation? My app uses active_storage + imgproxy for image handling

uninitialized constant ModuleX::ModuleY::ModuleZ::AttachedValidator

This is similar to
issue 15, but the context is sufficiently different to make a new issue.

Using Rails 5.2.3 with Ruby2.5.5.

This is a project I've inherited and it's old convoluted code. I'm moving from using Paperclip to ActiveStorage and trying to use active_storage_validations.

In part of the project we define an acts_as_image_upload method and then use that elsewhere on ActiveRecord models that need to have an image asset attached. The definition sits in an internal gem (actually a modified engine) which in included in the Gemfile for various projects inside the company.

In other words I'm not using active_storage_validations directly in models, but indirectly via an acts_as_x method. But it's not working.

I've defined this ;

 # unnecessary code removed
 module SomeDummyName
  def acts_as_image_upload
    has_one_attached :attachment
    validates :attachment, attached: true
 end 
 # unnecessary code removed
 ActiveRecord::Base.extend(SomeDummyName)
 # unnecessary code removed

When I try to use it in a model which is namespaced as ModuleX::ModuleY::ModuleZ::ModelClass then I get this error;-

uninitialized constant ModuleX::ModuleY::ModuleZ::AttachedValidator

So it's clear that the validator isn't being added to the model in this case.

Obviously by putting a require and include in the correct place I should be able to get this working. How can I do it?

image upload size

Is there any validations for how many images a user can uploaded? For example, each post can post up to 24 images?

content_type validator doesn't quite work

So I just discovered that the content type validation doesn't work quite right at the moment if you upload, e.g. a webp file with the name image.png.

To reproduce the issue:

  • Create an attachment on a model where the attachment can only be a PNG.
validates :avatar,
  attached: false,
  content_type: ['image/png']
  • Find and download a webp file (see https://developers.google.com/speed/webp/gallery1 for some examples)
  • Upload the webp file to your Rails app
  • See that it fails.
  • Rename it to whatever.png
  • Upload the file again.
  • See that it gets uploaded successfully.

You can reproduce this with the avatar uploader my Rails app, though it might be overkill to set it up just for this :)

Oddly, ActiveStorage does seem to analyze the image properly. as it ends up having a content_type of image/webp in the active_storage_blobs table. I'm not sure if it briefly has an incorrect content type immediately after upload or something?

This caused a problem with my application because ImageMagick can't handle webp files when producing variants, so it was causing errors.

Versions:

active_storage_validations: 0.8.3
Rails: 6.0.0

Size validation message is not very good

Using:

    validates :attachments, size: { less_than: 200.kilobytes }

The message I get is

Attachments size 1.5 MB is not between required range

  • This is broken English - numbers are "in" a range, not "between" a range
  • This doesn't mention what the requirements are
  • I didn't specify a range - just a maximum
  • It should reference a singular "attachment"

Rails's numericality validator gives messages like this:

Size must be less than 5

so something like

Attachment size must be less than 200KB

that would be good. (I know I can customize the message but I figure this would be a good improvement for anyone).

There's a way to save a record with no attachment even with an attachment presence validation (using Trestle)

I ran into an issue when using active_storage_validations with Trestle (https://github.com/TrestleAdmin/trestle/trestle-active_storage) and with trestle-active_storage (https://github.com/richardvenneman/trestle-active_storage, wrote about the issue here: richardvenneman/trestle-active_storage#23) and I'm not sure which of the gems possibly "owns" the issue, so forgive me if I'm wrong to open this issue here.

I found that there's a way to save a record with no attachments even with a presence validation. The validation works if you're trying to save a newly added record and adding attachments for the first time (or rather not adding any and wanting to save the record). But when you successfully save a record, and then delete all the attachments and save, it's successfully saved, even though the record is invalid (and you can check that in rails console ofc with the_saved_record.valid?, which is false).

I haven't found an issue anywhere, could you tell me if this is something you know about if you know a fix/workaround? Also let me know if this is not the right place for this issue. Thx!

PS Huge thank you for this gem <3 ๐Ÿ’Ž

PS thanks for the gem! ๐Ÿ’Ž

Rspec test matchers?

This isn't really an issue per se so feel free to close it ASAP, but I was just wondering if there's a way to test the validations in Rspec, similar to how Paperclip had validate_attachment_content_type?

How to exit when validation fails?

Hi,

I have this code:

class Barcode < ActiveRecord::Base

  has_one_attached :file

  validates :file, :attached => true, :content_type => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"

  validate :valid_file

end

It seems my valid_file method always runs, even when the validates :file method fails. How can I prevent that from happening?

Content type validation simply not working

class Accessory < ApplicationRecord
  has_one_attached :image
  validates :image, attached: true, content_type: { in: 'image/jpg', message:  'Picture must be jpg'}
end

rails console

a = Accessory.find(1)
a.image.attach(io: File.open('/home/jason/Documents/Affidavit.pdf'), filename: 'test', content_type: 'application/pdf')

No validation error is given and active storage records are created.

All validations don't work with Rails 6 in tests

My stack:

  • Rails 6
  • RSpec
  • This gem

My model code:

class User < ApplicationRecord
  has_one_attached :avatar
  validates :avatar,
    size: { less_than: 1.megabyte },
    content_type: [ :png, :jpeg, :gif ],
    aspect_ratio: :square,
    dimension: { 
      width: { in: 16..256 },
      height: { in: 16..256 }
    }
end

My spec code:

describe '#avatar' do
  it 'should allow attaching a picture' do
    subject.avatar.attach(
      io: File.open(Rails.root.join('spec/fixtures/files/dummy_image.jpg')), 
      filename: 'attachment.jpg', 
      content_type: 'image/jpg')
    expect(subject.avatar).to be_attached
  end
  it 'should not allow attaching garbage' do
    subject.avatar.attach(
      io: File.open(Rails.root.join('spec/fixtures/files/example_mod.jar')), 
      filename: 'attachment.jpg', 
      content_type: 'image/jpg')
    expect(subject.avatar).not_to be_attached
  end
end

First example passes with any file, second fails with any file. That means all validations used don't work (except size, which I haven't tested). It worked fine previously with Rails 5.2 outside of the test environment

Test matchers give MiniMagick::Error

The model:

class ContentImage < ApplicationRecord
  has_one_attached :image

  validates :image,
            attached: true,
            content_type: [:png, :jpg, :jpeg, :gif],
            size: { less_than: 20.megabytes },
            dimension: { width: { min: 640 } }
end

the tests

require "spec_helper"
describe ContentImage do
  it { is_expected.to validate_content_type_of(:image).allowing(:gif, :jpg, :png) }
  it { is_expected.to validate_content_type_of(:image).rejecting(:html, :text, :pdf) }
  it { is_expected.to validate_dimensions_of(:image).width_min(640) }
  it { is_expected.to validate_size_of(:image).less_than(20.megabytes) }
end

gives errors:

  1) ContentImage should validate the content types allowed on attachment image
     Failure/Error: it { is_expected.to validate_content_type_of(:image).allowing(:gif, :jpg, :png) }
     
     MiniMagick::Error:
       `identify -format %[orientation] /var/folders/vk/ptccmv4j3kv1krwhz2k7xy7w0000gn/T/ActiveStorage20210323-22180-q4kkxa.gif[0]` failed with error:
       identify: improper image header `/var/folders/vk/ptccmv4j3kv1krwhz2k7xy7w0000gn/T/ActiveStorage20210323-22180-q4kkxa.gif' @ error/gif.c/ReadGIFImage/1028.


  2) ContentImage should validate file size of image
     Failure/Error: it { is_expected.to validate_size_of(:image).less_than(20.megabytes) }
     
     MiniMagick::Error:
       `identify -format %[orientation] /var/folders/vk/ptccmv4j3kv1krwhz2k7xy7w0000gn/T/ActiveStorage20210323-22180-1gnvv0v.png[0]` failed with error:
       identify: improper image header `/var/folders/vk/ptccmv4j3kv1krwhz2k7xy7w0000gn/T/ActiveStorage20210323-22180-1gnvv0v.png' @ error/png.c/ReadPNGImage/4283.

Security when using direct uploads?

When using direct uploads, ActiveStorage will delay identifying the file into a background job, spawned only after the file has been attached. Currently, the initial blob's content_type is determined solely based on the extension of the uploaded file, and I believe this value will be the one that's used by active_storage_vaidations. In this case, if my app is accepting only images, a malicious user could upload a non-image file by giving it an image extension (e.g. jpg, png).

Metadata validation fails when config.active_storage.replace_on_assign_to_many = false

Using Rails 6
Set config.active_storage.replace_on_assign_to_many = false to append to blobs list on update instead of replacing, see: https://edgeguides.rubyonrails.org/6_0_release_notes.html
On a record which has_many_attached blobs, set metadata validations like dimension: { width: { min: 200 }, height: { min: 200 } }
Attach one or more images to a record. This succeeds.
Now, attach one or more images to the record again. This fails with:

RuntimeError in Controller#update
Something wrong with params.

active_storage_validations (0.8.7) lib/active_storage_validations/metadata.rb:67:in read_file_path'
active_storage_validations (0.8.7) lib/active_storage_validations/metadata.rb:37:in read_image' active_storage_validations (0.8.7) lib/active_storage_validations/metadata.rb:10:in metadata'
active_storage_validations (0.8.7) lib/active_storage_validations/dimension_validator.rb:46:in block in validate_each' active_storage_validations (0.8.7) lib/active_storage_validations/dimension_validator.rb:45:in each'
active_storage_validations (0.8.7) lib/active_storage_validations/dimension_validator.rb:45:in validate_each' activemodel (6.0.2.1) lib/active_model/validator.rb:152:in block in validate'
activemodel (6.0.2.1) lib/active_model/validator.rb:149:in each' activemodel (6.0.2.1) lib/active_model/validator.rb:149:in validate'
...`

Params:

"images"=> [#<ActionDispatch::Http::UploadedFile:0x00007fed67101308 @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"object[images][]\"; filename=\"filename.jpg\"\r\n" + "Content-Type: image/jpeg\r\n", @original_filename="filename.jpg", @tempfile=#<File:/tmp/RackMultipart20200127-18612-ox8p5.jpg>>],

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.