generated_schema_validations 0.6.1 → 0.7.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec2ac3a0961cac39ac98ad2b31f7c81d9858f50c0170c2f0ec49cf71df766806
4
- data.tar.gz: 117604c47ecae3fabd1c7199ea92e6b076d78b2131313e7998176e4d4d7bcd0b
3
+ metadata.gz: d49e52636e38031fc0717496547eebc4d6f92c04512830dcc5095b2c75f5215a
4
+ data.tar.gz: a2f01799b411e938aa8a79d6259353460e6d1180c3c5a86d42cb790cbc457620
5
5
  SHA512:
6
- metadata.gz: 716aa6fed3746fb60b25ec359eccfe282814ee86d3207e4bbac2001099a1f54bf61b552622e91c88046abb275b4b9c4495360e9e950c914fb55e7141625ef475
7
- data.tar.gz: e4669b5032d78f2b3583cee3895c99c61cdb020459853bd281e9e9f6f82d6dc7395eb8a24a6de67850545a70403dc500ccfcdc691f5a5479d8d81d16f40756d6
6
+ metadata.gz: b0718456b5e41a09fefffef2818a57d3b247f59043804877cfe115681a53da70a2d7682a8a7b5808740c24728b85e51f4fac95fb2035c6dd2411525a34edaabb
7
+ data.tar.gz: b46f4a7c41e0e926ac4a20e9f494fe79dc231f97e87c52780996fb192cc015c585dfb7f14c67ce22eb080b7ee854fb3c9303db72e937a9e4b087c25b18e87217
data/README.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  This Gem helps to transfer the defaults from the database schema to the rails validations. To do this, it uses the information in schema.rb and transfers it to a concern file. This file should be included in the version control.
4
4
 
5
+ ## Requirements
6
+
7
+ * Ruby 3.2 or newer
8
+ * Rails 7.2 to 8.1
9
+ * `db/schema.rb` as schema format (`structure.sql` is not supported)
10
+
5
11
  ## Installation
6
12
 
7
13
  Add this line to your application's Gemfile:
@@ -45,7 +51,7 @@ class User < ApplicationRecord
45
51
 
46
52
  enum :actived, no: 0, yes: 1
47
53
 
48
- validate :email_address, email_format: true
54
+ validates :email_address, email_format: true
49
55
 
50
56
  schema_validations # to use auto generated validations
51
57
 
@@ -90,21 +96,37 @@ It is hardcoded, that this will only run on development environment.
90
96
  ```ruby
91
97
  t.text :stuff, null: false
92
98
  # results in
93
- validate :stuff, presence: true
99
+ validates :stuff, presence: true
94
100
 
95
101
  t.string :stuff, limit: 10
96
102
  # results in
97
- validate :stuff, length: { maximum: 10 }
103
+ validates :stuff, length: { maximum: 10, allow_nil: true }
98
104
 
99
105
  t.integer :stuff
100
106
  # results in
101
- validate :stuff, numericality: true
107
+ validates :stuff, numericality: { only_integer: true, greater_than_or_equal_to: -2147483648, less_than: 2147483648, allow_nil: true }
102
108
  ```
103
109
 
104
110
  You can watch changes on `schema_validations.rb` to understand the generated validations.
105
111
 
106
112
  ## Changelog
107
113
 
114
+ ### 0.7.0
115
+
116
+ * requires Ruby 3.2 and Rails 7.2 or newer
117
+ * supports all PostgreSQL column types of Rails (e.g. `citext`, `money`, `bit`, `enum`, `hstore`, ranges and geometric types)
118
+ * `enum` columns are validated to contain a value of the PostgreSQL enum type
119
+ * `serial` and `bigserial` columns are not validated for presence, because the database sets them
120
+ * unique constraints are validated like unique indexes
121
+ * not null `json`, `jsonb`, `hstore` and array columns accept empty values (`{}` or `[]`)
122
+ * the generated file contains only the validator classes used by the tables
123
+ * `bigint` columns and `integer` columns with `limit` get a range validation
124
+ * the generated file is the same on all Ruby versions (`Hash#inspect` changed with Ruby 3.4)
125
+ * abstract classes do not need `schema_validations` anymore
126
+ * ignore `virtual` columns, check and exclusion constraints
127
+ * skip generating if the schema format is not ruby
128
+ * adds tests
129
+
108
130
  ### 0.6.1
