activerecord-databasevalidations 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/active_model/validations/bytesize.rb +2 -6
- data/lib/active_record/database_validations.rb +1 -0
- data/lib/active_record/database_validations/mysql.rb +74 -0
- data/lib/active_record/database_validations/varchar_191.rb +1 -0
- data/lib/active_record/database_validations/varchar_255.rb +1 -0
- data/lib/active_record/database_validations/version.rb +1 -1
- data/lib/active_record/validations/database_constraints.rb +10 -47
- data/lib/active_record/validations/string_truncator.rb +7 -25
- data/test/varchar_default_size_test.rb +20 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 337cf9c69d98f33651b5a008c20f0d24648151ad
|
4
|
+
data.tar.gz: 3d604b4f812af63b98c0cc0de1820a0b61147eae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6700a0056bb6dd20e32b6c584b21509b1f2531bec7c7c26eb03d4eaff7f118ad2485b62657c97ac1409d80c0ad080a0389b0dc60344a17ff5e9614612d619d35
|
7
|
+
data.tar.gz: 09cc0d7b8c4dffd6f32700e2575e66d74c012f9215db3da75bfda9587f297c8578e5c3c9a38260bcf29123cc6b90726abe5c11c52213fc36a0432db5980ec37e
|
@@ -15,8 +15,8 @@ module ActiveModel
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def validate_each(record, attribute, value)
|
18
|
-
string = value
|
19
|
-
|
18
|
+
string = ActiveRecord::DatabaseValidations::MySQL.value_for_column(value, options[:encoding])
|
19
|
+
|
20
20
|
if string.bytesize > options[:maximum]
|
21
21
|
errors_options = options.except(:too_many_bytes, :maximum)
|
22
22
|
default_message = options[:too_many_bytes]
|
@@ -25,10 +25,6 @@ module ActiveModel
|
|
25
25
|
record.errors.add(attribute, :too_many_bytes, errors_options)
|
26
26
|
end
|
27
27
|
end
|
28
|
-
|
29
|
-
def requires_transcoding?(value)
|
30
|
-
encoding.present? && encoding != value.encoding
|
31
|
-
end
|
32
28
|
end
|
33
29
|
|
34
30
|
module HelperMethods
|
@@ -2,6 +2,7 @@ require 'active_record'
|
|
2
2
|
require 'active_support/i18n'
|
3
3
|
require 'active_record/database_validations/version'
|
4
4
|
|
5
|
+
require 'active_record/database_validations/mysql'
|
5
6
|
require 'active_record/validations/database_constraints'
|
6
7
|
require 'active_record/validations/string_truncator'
|
7
8
|
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module DatabaseValidations
|
3
|
+
module MySQL
|
4
|
+
TYPE_LIMITS = {
|
5
|
+
char: { type: :characters },
|
6
|
+
varchar: { type: :characters },
|
7
|
+
varbinary: { type: :bytes },
|
8
|
+
|
9
|
+
tinytext: { type: :bytes, maximum: 2 ** 8 - 1 },
|
10
|
+
text: { type: :bytes, maximum: 2 ** 16 - 1 },
|
11
|
+
mediumtext: { type: :bytes, maximum: 2 ** 24 - 1 },
|
12
|
+
longtext: { type: :bytes, maximum: 2 ** 32 - 1 },
|
13
|
+
|
14
|
+
tinyblob: { type: :bytes, maximum: 2 ** 8 - 1 },
|
15
|
+
blob: { type: :bytes, maximum: 2 ** 16 - 1 },
|
16
|
+
mediumblob: { type: :bytes, maximum: 2 ** 24 - 1 },
|
17
|
+
longblob: { type: :bytes, maximum: 2 ** 32 - 1 },
|
18
|
+
}
|
19
|
+
|
20
|
+
def self.column_size_limit(column)
|
21
|
+
@column_size_limit ||= {}
|
22
|
+
@column_size_limit[column] ||= begin
|
23
|
+
column_type = column.sql_type.sub(/\(.*\z/, '').gsub(/\s/, '_').to_sym
|
24
|
+
type_limit = TYPE_LIMITS.fetch(column_type, {})
|
25
|
+
|
26
|
+
[
|
27
|
+
column.limit || type_limit[:maximum],
|
28
|
+
type_limit[:type],
|
29
|
+
determine_encoding(column),
|
30
|
+
]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.column_range(column)
|
35
|
+
args = {}
|
36
|
+
unsigned = column.sql_type =~ / unsigned\z/
|
37
|
+
case column.type
|
38
|
+
when :decimal
|
39
|
+
args[:less_than] = maximum = 10 ** (column.precision - column.scale)
|
40
|
+
if unsigned
|
41
|
+
args[:greater_than_or_equal_to] = 0
|
42
|
+
else
|
43
|
+
args[:greater_than] = 0 - maximum
|
44
|
+
end
|
45
|
+
|
46
|
+
when :integer
|
47
|
+
args[:only_integer] = true
|
48
|
+
args[:less_than] = unsigned ? 1 << (column.limit * 8) : 1 << (column.limit * 8 - 1)
|
49
|
+
args[:greater_than_or_equal_to] = unsigned ? 0 : 0 - args[:less_than]
|
50
|
+
end
|
51
|
+
|
52
|
+
args
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.determine_encoding(column)
|
56
|
+
return nil unless column.text?
|
57
|
+
case column.collation
|
58
|
+
when /\Autf8/; Encoding::UTF_8
|
59
|
+
else raise NotImplementedError, "Don't know how to determine the Ruby encoding for MySQL's #{column.collation} collation."
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.requires_transcoding?(value, column_encoding = nil)
|
64
|
+
column_encoding.present? && column_encoding != value.encoding
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.value_for_column(value, column_encoding = nil)
|
68
|
+
value = value.to_s unless value.is_a?(String)
|
69
|
+
value.encode!('utf-8') if requires_transcoding?(value, column_encoding)
|
70
|
+
return value
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::NATIVE_DATABASE_TYPES[:string][:limit] = 191
|
@@ -0,0 +1 @@
|
|
1
|
+
ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::NATIVE_DATABASE_TYPES[:string][:limit] = 255
|
@@ -5,26 +5,15 @@ require 'active_model/validations/basic_multilingual_plane'
|
|
5
5
|
module ActiveRecord
|
6
6
|
module Validations
|
7
7
|
class DatabaseConstraintsValidator < ActiveModel::EachValidator
|
8
|
-
TYPE_LIMITS = {
|
9
|
-
char: { validator: ActiveModel::Validations::LengthValidator },
|
10
|
-
varchar: { validator: ActiveModel::Validations::LengthValidator },
|
11
|
-
varbinary: { validator: ActiveModel::Validations::BytesizeValidator },
|
12
|
-
|
13
|
-
tinytext: { validator: ActiveModel::Validations::BytesizeValidator, default_maximum: 2 ** 8 - 1 },
|
14
|
-
text: { validator: ActiveModel::Validations::BytesizeValidator, default_maximum: 2 ** 16 - 1 },
|
15
|
-
mediumtext: { validator: ActiveModel::Validations::BytesizeValidator, default_maximum: 2 ** 24 - 1 },
|
16
|
-
longtext: { validator: ActiveModel::Validations::BytesizeValidator, default_maximum: 2 ** 32 - 1 },
|
17
|
-
|
18
|
-
tinyblob: { validator: ActiveModel::Validations::BytesizeValidator, default_maximum: 2 ** 8 - 1 },
|
19
|
-
blob: { validator: ActiveModel::Validations::BytesizeValidator, default_maximum: 2 ** 16 - 1 },
|
20
|
-
mediumblob: { validator: ActiveModel::Validations::BytesizeValidator, default_maximum: 2 ** 24 - 1 },
|
21
|
-
longblob: { validator: ActiveModel::Validations::BytesizeValidator, default_maximum: 2 ** 32 - 1 },
|
22
|
-
}
|
23
|
-
|
24
8
|
attr_reader :constraints
|
25
9
|
|
26
10
|
VALID_CONSTRAINTS = Set[:size, :basic_multilingual_plane, :not_null, :range]
|
27
11
|
|
12
|
+
SIZE_VALIDATORS_FOR_TYPE = {
|
13
|
+
characters: ActiveModel::Validations::LengthValidator,
|
14
|
+
bytes: ActiveModel::Validations::BytesizeValidator,
|
15
|
+
}
|
16
|
+
|
28
17
|
def initialize(options = {})
|
29
18
|
@constraints = Set.new(Array.wrap(options[:in]) + Array.wrap(options[:with]))
|
30
19
|
@constraint_validators = {}
|
@@ -49,11 +38,8 @@ module ActiveRecord
|
|
49
38
|
return unless constraints.include?(:size)
|
50
39
|
return unless column.text? || column.binary?
|
51
40
|
|
52
|
-
|
53
|
-
|
54
|
-
validator_class = type_limit[:validator]
|
55
|
-
maximum = column.limit || type_limit[:default_maximum]
|
56
|
-
encoding = column.text? ? determine_encoding(column) : nil
|
41
|
+
maximum, type, encoding = ActiveRecord::DatabaseValidations::MySQL.column_size_limit(column)
|
42
|
+
validator_class = SIZE_VALIDATORS_FOR_TYPE[type]
|
57
43
|
|
58
44
|
if validator_class && maximum
|
59
45
|
validator_class.new(attributes: [column.name.to_sym], class: klass, maximum: maximum, encoding: encoding)
|
@@ -64,23 +50,9 @@ module ActiveRecord
|
|
64
50
|
return unless constraints.include?(:range)
|
65
51
|
return unless column.number?
|
66
52
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
args = { attributes: [column.name.to_sym], class: klass, allow_nil: true }
|
71
|
-
args[:less_than] = maximum = 10 ** (column.precision - column.scale)
|
72
|
-
if unsigned
|
73
|
-
args[:greater_than_or_equal_to] = 0
|
74
|
-
else
|
75
|
-
args[:greater_than] = 0 - maximum
|
76
|
-
end
|
77
|
-
ActiveModel::Validations::NumericalityValidator.new(args)
|
78
|
-
|
79
|
-
when :integer
|
80
|
-
maximum = unsigned ? 1 << (column.limit * 8) : 1 << (column.limit * 8 - 1)
|
81
|
-
minimum = unsigned ? 0 : 0 - maximum
|
82
|
-
ActiveModel::Validations::NumericalityValidator.new(attributes: [column.name.to_sym], class: klass, greater_than_or_equal_to: minimum, less_than: maximum, allow_nil: true, only_integer: true)
|
83
|
-
end
|
53
|
+
args = { attributes: [column.name.to_sym], class: klass, allow_nil: true }
|
54
|
+
args.merge!(ActiveRecord::DatabaseValidations::MySQL.column_range(column))
|
55
|
+
ActiveModel::Validations::NumericalityValidator.new(args)
|
84
56
|
end
|
85
57
|
|
86
58
|
def basic_multilingual_plane_validator(klass, column)
|
@@ -107,15 +79,6 @@ module ActiveRecord
|
|
107
79
|
validator.validate_each(record, attribute, value)
|
108
80
|
end
|
109
81
|
end
|
110
|
-
|
111
|
-
private
|
112
|
-
|
113
|
-
def determine_encoding(column)
|
114
|
-
case column.collation
|
115
|
-
when /\Autf8/; Encoding.find('utf-8')
|
116
|
-
else raise NotImplementedError, "Don't know how to determine the Ruby encoding for MySQL's #{column.collation} collation."
|
117
|
-
end
|
118
|
-
end
|
119
82
|
end
|
120
83
|
end
|
121
84
|
end
|
@@ -11,21 +11,14 @@ module ActiveRecord
|
|
11
11
|
return if self[field].nil?
|
12
12
|
|
13
13
|
column = self.class.columns_hash[field.to_s]
|
14
|
-
|
14
|
+
maximum, type, encoding = ActiveRecord::DatabaseValidations::MySQL.column_size_limit(column)
|
15
|
+
value = ActiveRecord::DatabaseValidations::MySQL.value_for_column(self[field], encoding)
|
15
16
|
|
16
|
-
case
|
17
|
-
when :
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
when :text
|
24
|
-
value = self[field].to_s
|
25
|
-
value.encode!('utf-8') if value.encoding != Encoding::UTF_8
|
26
|
-
if limit && value.bytesize > limit
|
27
|
-
self[field] = value.mb_chars.limit(limit).to_s
|
28
|
-
end
|
17
|
+
case type
|
18
|
+
when :characters
|
19
|
+
self[field] = value.slice(0, maximum) if maximum && value.length > maximum
|
20
|
+
when :bytes
|
21
|
+
self[field] = value.mb_chars.limit(maximum).to_s if maximum && value.bytesize > maximum
|
29
22
|
end
|
30
23
|
|
31
24
|
return # to make sure the callback chain doesn't halt
|
@@ -33,17 +26,6 @@ module ActiveRecord
|
|
33
26
|
return method_name
|
34
27
|
end
|
35
28
|
end
|
36
|
-
|
37
|
-
def self.mysql_textual_column_limit(column)
|
38
|
-
@mysql_textual_column_limits ||= {}
|
39
|
-
@mysql_textual_column_limits[column] ||= begin
|
40
|
-
raise ArgumentError, "Only UTF-8 textual columns are supported." unless column.text? && column.collation =~ /\Autf8_/
|
41
|
-
|
42
|
-
column_type = column.sql_type.sub(/\(.*\z/, '').gsub(/\s/, '_').to_sym
|
43
|
-
type_limit = ActiveRecord::Validations::DatabaseConstraintsValidator::TYPE_LIMITS.fetch(column_type, {})
|
44
|
-
column.limit || type_limit[:default_maximum]
|
45
|
-
end
|
46
|
-
end
|
47
29
|
end
|
48
30
|
end
|
49
31
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'active_record/database_validations/varchar_191'
|
3
|
+
|
4
|
+
class Varchar < ActiveRecord::Base; end
|
5
|
+
|
6
|
+
class VarcharDefaultSizeTest < Minitest::Test
|
7
|
+
def test_field_was_created_with_191_characters
|
8
|
+
ActiveRecord::Migration.suppress_messages do
|
9
|
+
ActiveRecord::Migration.create_table(:varchars, force: true, options: "CHARACTER SET utf8mb4") do |t|
|
10
|
+
t.string :string
|
11
|
+
end
|
12
|
+
|
13
|
+
# This will fail if the field is more than 767 bytes.
|
14
|
+
ActiveRecord::Migration.add_index(:varchars, :string, unique: true)
|
15
|
+
end
|
16
|
+
|
17
|
+
assert_match /\Autf8mb4_/, Varchar.columns_hash['string'].collation
|
18
|
+
assert_equal 191, Varchar.columns_hash['string'].limit
|
19
|
+
end
|
20
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-databasevalidations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Willem van Bergen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-12-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -116,6 +116,9 @@ files:
|
|
116
116
|
- lib/active_model/validations/not_null.rb
|
117
117
|
- lib/active_record/database_validations.rb
|
118
118
|
- lib/active_record/database_validations/locale/en.yml
|
119
|
+
- lib/active_record/database_validations/mysql.rb
|
120
|
+
- lib/active_record/database_validations/varchar_191.rb
|
121
|
+
- lib/active_record/database_validations/varchar_255.rb
|
119
122
|
- lib/active_record/database_validations/version.rb
|
120
123
|
- lib/active_record/validations/database_constraints.rb
|
121
124
|
- lib/active_record/validations/string_truncator.rb
|
@@ -129,6 +132,7 @@ files:
|
|
129
132
|
- test/not_null_validator_test.rb
|
130
133
|
- test/string_truncator_test.rb
|
131
134
|
- test/test_helper.rb
|
135
|
+
- test/varchar_default_size_test.rb
|
132
136
|
homepage: https://github.com/wvanbergen/activerecord-database_validations
|
133
137
|
licenses:
|
134
138
|
- MIT
|
@@ -162,3 +166,4 @@ test_files:
|
|
162
166
|
- test/not_null_validator_test.rb
|
163
167
|
- test/string_truncator_test.rb
|
164
168
|
- test/test_helper.rb
|
169
|
+
- test/varchar_default_size_test.rb
|