active_record_auto_validations 0.0.2 → 0.0.4

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: db14026d46494a83057c52f72b6bc2d7c61e9bea8321d3f25830a04ccf4a01a0
4
- data.tar.gz: 989f1988c0b10ae172a9735705458025db3c141349b1324e75278e7178681097
3
+ metadata.gz: fed3fd2ca11387161a5229a84c3a2830e75ecfccccb690f7e3df623d1b7860b0
4
+ data.tar.gz: cbce1703024f0281fd689a87f135b5ee3c197d4fa282ff8937790748e160823c
5
5
  SHA512:
6
- metadata.gz: c57baea430f577cecdfb69d9890d2ebcee10662dd0dac05eb3e0baca884f2499bfabfd2867940f6af3ce168373a983f403af5e269f9df6866afa0bd7f07cb091
7
- data.tar.gz: f4c749d363d248d89733da4d9260eb2a01df123afd23cf1c74ac6aefe3b366596dbd306f04f2c2a77a3c2b490ee2ae557d159fc79ff92400859ecec1dfbc11bc
6
+ metadata.gz: 6f3f3022a614e737fd610d50ece642e8d8b5db811cb5e5a4a8c228935e3103b266480a16ad71f0ecce1591ef6614c1d7b258cfdc6af9a22720e7c1c1e238c5e4
7
+ data.tar.gz: 95f0eaf31a5ae94683bd686c0677ac783f02a6c36ef816a20fdfbe74c7c6a2ce673366d4fbed1df40c13aa7bd48dd2b494977085bc0079a6cf010f7c46d1d362
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
 
@@ -2,6 +2,10 @@ module ActiveRecordAutoValidations::ModelConcern
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included do |base|
5
- ActiveRecordAutoValidations::AutoValidateModelClass.execute!(model_class: 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)
6
10
  end
7
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
@@ -1,51 +1,119 @@
1
- class ActiveRecordAutoValidations::AutoValidateModelClass < ApplicationService
2
- attr_reader :model_class
1
+ class ActiveRecordAutoValidations::AutoValidateModelClass
2
+ attr_reader :ignore_column_names, :model_class
3
3
 
4
- def initialize(model_class:)
4
+ def self.execute!(...)
5
+ ActiveRecordAutoValidations::AutoValidateModelClass.new(...).perform
6
+ end
7
+
8
+ def initialize(model_class:, ignore_column_names: [])
9
+ @ignore_column_names = ignore_column_names
5
10
  @model_class = model_class
6
11
  end
7
12
 
8
13
  def perform
14
+ check_if_already_loaded_on_model_class!
15
+ register_loaded_on_model_class!
16
+
9
17
  begin
10
18
  columns
19
+ indexes
11
20
  rescue ActiveRecord::StatementInvalid => e
12
21
  # 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!
22
+ Rails.logger.info { "AutoValidate: Ignoring error while loading columns, because database might not be initialized: #{e.message}" }
23
+ return
15
24
  end
16
25
 
17
- insert_active_record_auto_validations!
18
- succeed!
26
+ insert_active_record_auto_validations_from_columns!
27
+ insert_active_record_auto_validations_from_indexes!
28
+ end
29
+
30
+ def check_if_already_loaded_on_model_class!
31
+ auto_validations_loaded = model_class.instance_variable_get(:@_active_record_auto_validations_loaded)
32
+
33
+ raise "AutoValidations already loaded for #{model_class.name}" if auto_validations_loaded
34
+ end
35
+
36
+ def register_loaded_on_model_class!
37
+ model_class.instance_variable_set(:@_active_record_auto_validations_loaded, true)
19
38
  end
20
39
 
21
40
  def columns
22
41
  @columns ||= model_class.columns
23
42
  end
24
43
 
25
- def insert_active_record_auto_validations!
44
+ def ignore_column?(column_name)
45
+ return true if column_name == "id" || column_name == "created_at" || column_name == "updated_at"
46
+
47
+ ignore_column_names.include?(column_name)
48
+ end
49
+
50
+ def ignore_index?(index)
51
+ return true unless index.unique
52
+
53
+ index.columns.any? { |index_column_name| ignore_column?(index_column_name) }
54
+ end
55
+
56
+ def indexes
57
+ @indexes ||= model_class.connection.indexes(model_class.table_name)
58
+ end
59
+
60
+ def insert_active_record_auto_validations_from_columns!
26
61
  columns.each do |column|
27
- next if column.name == "id" || column.name == "created_at" || column.name == "updated_at"
62
+ next if ignore_column?(column.name)
28
63
 
29
64
  auto_validate_pesence_on_column!(column) if auto_validate_presence_on_column?(column)
30
65
  auto_validate_max_length_on_column!(column) if auto_validate_max_length_on_column?(column)