109
131
 
110
132
  * adds support for Rails 8.1 (integer range validations no longer use the private `ActiveRecord::Type::Integer#range`, which was removed in Rails 8.1)
@@ -190,6 +212,16 @@ require 'generated_schema_validations/capistrano_tasks'
190
212
 
191
213
  * Add column type `binary`
192
214
 
215
+ ## Development
216
+
217
+ The specs run with SQLite. Set `DATABASE_URL` to run the PostgreSQL specs too:
218
+
219
+ $ DATABASE_URL=postgres://user:password@localhost/generated_schema_validations_test bundle exec rspec
220
+
221
+ Other Rails versions are tested with the gemfiles in `gemfiles/`:
222
+
223
+ $ BUNDLE_GEMFILE=gemfiles/rails_7.2.gemfile bundle exec rspec
224
+
193
225
  ## Contributing
194
226
 
195
227
  Bug reports and pull requests are welcome on GitHub at https://github.com/Lichtbit/generated_schema_validations.
@@ -19,11 +19,13 @@ namespace :generated_schema_validations do
19
19
  with(
20
20
  rails_env: fetch(:rails_env),
21
21
  rails_groups: fetch(:rails_assets_groups),
22
- disable_database_environment_check: 1
22
+ disable_database_environment_check: 1,
23
23
  ) do
24
24
  dump_output = capture(:rake, 'db:validation_dump_direct').strip
25
25
 
26
- if dump_output != local_content
26
+ if dump_output == local_content
27
+ info '✔ Schema OK'
28
+ else
27
29
  warn '❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌'
28
30
  warn 'The online version of the automatically generated validations differs from the local version.'
29
31
  warn 'Please check immediately.'
@@ -41,7 +43,7 @@ namespace :generated_schema_validations do
41
43
  diffs.each do |piece|
42
44
  hunk = Diff::LCS::Hunk.new(
43
45
  old_lines, new_lines, piece,
44
- 3, # context lines
46
+ 3, # context lines
45
47
  file_length_difference
46
48
  )
47
49
 
@@ -54,8 +56,6 @@ namespace :generated_schema_validations do
54
56
  end
55
57
  end
56
58
  warn '❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌❌'
57
- else
58
- info '✔ Schema OK'
59
59
  end
60
60
  end
61
61
  end
@@ -1,23 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'stringio'
4
+
3
5
  class GeneratedSchemaValidations::DumpChecker < GeneratedSchemaValidations::Dumper
4
6
  def self.read_schema_content
5
7
  stream = StringIO.new
6
-
7
- if ActiveRecord::VERSION::STRING >= "7.2"
8
- ActiveRecord::SchemaDumper.dump(
9
- ActiveRecord::Base.connection_pool,
10
- stream
11
- )
12
- else
13
- ActiveRecord::SchemaDumper.dump(
14
- ActiveRecord::Base.connection,
15
- stream
16
- )
17
- end
18
-
19
- stream.rewind
20
- stream.read
8
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, stream)
9
+ stream.string
21
10
  end
22
11
 
23
12
  def write_schema_validations(template_ruby)
@@ -1,11 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'tempfile'
4
+
3
5
  class GeneratedSchemaValidations::Dumper
6
+ # Validators from validators/, added to the generated file only if a table uses them
7
+ VALIDATORS = %i[date_time_in_db_range date_in_db_range time_in_db_range not_null].freeze
8
+
4
9
  def self.generate
5
10
  file = Tempfile.new(['schema', '.rb'])
6
11
  begin
7
- schema_content = read_schema_content.gsub(/ActiveRecord::Schema(\[[^\]]+\])?/, self.name)
8
- raise 'The scheme is not well-formed.' if schema_content.include?('ActiveRecord')
12
+ schema_content = read_schema_content.gsub(/ActiveRecord::Schema(\[[^\]]+\])?/, name)
13
+ raise 'The schema is not well-formed.' if schema_content.include?('ActiveRecord')
9
14
 
10
15
  file.write(schema_content)
11
16
  file.close
@@ -20,20 +25,21 @@ class GeneratedSchemaValidations::Dumper
20
25
  File.read(Rails.root.join('db/schema.rb'))
21
26
  end
22
27
 
