Validates Lengths from Database
Few people add length validations to fields in their database, and when saving such fields that have exhausted their length, an SQL error occurs. This gem introspects your table schema for maximum lengths on string and text fields and automatically adds length validations to the model.
Requirements
- Ruby 2.7 or newer
- ActiveRecord 6.1 or newer
For older versions of Rails or Ruby, use the 0.8.x releases.
Installation
Include the gem using bundler in your Gemfile:
gem "validates_lengths_from_database"Usage
In your model you can activate validations:
class Post < ActiveRecord::Base
validates_lengths_from_database
endIt also supports filter-style :only and :except options:
class Post < ActiveRecord::Base
validates_lengths_from_database only: [:title, :contents]
end
class Post < ActiveRecord::Base
validates_lengths_from_database except: [:other_field]
endIf you'd rather specify the limits explicitly instead of pulling them from the database, you can set it:
class Post < ActiveRecord::Base
validates_lengths_from_database limit: 255
endOr to set the limit differently for string (VARCHAR) and TEXT columns:
class Post < ActiveRecord::Base
validates_lengths_from_database limit: { string: 255, text: 4092 }
endYou can use validates_lengths_from_database on an abstract parent (without a table), such as ApplicationRecord or ActiveRecord::Base:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
validates_lengths_from_database limit: 255
end
# This will use the validations with default options (from parent)
class Post < ApplicationRecord
end
# This will use the validations, with overriding the default options (the limit will not be applied)
class User < ApplicationRecord
validates_lengths_from_database only: [:name]
end
# This will use the validations, using the default options + adding some class specific customizations
class Comment < ApplicationRecord
validate_lengths_from_database_options[:only] << :name
endWhat gets validated
-
String (
VARCHAR) columns are limited to the column's length in characters. -
TEXTcolumns are limited to the column's length in bytes, since that is how MySQL measures them. -
DECIMALcolumns with a precision and scale must fit within them. For example,DECIMAL(5,2)accepts -999.99 through 999.99. -
Integer columns must fit the column's size, such as -32,768 through 32,767 for
SMALLINT, including MySQL'sTINYINTandUNSIGNEDcolumns. These are exactly the values Rails would otherwise refuse to save with anActiveModel::RangeError. Useexcept:to skip a column.
Columns without a limit are skipped, so what this gem does depends on your database:
-
MySQL:
t.stringcolumns areVARCHAR(255)andt.textcolumns areTEXT(65,535 bytes) unless you say otherwise. This is where the gem helps most, turningActiveRecord::ValueTooLongexceptions into validation errors. -
PostgreSQL: Rails creates
t.stringcolumns with no length limit, andTEXTcolumns never have one, so only columns declared with an explicitlimit:are validated. - SQLite: limits declared in migrations are validated, even though SQLite itself does not enforce them.
Translating error messages
TEXT columns that are too long get the too_long_bytes error, which defaults to "is too long (maximum is %{count} bytes)".
Integers that don't fit their column get the out_of_range error, which defaults to "is out of range".
Override them in your locale files like any other ActiveModel error message:
en:
errors:
messages:
too_long_bytes:
one: "is too long (maximum is 1 byte)"
other: "is too long (maximum is %{count} bytes)"
out_of_range: "is out of range"Running Tests
The specs run against SQLite by default:
bundle install
bundle exec rspec
Set DB=postgresql or DB=mysql to test against those databases instead. Both expect a database named validates_lengths_from_database_test, and connection settings can be changed with environment variables (see spec/db/database.yml).
To test against a specific Rails version, use one of the gemfiles in gemfiles/:
BUNDLE_GEMFILE=gemfiles/rails_7.0.gemfile bundle install
BUNDLE_GEMFILE=gemfiles/rails_7.0.gemfile bundle exec rspec