• Stars
    star
    959
  • Rank 46,009 (Top 1.0 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 5 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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 }

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 usefull 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 maxmimum 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 developped.

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.

More Repositories

1

rails_db

Rails Database Viewer and SQL Query Runner
JavaScript
1,447
star
2

rails_performance

Monitor performance of you Rails applications (self-hosted and free)
Ruby
793
star
3

any_login

Easy way to login as any user in system
Ruby
376
star
4

log_analyzer

Rails logs analyzer (see how fast your views are rendering)
Ruby
349
star
5

rails_pdf

A reliable way to generate PDF of any complexity in Ruby on Rails apps
HTML
175
star
6

fake_api

The fastest way to prototype API in your Rails application
Ruby
142
star
7

execution_time

How fast is your code? See it directly in Rails console.
Ruby
111
star
8

benchmark_methods

Benchmark and measure execution time your Ruby methods without an additional code changes
Ruby
89
star
9

new_google_recaptcha

reCAPTCHA v3 Ruby on Rails gem
Ruby
82
star
10

transactify

Wrap your methods in DB Transactions
Ruby
55
star
11

sql_view

Rails SQL Views made easy ;)
Ruby
49
star
12

sweet_staging

Access your Rails console, see logs, execute rake commands directly from the browser. Great addition to your Staging ENV.
JavaScript
46
star
13

execute_sql

Execute SQL inside Rails console, or app itself
Ruby
41
star
14

cache_with_locale

Easy wait to do view caching with automatically added "locale" value to the cached key.
Ruby
37
star
15

avatarro

Generate google-style avatars in your application
Ruby
37
star
16

awesome_back_url

Redirect the user to the proper "back" page
Ruby
33
star
17

new_ckeditor

Ruby on Rails + CKEditor 5
Ruby
31
star
18

records_count

See in development logs how many records your queries returns. It can help with solving performance issues.
Ruby
31
star
19

amazon_static_site

Static website using https with your own domain name using Amazon S3 and Cloudflare for FREE
Ruby
29
star
20

omg_image

Generate PNG previews for HTML snippets (html/css/js). Any complexity.
Ruby
28
star
21

wrapped_print

Easy print debug information to your console in Ruby/Rails app.
Ruby
23
star
22

calculate_in_group

Group Active Record by ranges or set of values with a single SQL query.
Ruby
22
star
23

embed_view

Embed ERB files inside another ERB files for faster performance (5-20% BOOST!!!)
HTML
21
star
24

rails_time_travel

HTML
19
star
25

sabotage

Coding & debugging must be fun. Make life a bit harder for your colleagues :)
Ruby
18
star
26

mechanical

All models in a single table, new attributes without migrations. Works like regular AR model
Ruby
17
star
27

secrett11tto

Simple way to protect your content from copy-pasting
Ruby
15
star
28

rails_live

Ruby
15
star
29

mini-guard

Ruby
14
star
30

railsjazz.com

Rails Jazz (personal web site)
JavaScript
14
star
31

sidekiq_log_analyzer

SidekiqLogAnalyser gem allows to see summary of your sidekiq workers (based on log file).
Ruby
13
star
32

hasharay_ext

Painless work with complex Ruby hashes/arrays.
Ruby
13
star
33

active_storage_silent_logs

The idea of this gem is to hide as much as possible Active Storage logs from console so you can see only important information and requests
Ruby
13
star
34

rails_cached_method

Simple way to cache results of methods.
Ruby
11
star
35

with_record

Returns relations/association for soft deleted records in DB
Ruby
10
star
36

rrr

Run recent rspec files only (the only recently modified).
Ruby
9
star
37

font_awesome_file_icons

Ruby
4
star
38

unwhere

Ruby
4
star
39

travel_and_talk

JavaScript
3
star
40

lazy_mobile_tester

Rails Lazy Mobile Tester
Ruby
3
star
41

serpjazz

SERP keywords tracking
JavaScript
3
star
42

jeanappv2

JavaScript
2
star
43

mega-simple-authorization

mega simple authorization plugin for RoR
Ruby
2
star
44

tv

eb5 tv
JavaScript
2
star
45

spring_rspec_commands_addon

rails+spring+rspec = friends :)
Ruby
2
star
46

layouts_from_db_sample

Allow store layouts to DB (Sample)
2
star
47

CheaperDrinker

CheaperDrinker web site
JavaScript
2
star
48

any_login_test

AnyLogin gem test application
Ruby
2
star
49

tell_my_env

Ruby
2
star
50

slim_erb_backport

Slim 4+ and ERB friends again :)
Ruby
2
star
51

VerySimple

1
star
52

ShareT

online translations
JavaScript
1
star
53

test-ec2

test-ec2
1
star
54

sa1

1
star
55

better_tempfile

Ruby
1
star
56

just_for_fun

Do you want to call 42.to_user, [42, 43, 44].to_users? Try this gem :)
Ruby
1
star
57

tophouse.com.ua

JavaScript
1
star
58

ar_enumerations_test_application

ActiveRecord enumeration field type - test application
Ruby
1
star
59

capistranotest

1
star
60

zip_and_phone

Zips & Phones
Ruby
1
star
61

portfolio

1
star
62

tdemo

tdemo
Ruby
1
star
63

cisarska_and_frankivska

Cisarska & Frankivska
JavaScript
1
star
64

seminars

JavaScript
1
star
65

deprecations_collector

Save all Rails deprecation in log file for future investigation
Ruby
1
star
66

rails_logs

Ruby
1
star