23
- def self.define(info = {}, &block)
24
- new.define(info, &block)
28
+ def self.define(info = {}, &)
29
+ new.define(info, &)
25
30
  end
26
31
 
27
- def define(info, &block)
28
- instance_eval(&block)
32
+ def define(info, &)
33
+ instance_eval(&)
29
34
 
30
35
  template_ruby = File.read(File.expand_path('template.rb', File.dirname(__FILE__)))
31
36
  template_ruby.gsub!('VERSION_INFO', info[:version].to_s)
32
37
  indention_spaces = template_ruby.match(/( +)TABLE_VALIDATIONS/)[1]
33
- table_validations_ruby = @table_validations_ruby.lines.map do |line|
38
+ table_validations_ruby = @table_validations_ruby.to_s.lines.map do |line|
34
39
  line.strip.present? ? "#{indention_spaces}#{line}" : "\n"
35
- end.join
40
+ end.join.delete_prefix("\n").chomp
36
41
  template_ruby.gsub!("#{indention_spaces}TABLE_VALIDATIONS", table_validations_ruby)
42
+ template_ruby.sub!("\n\n VALIDATOR_CLASSES", validator_classes_ruby)
37
43
 
38
44
  write_schema_validations(template_ruby)
39
45
  end
@@ -44,12 +50,30 @@ class GeneratedSchemaValidations::Dumper
44
50
  File.write(Rails.root.join('app/models/concerns/schema_validations.rb'), template_ruby)
45
51
  end
46
52
 
47
- def create_table(table_name, *, &block)
53
+ def create_table(table_name, *, &)
54
+ table = GeneratedSchemaValidations::Table.new(table_name, enum_types: @enum_types || {}, &)
55
+ @used_validators = (@used_validators || []) | table.validators
48
56
  @table_validations_ruby ||= ''
49
- @table_validations_ruby += GeneratedSchemaValidations::Table.new(table_name, &block).to_s
57
+ @table_validations_ruby += table.to_s
58
+ end
59
+
60
+ def create_enum(name, values)
61
+ @enum_types ||= {}
62
+ @enum_types[name.to_s] = values
50
63
  end
51
64
 
52
65
  def do_nothing(*); end
53
66
  alias enable_extension do_nothing
67
+ alias create_schema do_nothing
54
68
  alias add_foreign_key do_nothing
69
+
70
+ private
71
+
72
+ def validator_classes_ruby
73
+ (VALIDATORS & @used_validators.to_a).map do |validator|
74
+ ruby = File.read(File.expand_path("validators/#{validator}_validator.rb", __dir__))
75
+ ruby = ruby.delete_prefix("# frozen_string_literal: true\n\n")
76
+ "\n\n#{ruby.lines.map { |line| line.strip.present? ? " #{line}" : line }.join.chomp}"
77
+ end.join
78
+ end
55
79
  end
@@ -1,29 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class GeneratedSchemaValidations::Table
4
+ # Hash#inspect changed with Ruby 3.4. Use its new style on all versions to get the same file everywhere.
5
+ def self.inspect_value(value)
6
+ return value.inspect unless value.is_a?(Hash)
7
+
8
+ "{#{value.map { |key, v| "#{key}: #{inspect_value(v)}" }.join(', ')}}"
9
+ end
10
+
4
11
  Validation = Struct.new(:attribute, :validator, :options) do
5
12
  def to_s
6
- "validates_with_filter #{attribute.to_sym.inspect}, #{{ validator => options }.inspect}"
13
+ "validates_with_filter #{attribute.to_sym.inspect}, " \
14
+ "#{GeneratedSchemaValidations::Table.inspect_value(validator => options)}"
7
15
  end
8
16
  end
9
17
 
10
18
  ValidationWithoutEnum = Struct.new(:attribute, :validator, :options) do
11
19
  def to_s
12
- "validates_with_filter #{attribute.to_sym.inspect}, #{{ validator => options }.inspect} unless enums.include?(#{attribute.to_sym.inspect})"
20
+ "#{Validation.new(attribute, validator, options)} unless enums.include?(#{attribute.to_sym.inspect})"
13
21
  end
14
22
  end
15
23
 
24
+ # Valid values of these types can be blank, e.g. an empty hash or an empty bit string.
25
+ BLANK_VALUE_TYPES = %i[json jsonb hstore bit_varying ltree tsvector].freeze
26
+
16
27
  attr_reader :table_name