31
66
  end
32
67
  end
33
68
 
69
+ def insert_active_record_auto_validations_from_indexes!
70
+ indexes.each do |index|
71
+ next if ignore_index?(index)
72
+
73
+ # Dont add uniqueness validation to ActsAsList position columns
74
+ if index.columns.include?("position") && model_class.respond_to?(:acts_as_list_top)
75
+ Rails.logger.info do
76
+ "AutoValidate: Skipping unique validation on #{model_class.table_name}##{index.columns.join(",")} because it looks like ActsAsList"
77
+ end
78
+
79
+ next
80
+ end
81
+
82
+ ActiveRecordAutoValidations::AutoUniqueIndex.execute!(columns: columns, model_class: model_class, index: index)
83
+ end
84
+ end
85
+
86
+ def presence_validation_exists_on_column?(column)
87
+ model_class.validators_on(column.name.to_sym).each do |validator|
88
+ return true if validator.kind == :presence
89
+ end
90
+
91
+ false
92
+ end
93
+
34
94
  def auto_validate_presence_on_column?(column)
35
- !column.null && !column.name.end_with?("_id") && column.default.nil?
95
+ !column.null && !column.name.end_with?("_id") && column.default.nil? && column.default_function.nil? && !presence_validation_exists_on_column?(column)
36
96
  end
37
97
 
38
98
  def auto_validate_pesence_on_column!(column)
39
- Rails.logger.info "AutoValidate: Adding presence validation to #{model_class.table_name}##{column.name}"
99
+ Rails.logger.info { "AutoValidate: Adding presence validation to #{model_class.table_name}##{column.name}" }
40
100
  model_class.validates column.name.to_sym, presence: true
41
101
  end
42
102
 
43
103
  def auto_validate_max_length_on_column?(column)
44
- column.type == :string && column.limit
104
+ column.type == :string && column.limit && !max_length_validation_exists_on_column?(column)
45
105
  end
46
106
 
47
107
  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}"
108
+ Rails.logger.info { "AutoValidate: Adding maxlength of #{column.limit} validation to #{model_class.table_name}##{column.name}" }
49
109
  model_class.validates column.name.to_sym, allow_blank: true, length: {maximum: column.limit}
50
110
  end
111
+
112
+ def max_length_validation_exists_on_column?(column)
113
+ model_class.validators_on(column.name.to_sym).each do |validator|
114
+ return true if validator.kind == :length
115
+ end
116
+
117
+ false
118
+ end
51
119
  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.2"
2
+ VERSION = "0.0.4".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.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - kaspernj
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-19 00:00:00.000000000 Z
11
+ date: 2024-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -35,16 +35,10 @@ files:
35
35
  - MIT-LICENSE
36
36
  - README.md
37
37
  - Rakefile
38
- - app/assets/config/active_record_auto_validations_manifest.js
39
- - app/assets/stylesheets/active_record_auto_validations/application.css
40
- - app/controllers/active_record_auto_validations/application_controller.rb
41
- - app/helpers/active_record_auto_validations/application_helper.rb
42
- - app/jobs/active_record_auto_validations/application_job.rb
43
- - app/mailers/active_record_auto_validations/application_mailer.rb
44
- - app/models/active_record_auto_validations/application_record.rb
45
38
  - app/models/concerns/active_record_auto_validations/model_concern.rb
39
+ - app/services/active_record_auto_validations/auto_unique_index.rb
46
40
  - app/services/active_record_auto_validations/auto_validate_model_class.rb
47
- - app/views/layouts/active_record_auto_validations/application.html.erb
41
+ - app/services/active_record_auto_validations/on_load.rb
48
42
  - config/routes.rb
49
43
  - lib/active_record_auto_validations.rb
50
44
  - lib/active_record_auto_validations/engine.rb
@@ -57,7 +51,8 @@ metadata:
57
51
  homepage_uri: https://github.com/kaspernj/active_record_auto_validations
58
52
  source_code_uri: https://github.com/kaspernj/active_record_auto_validations
59
53
  changelog_uri: https://github.com/kaspernj/active_record_auto_validations
60
- post_install_message:
54
+ rubygems_mfa_required: 'true'
55
+ post_install_message:
61
56
  rdoc_options: []
62
57
  require_paths:
63
58
  - lib
@@ -72,8 +67,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
67
  - !ruby/object:Gem::Version
73
68
  version: '0'
74
69
  requirements: []
75
- rubygems_version: 3.3.7
76
- signing_key:
70
+ rubygems_version: 3.4.10
71
+ signing_key:
77
72
  specification_version: 4
78
73
  summary: Scans ActiveRecord models and adds automatic validations based on null, max
79
74
  length etc.
@@ -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,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>