active_record_auto_validations 0.0.1 → 0.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0f8bdbdb695e04d78b7e03badf31c5bda8ba83a299c64e05bd674f7ebee5309
4
- data.tar.gz: d0db6d6605807a16c0cef57488011e440da7f148c74e499c69473576054df564
3
+ metadata.gz: d82df8dff2fe3dd3b03523499df936304e262721ea812792749059393602b662
4
+ data.tar.gz: 2f34c86ef85893c79c215c8227458ab42e0503c46cca848527ddef8f71dc1642
5
5
  SHA512:
6
- metadata.gz: 3a2888303307eb73773a4136d5d1d4d30f1b25476d6ee5047b8a6399e2d94e36944d8d0c1c3cc9765f16e4879425fc185fcd39c001e15024f567bc94f13f25bb
7
- data.tar.gz: ab897c30fa03d44438ae8393bf35583bc1c471b0dfe046a94282f14c54b8be157f03a063b9df5bc4f02c575e6eaf3eed0b00b3651383c43d6dc230afba9d78be
6
+ metadata.gz: a3cee5f5a370c5c800efc0976eea8a350bb08d75bb9eab11d6fab4446d7ca2eab18096777daeff71f790bf51dd228c2a6c12e1d961c4eeda03541f1055f2be11
7
+ data.tar.gz: f2eb99db19909b63de9de904a86f0dc9b119117c6391b27d5595e8b0ca0ad5c4013b3bfa164a2314dafe393e315ca148db60206f2a99fb79d593da69c382608f
data/README.md CHANGED
@@ -21,6 +21,32 @@ Or install it yourself as:
21
21
  $ gem install active_record_auto_validations