17
28
 
18
- def initialize(table_name, &block)
29
+ def initialize(table_name, enum_types: {}, &)
19
30
  @table_name = table_name
31
+ @enum_types = enum_types
20
32
  @column_names = []
21
33
  @possible_belongs_to_not_null_columns = []
22
34
  @bad_indexes = []
23
35
  @unique_indexes = []
24
36
  @validations = []
25
37
 
26
- instance_eval(&block)
38
+ instance_eval(&)
27
39
  end
28
40
 
29
41
  def to_s
@@ -45,6 +57,10 @@ class GeneratedSchemaValidations::Table
45
57
  "#{string}end\n"
46
58
  end
47
59
 
60
+ def validators
61
+ @validations.map(&:validator).uniq
62
+ end
63
+
48
64
  def validates(attribute, validator, options = {})
49
65
  @validations.push(Validation.new(attribute, validator, options))
50
66
  end
@@ -61,8 +77,8 @@ class GeneratedSchemaValidations::Table
61
77
  @possible_belongs_to_not_null_columns.push(name.to_sym) if datatype.in?(%i[bigint integer uuid])
62
78
  if datatype == :boolean
63
79
  validates name, :inclusion, in: [true, false], message: :blank
64
- elsif datatype.in?(%i[json jsonb])
65
- validates name, :exclusion, in: [nil], message: :blank
80
+ elsif datatype.in?(BLANK_VALUE_TYPES) || column_options[:array]
81
+ validates name, :not_null
66
82
  else
67
83
  validates name, :presence
68
84
  end
@@ -74,27 +90,48 @@ class GeneratedSchemaValidations::Table
74
90
 
75
91
  def bigint(name, column_options = {})
76
92
  null_validation(:bigint, name, column_options)
77
-
78
- validates_without_enum name, :numericality, allow_nil: true
93
+ integer_range_validation(name, column_options, default_limit: 8)
79
94
  end
80
95
 
81
96
  def integer(name, column_options = {})
82
97
  null_validation(:integer, name, column_options)
98
+ integer_range_validation(name, column_options, default_limit: ::ActiveRecord::Type::Integer::DEFAULT_LIMIT)
99
+ end
100
+
101
+ def serial(name, column_options = {})
102
+ # The database sets the value on insert, so it is nil before.
103
+ null_validation(:serial, name, column_options.except(:null))
104
+ integer_range_validation(name, column_options, default_limit: 4)
105
+ end
106
+
107
+ def bigserial(name, column_options = {})
108
+ null_validation(:bigserial, name, column_options.except(:null))
109
+ integer_range_validation(name, column_options, default_limit: 8)
110
+ end
83
111
 
112
+ def oid(name, column_options = {})
113
+ null_validation(:oid, name, column_options)
84
114
  return if column_options[:array]
85
115
 
86
- # Same bounds as ActiveModel::Type::Integer (min...max), computed here because
87
- # Rails 8.1 removed the private #range method this used to call.
88
- max_value = 1 << ((::ActiveRecord::Type::Integer::DEFAULT_LIMIT * 8) - 1)
89
- options = { allow_nil: true, only_integer: true, greater_than_or_equal_to: -max_value, less_than: max_value }
116
+ validates name, :numericality, allow_nil: true, only_integer: true,
117
+ greater_than_or_equal_to: 0, less_than: 1 << 32
118
+ end
90
119
 
91
- validates_without_enum name, :numericality, options
120
+ def money(name, column_options = {})
121
+ null_validation(:money, name, column_options)
122
+ return if column_options[:array]
123
+
124
+ # PostgreSQL stores money as 64 bit integer of the smallest currency unit
125
+ limit = (1 << 63) / (10**(column_options[:scale] || 2))
126
+ validates name, :numericality, allow_nil: true, greater_than_or_equal_to: -limit, less_than_or_equal_to: limit
92
127
  end
93
128
 
94
129
  def datetime(name, column_options = {})
95
130
  null_validation(:datetime, name, column_options)
96
131
  validates name, :date_time_in_db_range
97
132
  end
133
+ alias timestamp datetime
134
+ alias timestamptz datetime
98
135
 
99
136
  def date(name, column_options = {})
100
137
  null_validation(:date, name, column_options)
@@ -153,6 +190,42 @@ class GeneratedSchemaValidations::Table
153
190
 
154
191
  validates name, :length, allow_nil: true, maximum: column_options[:limit]
155
192
  end
193
+ alias citext text
194
+
195
+ def bit(name, column_options = {})
196
+ null_validation(:bit, name, column_options)
197
+ return if column_options[:array]
198
+
199
+ validates name, :format, allow_nil: true, with: /\A[01]*\z/
200
+ validates name, :length, allow_nil: true, is: column_options[:limit] || 1
201
+ end
202
+
203
+ def bit_varying(name, column_options = {})
204
+ null_validation(:bit_varying, name, column_options)
205
+ return if column_options[:array]
206
+
207
+ validates name, :format, allow_nil: true, with: /\A[01]*\z/
208
+ validates name, :length, allow_nil: true, maximum: column_options[:limit] if column_options[:limit]
209
+ end
210
+
211
+ def enum(name, column_options = {})
212
+ null_validation(:enum, name, column_options)
213
+ return if column_options[:array]
214
+
215
+ values = @enum_types[column_options[:enum_type].to_s]
216
+ validates_without_enum name, :inclusion, allow_nil: true, in: values if values
217
+ end
218
+
219
+ # Columns without own validations. The database checks the format, the not null constraint is validated.
220
+ %i[
221
+ daterange numrange tsrange tstzrange int4range int8range
222
+ hstore inet cidr macaddr ltree tsvector interval
223
+ point line lseg box path polygon circle
224
+ ].each do |datatype|
225
+ define_method(datatype) do |name, column_options = {}|
226
+ null_validation(datatype, name, column_options)
227
+ end
228
+ end
156
229
 
157
230
  def index(names, index_options = {})
158
231
  names = [names] unless names.is_a?(Array)
@@ -166,7 +239,29 @@ class GeneratedSchemaValidations::Table
166
239
  end
167
240
  end
168
241
 
242
+ def unique_constraint(names, _constraint_options = {})
243
+ index(names, unique: true)
244
+ end
245
+
169
246
  def geography(*)
170
247
  # do nothing
171
248
  end
249
+
250
+ def do_nothing(*); end
251
+ alias virtual do_nothing # generated columns can not be written
252
+ alias check_constraint do_nothing
253
+ alias exclusion_constraint do_nothing
254
+
255
+ private
256
+
257
+ def integer_range_validation(name, column_options, default_limit:)
258
+ return if column_options[:array]
259
+
260
+ # Same bounds as ActiveModel::Type::Integer (min...max), computed here because
261
+ # Rails 8.1 removed the private #range method this used to call.
262
+ max_value = 1 << (((column_options[:limit] || default_limit) * 8) - 1)
263
+ options = { allow_nil: true, only_integer: true, greater_than_or_equal_to: -max_value, less_than: max_value }
264
+
265
+ validates_without_enum name, :numericality, options
266
+ end
172
267
  end
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Rake::Task['db:schema:dump'].enhance do
4
+ # The validations are generated from db/schema.rb, so there is nothing to do with structure.sql.
5
+ next unless (ENV['SCHEMA_FORMAT'] || ActiveRecord.schema_format).to_s == 'ruby'
6
+
4
7
  require 'generated_schema_validations'
5
8
  require_relative 'dumper'
6
9
  require_relative 'table'
@@ -26,9 +26,10 @@ module SchemaValidations
26
26
  if defined?(Rails::Railtie) && (Rails.env.development? || Rails.env.test?)
27
27
  TracePoint.trace(:end) do |t|
28
28
  if t.self.respond_to?(:schema_validations_called) &&
29
- t.self < ApplicationRecord &&
30
- !t.self.schema_validations_called &&
31
- !SchemaValidations.relaxed?
29
+ t.self < ApplicationRecord &&
30
+ !t.self.abstract_class? &&
31
+ !t.self.schema_validations_called &&
32
+ !SchemaValidations.relaxed?
32
33
  raise "#{t.self}: schema_validations or skip_schema_validations missing"
33
34
  end
34
35
  end
@@ -73,7 +74,7 @@ module SchemaValidations
73
74
  names.each do |name|
74
75
  next if name.to_sym.in?(schema_validations_excluded_columns)
75
76
  next unless defined?(Rails::Railtie)
76
- next unless (Rails.env.development? || Rails.env.test?)
77
+ next unless Rails.env.development? || Rails.env.test?
77
78
 