22
22
  ```
23
23
 
24
+ Add this initializer:
25
+ ```ruby
26
+ Rails.autoloaders.main.on_load do |_klass_name, klass, _defined_by_file|
27
+ ActiveRecordAutoValidations::OnLoad.execute!(klass: klass)
28
+ end
29
+ ```
30
+
31
+ To run auto-validations on all models you can add something like this to ApplicationRecord:
32
+ ```ruby
33
+ class ApplicationRecord < ActiveRecord::Base
34
+ primary_abstract_class
35
+
36
+ def self.inherited(child)
37
+ super
38
+ child.include ActiveRecordAutoValidations::ModelConcern
39
+ end
40
+ end
41
+ ```
42
+
43
+ To run auto-validations on a single model you can do this:
44
+ ```ruby
45
+ class Project < ApplicationRecord
46
+ include ActiveRecordAutoValidations::ModelConcern
47
+ end
48
+ ```
49
+
24
50
  ## Contributing
25
51
  Contribution directions go here.
26
52
 
@@ -0,0 +1,11 @@
1
+ module ActiveRecordAutoValidations::ModelConcern
2
+ extend ActiveSupport::Concern
3
+
4
+ included do |base|
5
+ # The actual loading needs to be done after the model has been fully loaded,
6
+ # so this is actually done through a "on_load"-callback in an initializer.
7
+ #
8
+ # Kind of a hack but haven't been able to find another way of doing this
9
+ base.instance_variable_set(:@active_record_auto_validations_marked, true)
10
+ end
11
+ end
@@ -0,0 +1,41 @@
1
+ class ActiveRecordAutoValidations::AutoUniqueIndex
2
+ def self.execute!(**args)
3
+ ActiveRecordAutoValidations::AutoUniqueIndex.new(**args).perform
4
+ end
5
+
6
+ attr_reader :columns, :index, :model_class
7
+
8
+ def initialize(columns:, index:, model_class:)
9
+ @columns = columns
10
+ @index = index
11
+ @model_class = model_class
12
+ end
13
+
14
+ def perform
15
+ model_class.validates last_column_name.to_sym, **validates_args
16
+ end
17
+
18
+ def index_columns
19
+ @index_columns ||= index.columns.map { |column_name| columns.find { |column| column.name == column_name } }
20
+ end
21
+
22
+ def last_column_name
23
+ @last_column_name ||= index.columns.last
24
+ end
25
+
26
+ def validates_args
27
+ rest_of_columns = index.columns.clone
28
+ rest_of_columns.pop
29
+
30
+ validates_args = {}
31
+
32
+ if rest_of_columns.length.positive?
33
+ validates_args[:uniqueness] ||= {}
34
+ validates_args[:uniqueness][:scope] = rest_of_columns
35
+ end
36
+
37
+ validates_args[:allow_blank] = true if index_columns.any?(&:null)
38
+ validates_args[:uniqueness] ||= true
39
+ validates_args
40
+ end
41
+ end
@@ -0,0 +1,103 @@
1
+ class ActiveRecordAutoValidations::AutoValidateModelClass
2
+ attr_reader :model_class
3
+
4
+ def self.execute!(model_class:)
5
+ ActiveRecordAutoValidations::AutoValidateModelClass.new(model_class: model_class).perform
6
+ end
7
+
8
+ def initialize(model_class:)
9
+ @model_class = model_class
10
+ end
11
+
12
+ def perform
13
+ check_if_already_loaded_on_model_class!
14
+ register_loaded_on_model_class!
15
+
16
+ begin
17
+ columns
18
+ indexes
19
+ rescue ActiveRecord::StatementInvalid => e
20
+ # Database is probably not running - we need to ignore this to make stuff like db:migrate, db:schema:load work
21
+ Rails.logger.error { "AutoValidate: Ignoring error while loading columns, because database might not be initialized: #{e.message}" }
22
+ return
23
+ end
24
+
25
+ insert_active_record_auto_validations_from_columns!
26
+ insert_active_record_auto_validations_from_indexes!
27
+ end
28
+
29
+ def check_if_already_loaded_on_model_class!
30
+ auto_validations_loaded = model_class.instance_variable_get(:@_active_record_auto_validations_loaded)
31
+
32
+ raise "AutoValidations already loaded for #{model_class.name}" if auto_validations_loaded
33
+ end
34
+
35
+ def register_loaded_on_model_class!
36
+ model_class.instance_variable_set(:@_active_record_auto_validations_loaded, true)
37
+ end
38
+
39
+ def columns
40
+ @columns ||= model_class.columns
41
+ end
42
+
43
+ def indexes
44
+ @indexes ||= model_class.connection.indexes(model_class.table_name)
45
+ end
46
+
47
+ def insert_active_record_auto_validations_from_columns!
48
+ columns.each do |column|
49
+ next if column.name == "id" || column.name == "created_at" || column.name == "updated_at"
50
+
51
+ auto_validate_pesence_on_column!(column) if auto_validate_presence_on_column?(column)
52
+ auto_validate_max_length_on_column!(column) if auto_validate_max_length_on_column?(column)
53
+ end
54
+ end
55
+
56
+ def insert_active_record_auto_validations_from_indexes!
57
+ indexes.each do |index|
58
+ next unless index.unique
59
+
60
+ # Dont add uniqueness validation to ActsAsList position columns
61
+ if index.columns.include?("position") && model_class.respond_to?(:acts_as_list_top)
62
+ Rails.logger.info { "AutoValidate: Skipping unique validation on #{model_class.table_name}##{index.columns.join(",")} because it looks like ActsAsList" }
63
+ next
64
+ end
65
+
66
+ ActiveRecordAutoValidations::AutoUniqueIndex.execute!(columns: columns, model_class: model_class, index: index)
67
+ end
68
+ end
69
+
70
+ def presence_validation_exists_on_column?(column)
71
+ model_class.validators_on(column.name.to_sym).each do |validator|
72
+ return true if validator.kind == :presence
73
+ end
74
+
75
+ false
76
+ end
77
+
78
+ def auto_validate_presence_on_column?(column)
79
+ !column.null && !column.name.end_with?("_id") && column.default.nil? && !presence_validation_exists_on_column?(column)
80
+ end
81
+
82
+ def auto_validate_pesence_on_column!(column)
83
+ Rails.logger.info { "AutoValidate: Adding presence validation to #{model_class.table_name}##{column.name}" }
84
+ model_class.validates column.name.to_sym, presence: true
85
+ end
86
+
87
+ def auto_validate_max_length_on_column?(column)
88
+ column.type == :string && column.limit && !max_length_validation_exists_on_column?(column)
89
+ end
90
+
91
+ def auto_validate_max_length_on_column!(column)
92
+ Rails.logger.info { "AutoValidate: Adding maxlength of #{column.limit} validation to #{model_class.table_name}##{column.name}" }
93
+ model_class.validates column.name.to_sym, allow_blank: true, length: {maximum: column.limit}
94
+ end
95
+
96
+ def max_length_validation_exists_on_column?(column)
97
+ model_class.validators_on(column.name.to_sym).each do |validator|
98
+ return true if validator.kind == :length
99
+ end
100
+
101
+ false
102
+ end
103
+ end
@@ -0,0 +1,9 @@
1
+ class ActiveRecordAutoValidations::OnLoad
2
+ def self.execute!(klass:)
3
+ # This callback is going to be called for all classes. We should only run auto-validations on the ones that have had the module included
4
+ # which is done through the marked-variable.
5
+ marked = klass.instance_variable_get(:@active_record_auto_validations_marked)
6
+
7
+ ActiveRecordAutoValidations::AutoValidateModelClass.execute!(model_class: klass) if marked
8
+ end
9
+ end
data/config/routes.rb CHANGED
@@ -1,2 +1,2 @@
1
- ActiveRecordAutoValidations::Engine.routes.draw do
1
+ ActiveRecordAutoValidations::Engine.routes.draw do # rubocop:disable Lint/EmptyBlock
2
2
  end
@@ -1,5 +1,5 @@
1
- module ActiveRecordAutoValidations
2
- class Engine < ::Rails::Engine
3
- isolate_namespace ActiveRecordAutoValidations
4
- end
1
+ module ActiveRecordAutoValidations; end
2
+
3
+ class ActiveRecordAutoValidations::Engine < ::Rails::Engine
4
+ isolate_namespace ActiveRecordAutoValidations
5
5
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveRecordAutoValidations
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.3".freeze
3
3
  end
@@ -2,5 +2,4 @@ require "active_record_auto_validations/version"
2
2
  require "active_record_auto_validations/engine"
3
3
 
4
4
  module ActiveRecordAutoValidations
5
- # Your code goes here...
6
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_auto_validations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - kaspernj
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-19 00:00:00.000000000 Z
11
+ date: 2022-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -24,6 +24,104 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 7.0.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: dotenv-rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: mysql2
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-performance
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop-rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
27
125
  description: Scans ActiveRecord models and adds automatic validations based on null,
28
126
  max length etc.
29
127
  email:
@@ -35,21 +133,15 @@ files:
35
133
  - MIT-LICENSE
36
134
  - README.md
37
135
  - Rakefile
38
- - app/assets/config/auto_validations_manifest.js
39
- - app/assets/stylesheets/auto_validations/application.css
40
- - app/controllers/auto_validations/application_controller.rb
41
- - app/helpers/auto_validations/application_helper.rb
42
- - app/jobs/auto_validations/application_job.rb
43
- - app/mailers/auto_validations/application_mailer.rb
44
- - app/models/auto_validations/application_record.rb
45
- - app/models/concerns/auto_validations/model_concern.rb
46
- - app/services/auto_validations/auto_validate_model_class.rb
47
- - app/views/layouts/auto_validations/application.html.erb
136
+ - app/models/concerns/active_record_auto_validations/model_concern.rb
137
+ - app/services/active_record_auto_validations/auto_unique_index.rb
138
+ - app/services/active_record_auto_validations/auto_validate_model_class.rb
139
+ - app/services/active_record_auto_validations/on_load.rb
48
140
  - config/routes.rb
49
141
  - lib/active_record_auto_validations.rb
50
142
  - lib/active_record_auto_validations/engine.rb
51
143
  - lib/active_record_auto_validations/version.rb
52
- - lib/tasks/auto_validations_tasks.rake
144
+ - lib/tasks/active_record_auto_validations_tasks.rake
53
145
  homepage: https://github.com/kaspernj/active_record_auto_validations
54
146
  licenses:
55
147
  - MIT
@@ -57,6 +149,7 @@ metadata:
57
149
  homepage_uri: https://github.com/kaspernj/active_record_auto_validations
58
150
  source_code_uri: https://github.com/kaspernj/active_record_auto_validations
59
151
  changelog_uri: https://github.com/kaspernj/active_record_auto_validations
152
+ rubygems_mfa_required: 'true'
60
153
  post_install_message:
61
154
  rdoc_options: []
62
155
  require_paths:
@@ -1 +0,0 @@
1
- //= link_directory ../stylesheets/active_record_auto_validations .css
@@ -1,15 +0,0 @@
1
- /*
2
- * This is a manifest file that'll be compiled into application.css, which will include all the files
3
- * listed below.
4
- *
5
- * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
- * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
- *
8
- * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
- * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
- * files in this directory. Styles in this file should be added after the last require_* statement.
11
- * It is generally better to create a new file per style scope.
12
- *
13
- *= require_tree .
14
- *= require_self
15
- */
@@ -1,4 +0,0 @@
1
- module ActiveRecordAutoValidations
2
- class ApplicationController < ActionController::Base
3
- end
4
- end
@@ -1,4 +0,0 @@
1
- module ActiveRecordAutoValidations
2
- module ApplicationHelper
3
- end
4
- end
@@ -1,4 +0,0 @@
1
- module ActiveRecordAutoValidations
2
- class ApplicationJob < ActiveJob::Base
3
- end
4
- end
@@ -1,6 +0,0 @@
1
- module ActiveRecordAutoValidations
2
- class ApplicationMailer < ActionMailer::Base
3
- default from: "from@example.com"
4
- layout "mailer"
5
- end
6
- end
@@ -1,5 +0,0 @@
1
- module ActiveRecordAutoValidations
2
- class ApplicationRecord < ActiveRecord::Base
3
- self.abstract_class = true
4
- end
5
- end
@@ -1,7 +0,0 @@
1
- module ActiveRecordAutoValidations::ModelConcern
2
- extend ActiveSupport::Concern
3
-
4
- included do |base|
5
- ActiveRecordAutoValidations::AutoValidateModelClass.execute!(model_class: base)
6
- end
7
- end
@@ -1,51 +0,0 @@
1
- class ActiveRecordAutoValidations::AutoValidateModelClass < ApplicationService
2
- attr_reader :model_class
3
-
4
- def initialize(model_class:)
5
- @model_class = model_class
6
- end
7
-
8
- def perform
9
- begin
10
- columns
11
- rescue ActiveRecord::StatementInvalid => e
12
- # Database is probably not running - we need to ignore this to make stuff like db:migrate, db:schema:load work
13
- Rails.logger.error "AutoValidate: Ignoring error while loading columns, because database might not be initialized: #{e.message}"
14
- return succeed!
15
- end
16
-
17
- insert_active_record_auto_validations!
18
- succeed!
19
- end
20
-
21
- def columns
22
- @columns ||= model_class.columns
23
- end
24
-
25
- def insert_active_record_auto_validations!
26
- columns.each do |column|
27
- next if column.name == "id" || column.name == "created_at" || column.name == "updated_at"
28
-
29
- auto_validate_pesence_on_column!(column) if auto_validate_presence_on_column?(column)
30
- auto_validate_max_length_on_column!(column) if auto_validate_max_length_on_column?(column)
31
- end
32
- end
33
-
34
- def auto_validate_presence_on_column?(column)
35
- !column.null && !column.name.end_with?("_id") && column.default.nil?
36
- end
37
-
38
- def auto_validate_pesence_on_column!(column)
39
- Rails.logger.info "AutoValidate: Adding presence validation to #{model_class.table_name}##{column.name}"
40
- model_class.validates column.name.to_sym, presence: true
41
- end
42
-
43
- def auto_validate_max_length_on_column?(column)
44
- column.type == :string && column.limit
45
- end
46
-
47
- def auto_validate_max_length_on_column!(column)
48
- Rails.logger.info "AutoValidate: Adding maxlength of #{column.limit} validation to #{model_class.table_name}##{column.name}"
49
- model_class.validates column.name.to_sym, allow_blank: true, length: {maximum: column.limit}
50
- end
51
- end
@@ -1,15 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>Auto validations</title>
5
- <%= csrf_meta_tags %>
6
- <%= csp_meta_tag %>
7
-
8
- <%= stylesheet_link_tag "active_record_auto_validations/application", media: "all" %>
9
- </head>
10
- <body>
11
-
12
- <%= yield %>
13
-
14
- </body>
15
- </html>