78
79
  raise "Unique index with where clause is outside the scope of this gem.\n\n" \
79
80
  "You can exclude this column: `schema_validations exclude: [:#{name}]`"
@@ -117,39 +118,5 @@ module SchemaValidations
117
118
  end
118
119
  end
119
120
 
120
- class DateTimeInDbRangeValidator < ActiveModel::EachValidator
121
- def validate_each(record, attr_name, value)
122
- return if value.nil?
123
- return unless value.is_a?(DateTime) || value.is_a?(Time)
124
- return if value.year.between?(-4711, 294_275) # see https://www.postgresql.org/docs/9.3/datatype-datetime.html
125
-
126
- record.errors.add(attr_name, :invalid)
127
- end
128
- end
129
-
130
- class DateInDbRangeValidator < ActiveModel::EachValidator
131
- def validate_each(record, attr_name, value)
132
- return if value.nil?
133
- return unless value.is_a?(Date)
134
- return if value.year.between?(-4711, 5_874_896) # see https://www.postgresql.org/docs/9.3/datatype-datetime.html
135
-
136
- record.errors.add(attr_name, :invalid)
137
- end
138
- end
139
-
140
- class TimeInDbRangeValidator < ActiveModel::EachValidator
141
- def validate_each(record, attr_name, value)
142
- return if value.nil?
143
-
144
- unless value.is_a?(Time) || value.is_a?(ActiveSupport::TimeWithZone)
145
- record.errors.add(attr_name, :invalid)
146
- return
147
- end
148
-
149
- # PostgreSQL allows only times in one day
150
- return if value.to_date == Date.new(2000, 1, 1)
151
-
152
- record.errors.add(attr_name, :invalid)
153
- end
154
- end
121
+ VALIDATOR_CLASSES
155
122
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DateInDbRangeValidator < ActiveModel::EachValidator
4
+ def validate_each(record, attr_name, value)
5
+ return if value.nil?
6
+ return unless value.is_a?(Date)
7
+ return if value.year.between?(-4711, 5_874_896) # see https://www.postgresql.org/docs/9.3/datatype-datetime.html
8
+
9
+ record.errors.add(attr_name, :invalid)
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DateTimeInDbRangeValidator < ActiveModel::EachValidator
4
+ def validate_each(record, attr_name, value)
5
+ return if value.nil?
6
+ return unless value.is_a?(DateTime) || value.is_a?(Time)
7
+ return if value.year.between?(-4711, 294_275) # see https://www.postgresql.org/docs/9.3/datatype-datetime.html
8
+
9
+ record.errors.add(attr_name, :invalid)
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Like presence, but accepts blank values like an empty hash or array
4
+ class NotNullValidator < ActiveModel::EachValidator
5
+ def validate_each(record, attr_name, value)
6
+ record.errors.add(attr_name, :blank) if value.nil?
7
+ end
8
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TimeInDbRangeValidator < ActiveModel::EachValidator
4
+ def validate_each(record, attr_name, value)
5
+ return if value.nil?
6
+
7
+ unless value.is_a?(Time) || value.is_a?(ActiveSupport::TimeWithZone)
8
+ record.errors.add(attr_name, :invalid)
9
+ return
10
+ end
11
+
12
+ # PostgreSQL allows only times in one day
13
+ return if value.to_date == Date.new(2000, 1, 1)
14
+
15
+ record.errors.add(attr_name, :invalid)
16
+ end
17
+ end
metadata CHANGED
@@ -1,31 +1,65 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: generated_schema_validations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Georg Limbach
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-17 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2026-10-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '7.2'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '9'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '7.2'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '9'
33
+ - !ruby/object:Gem::Dependency
34
+ name: railties
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '7.2'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '9'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '7.2'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '9'
13
53
  description: After each migration it generates a file with some validations. Each
14
- active record should include this file and can uns generated validations.
54
+ active record should include this file and can use the generated validations.
15
55
  email:
16
56
  - georg.limbach@lichtbit.com
17
57
  executables: []
18
58
  extensions: []
19
59
  extra_rdoc_files: []
20
60
  files:
21
- - ".gitignore"
22
- - ".rspec"
23
- - Gemfile
24
- - Gemfile.lock
25
61
  - LICENSE.txt
26
62
  - README.md
27
- - Rakefile
28
- - generated_schema_validations.gemspec
29
63
  - lib/generated_schema_validations.rb
30
64
  - lib/generated_schema_validations/capistrano_tasks.rb
31
65
  - lib/generated_schema_validations/dump_checker.rb
@@ -34,12 +68,17 @@ files:
34
68
  - lib/generated_schema_validations/table.rb
35
69
  - lib/generated_schema_validations/tasks.rb
36
70
  - lib/generated_schema_validations/template.rb
71
+ - lib/generated_schema_validations/validators/date_in_db_range_validator.rb
72
+ - lib/generated_schema_validations/validators/date_time_in_db_range_validator.rb
73
+ - lib/generated_schema_validations/validators/not_null_validator.rb
74
+ - lib/generated_schema_validations/validators/time_in_db_range_validator.rb
37
75
  homepage: https://github.com/Lichtbit/generated_schema_validations
38
76
  licenses:
39
77
  - MIT
40
78
  metadata:
41
- homepage_uri: https://github.com/Lichtbit/generated_schema_validations
42
79
  source_code_uri: https://github.com/Lichtbit/generated_schema_validations
80
+ changelog_uri: https://github.com/Lichtbit/generated_schema_validations#changelog
81
+ rubygems_mfa_required: 'true'
43
82
  post_install_message:
44
83
  rdoc_options: []
45
84
  require_paths:
@@ -48,7 +87,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
48
87
  requirements:
49
88
  - - ">="
50
89
  - !ruby/object:Gem::Version
51
- version: 2.3.0
90
+ version: '3.2'
52
91
  required_rubygems_version: !ruby/object:Gem::Requirement
53
92
  requirements:
54
93
  - - ">="
data/.gitignore DELETED
@@ -1,12 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
-
10
- # rspec failure tracking
11
- .rspec_status
12
- generated_schema_validations-*.gem
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/Gemfile DELETED
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- # Specify your gem's dependencies in generated_schema_validations.gemspec
6
- gemspec
7
-
8
- gem 'rake', '~> 12.0'
9
- gem 'rspec', '~> 3.0'
data/Gemfile.lock DELETED
@@ -1,34 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- generated_schema_validations (0.2.0)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- diff-lcs (1.4.4)
10
- rake (12.3.3)
11
- rspec (3.10.0)
12
- rspec-core (~> 3.10.0)
13
- rspec-expectations (~> 3.10.0)
14
- rspec-mocks (~> 3.10.0)
15
- rspec-core (3.10.1)
16
- rspec-support (~> 3.10.0)
17
- rspec-expectations (3.10.1)
18
- diff-lcs (>= 1.2.0, < 2.0)
19
- rspec-support (~> 3.10.0)
20
- rspec-mocks (3.10.2)
21
- diff-lcs (>= 1.2.0, < 2.0)
22
- rspec-support (~> 3.10.0)
23
- rspec-support (3.10.3)
24
-
25
- PLATFORMS
26
- ruby
27
-
28
- DEPENDENCIES
29
- generated_schema_validations!
30
- rake (~> 12.0)
31
- rspec (~> 3.0)
32
-
33
- BUNDLED WITH
34
- 2.1.4
data/Rakefile DELETED
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/gem_tasks'
4
- require 'rspec/core/rake_task'
5
-
6
- RSpec::Core::RakeTask.new(:spec)
7
-
8
- task default: :spec
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- Gem::Specification.new do |spec|
4
- spec.name = 'generated_schema_validations'
5
- spec.version = '0.6.1'
6
- spec.authors = ['Georg Limbach']
7
- spec.email = ['georg.limbach@lichtbit.com']
8
-
9
- spec.summary = 'Generate rails validations from schema.rb file'
10
- spec.description = 'After each migration it generates a file with some validations. Each active record should ' \
11
- 'include this file and can uns generated validations.'
12
- spec.homepage = 'https://github.com/Lichtbit/generated_schema_validations'
13
- spec.license = 'MIT'
14
- spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
15
-
16
- spec.metadata['homepage_uri'] = spec.homepage
17
- spec.metadata['source_code_uri'] = 'https://github.com/Lichtbit/generated_schema_validations'
18
-
19
- # Specify which files should be added to the gem when it is released.
20
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
- spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
- end
24
- spec.require_paths = ['lib']
25
- end