├── .github └── workflows │ └── main.yml ├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── MIT-LICENSE ├── README.md ├── Rakefile ├── html5_validators.gemspec ├── lib ├── html5_validators.rb └── html5_validators │ ├── action_view │ ├── form_helpers.rb │ └── form_helpers_rails3.rb │ ├── active_model │ ├── helper_methods.rb │ └── validations.rb │ ├── active_record │ └── base.rb │ └── version.rb └── test ├── fake_app.rb ├── features ├── active_model_validation_test.rb └── active_record_validation_test.rb ├── models └── active_record │ └── inherited_test.rb └── test_helper.rb /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | matrix: 9 | ruby_version: [ruby-head, '3.4', '3.3', '3.2', '3.1'] 10 | rails_version: [edge, '8.0', '7.2', '7.1', '7.0', '6.1'] 11 | 12 | include: 13 | - ruby_version: '3.1' 14 | activemodel_version: '7.0' 15 | 16 | - ruby_version: '3.0' 17 | rails_version: '7.1' 18 | - ruby_version: '3.0' 19 | activemodel_version: '7.1' 20 | - ruby_version: '3.0' 21 | rails_version: '7.0' 22 | - ruby_version: '3.0' 23 | activemodel_version: '7.0' 24 | - ruby_version: '3.0' 25 | rails_version: '6.1' 26 | - ruby_version: '3.0' 27 | activemodel_version: '6.1' 28 | - ruby_version: '3.0' 29 | rails_version: '6.0' 30 | - ruby_version: '3.0' 31 | activemodel_version: '6.0' 32 | 33 | - ruby_version: '2.7' 34 | rails_version: '7.1' 35 | - ruby_version: '2.7' 36 | activemodel_version: '7.1' 37 | - ruby_version: '2.7' 38 | rails_version: '7.0' 39 | - ruby_version: '2.7' 40 | activemodel_version: '7.0' 41 | - ruby_version: '2.7' 42 | rails_version: '6.1' 43 | - ruby_version: '2.7' 44 | activemodel_version: '6.1' 45 | - ruby_version: '2.7' 46 | rails_version: '6.0' 47 | - ruby_version: '2.7' 48 | activemodel_version: '6.0' 49 | 50 | - ruby_version: '2.6' 51 | rails_version: '5.2' 52 | - ruby_version: '2.6' 53 | activemodel_version: '5.2' 54 | - ruby_version: '2.6' 55 | rails_version: '5.1' 56 | - ruby_version: '2.6' 57 | rails_version: '5.0' 58 | - ruby_version: '2.6' 59 | rails_version: '4.2' 60 | 61 | - ruby_version: '2.5' 62 | rails_version: '5.2' 63 | 64 | - ruby_version: '2.4' 65 | rails_version: '4.2' 66 | 67 | - ruby_version: '2.3' 68 | rails_version: '4.2' 69 | - ruby_version: '2.3' 70 | rails_version: '4.1' 71 | - ruby_version: '2.3' 72 | rails_version: '4.0' 73 | 74 | - ruby_version: '2.2' 75 | rails_version: '4.2' 76 | 77 | - ruby_version: '2.1' 78 | rails_version: '3.2' 79 | 80 | exclude: 81 | - ruby_version: '3.1' 82 | rails_version: '8.0' 83 | 84 | runs-on: ubuntu-24.04 85 | 86 | env: 87 | BUNDLE_GEMFILE: ${{ matrix.gemfile }} 88 | RAILS_VERSION: ${{ matrix.rails_version }} 89 | ACTIVEMODEL_VERSION: ${{ matrix.activemodel_version }} 90 | 91 | steps: 92 | - uses: actions/checkout@v4 93 | 94 | - uses: ruby/setup-ruby@v1 95 | with: 96 | ruby-version: ${{ matrix.ruby_version }} 97 | rubygems: ${{ matrix.ruby_version < '2.6' && 'default' || 'latest' }} 98 | bundler: ${{ (contains(matrix.gemfile, 'rails_4') || contains(matrix.gemfile, 'rails_3')) && '1' || '2' }} 99 | bundler-cache: true 100 | continue-on-error: ${{ (matrix.ruby_version == 'ruby-head') || (matrix.rails_version == 'edge') || (matrix.allow_failures == 'true') }} 101 | 102 | - run: bundle exec rake 103 | continue-on-error: ${{ (matrix.ruby_version == 'ruby-head') || (matrix.rails_version == 'edge') || (matrix.allow_failures == 'true') }} 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | tmp 6 | log 7 | gemfiles/*.lock 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.9.0 2 | 3 | * Added support for `form_with` [@DialBird] 4 | 5 | * Enhanced support for non-AR applications to load all necessary monkey-patches on application initialization 6 | 7 | 8 | ## 1.8.0 9 | 10 | * Added support for AR `validation_context` such as `on: :create` or `on: :update` 11 | 12 | ## 1.7.0 13 | 14 | * Fixed a bug that `required: false` helper option does not override `required: true` value from PresenceValidator [@sandipransing] 15 | 16 | 17 | ## 1.5.0 18 | 19 | * Rails 5 support 20 | 21 | * Changed `auto_html5_validation` config behavior not to propagate to children classes 22 | 23 | 24 | ## 1.4.0 25 | 26 | * Dropped Ruby 1.8 support 27 | 28 | * Dropped Ruby 1.9 support 29 | 30 | * Dropped Rails 3.2 support 31 | 32 | * Fixed a bug that `maxlength` validation for inputs which can be blank was ignored [@kv109] 33 | 34 | * Fixed a bug that `auto_html5_validation` was not properly enabled for models that are loaded after loading this gem 35 | 36 | 37 | ## 1.3.0 38 | 39 | * Switched from `alias_method_chain` to `Module#prepend` on Ruby 2 and Rails 4 40 | 41 | * Added `minlength` support 42 | 43 | 44 | ## 1.2.2 45 | 46 | * Fixed undefined method `auto_html5_validation` error for non-AR Active Model models 47 | 48 | 49 | ## 1.2.1 50 | 51 | * Fixed a bug that `minlength / maxlength` values were assigned to `min / max` 52 | 53 | 54 | ## 1.2.0 55 | 56 | * Added global `Html5Validators.enabled` flag [@sinsoku] 57 | 58 | * Fixed a bug that some Symbol keyed helper options were ignored [@lucas-nelson] 59 | 60 | 61 | ## 1.1.3 62 | 63 | * Fixed a bug that AR::Base.inherited conflicts with other gems [@tricknotes] 64 | 65 | 66 | ## 1.1.2 67 | 68 | * Fixed a bug that `cattr_accessor` + `:instance_accessor => false` still creates an `instance_accessor` on AR 3.0 and 3.1 69 | 70 | 71 | ## 1.1.1 72 | 73 | * Rails 4 support 74 | 75 | 76 | ## 1.1.0 77 | 78 | * Added `maxlength` validator support for `text_area` [@ursm] 79 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source 'https://rubygems.org' 3 | 4 | # Specify your gem's dependencies in html5_validators.gemspec 5 | gemspec 6 | 7 | if ENV['RAILS_VERSION'] == 'edge' 8 | gem 'railties', git: 'https://github.com/rails/rails.git' 9 | gem 'activerecord', git: 'https://github.com/rails/rails.git' 10 | gem 'sqlite3' 11 | elsif ENV['RAILS_VERSION'] && !ENV['RAILS_VERSION'].empty? 12 | gem 'railties', "~> #{ENV['RAILS_VERSION']}.0" 13 | gem 'activerecord', "~> #{ENV['RAILS_VERSION']}.0" 14 | if ENV['RAILS_VERSION'] <= '5.0' 15 | gem 'sqlite3', '< 1.4' 16 | elsif (ENV['RAILS_VERSION'] <= '8') || (RUBY_VERSION < '3') 17 | gem 'sqlite3', '< 2' 18 | end 19 | 20 | if ENV['RAILS_VERSION'] < '4' 21 | gem 'capybara', '~> 2.0.0' 22 | gem 'test-unit-rails', '1.0.2' 23 | end 24 | 25 | gem 'webdrivers' if (ENV['RAILS_VERSION'] && ENV['RAILS_VERSION'] >= '6') && (RUBY_VERSION < '3') 26 | 27 | elsif ENV['ACTIVEMODEL_VERSION'] 28 | gem 'railties', "~> #{ENV['ACTIVEMODEL_VERSION']}.0" 29 | gem 'activemodel', "~> #{ENV['ACTIVEMODEL_VERSION']}.0" 30 | else 31 | gem 'railties' 32 | gem 'activerecord' 33 | gem 'sqlite3' 34 | end 35 | 36 | if RUBY_VERSION < '2.7' 37 | gem 'puma', '< 6' 38 | else 39 | gem 'puma' 40 | end 41 | 42 | gem 'nokogiri', RUBY_VERSION < '2.1' ? '~> 1.6.0' : '>= 1.7' 43 | gem 'loofah', RUBY_VERSION < '2.5' ? '< 2.21.0' : '>= 0' 44 | gem 'selenium-webdriver' 45 | gem 'net-smtp' if RUBY_VERSION >= '3.1' 46 | gem 'bigdecimal' if RUBY_VERSION >= '3.4' 47 | gem 'mutex_m' if RUBY_VERSION >= '3.4' 48 | gem 'drb' if RUBY_VERSION >= '3.4' 49 | gem 'benchmark' if RUBY_VERSION >= '3.5' 50 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Akira Matsuda 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTML5Validators 2 | 3 | Automatic client-side validation using HTML5 Form Validation 4 | 5 | ## What is this? 6 | 7 | html5_validators is a gem/plugin for Rails 3+ that enables automatic client-side 8 | validation using ActiveModel + HTML5. Once you bundle this gem on your app, 9 | the gem will automatically translate your model validation code into HTML5 10 | validation attributes on every `form_for` invocation unless you explicitly 11 | cancel it. 12 | 13 | ## Features 14 | 15 | ### PresenceValidator => required 16 | 17 | * Model 18 | ```ruby 19 | class User 20 | include ActiveModel::Validations 21 | validates_presence_of :name 22 | end 23 | ``` 24 | 25 | * View 26 | ```erb 27 | <%= f.text_field :name %> 28 | ``` 29 | other `text_field`ish helpers, `text_area`, `radio_button`, and `check_box` are also available 30 | 31 | * HTML 32 | ```html 33 | 34 | ``` 35 | 36 | * SPEC 37 | 38 | http://dev.w3.org/html5/spec/Overview.html#attr-input-required 39 | 40 | ![PresenceValidator](https://raw.githubusercontent.com/amatsuda/html5_validators/0928dc13fdd1a7746deed9a9cf7e865e13039df8/assets/presence.png) 41 | 42 | ### LengthValidator => maxlength 43 | 44 | * Model 45 | ```ruby 46 | class User 47 | include ActiveModel::Validations 48 | validates_length_of :name, maximum: 10 49 | end 50 | ``` 51 | 52 | * View 53 | ```erb 54 | <%= f.text_field :name %> 55 | ``` 56 | `text_area` is also available 57 | 58 | * HTML 59 | ```html 60 | 61 | ``` 62 | 63 | * SPEC 64 | 65 | http://dev.w3.org/html5/spec/Overview.html#attr-input-maxlength 66 | 67 | ### NumericalityValidator => max, min 68 | 69 | * Model 70 | ```ruby 71 | class User 72 | include ActiveModel::Validations 73 | validates_numericality_of :age, greater_than_or_equal_to: 20 74 | end 75 | ``` 76 | 77 | * View (be sure to use number_field) 78 | ```erb 79 | <%= f.number_field :age %> 80 | ``` 81 | 82 | * HTML 83 | ```html 84 | 85 | ``` 86 | 87 | * SPEC 88 | 89 | http://dev.w3.org/html5/spec/Overview.html#attr-input-max 90 | http://dev.w3.org/html5/spec/Overview.html#attr-input-min 91 | 92 | ![NumericalityValidator](https://raw.githubusercontent.com/amatsuda/html5_validators/0928dc13fdd1a7746deed9a9cf7e865e13039df8/assets/numericality.png) 93 | 94 | ### And more (coming soon...?) 95 | :construction: 96 | 97 | ## Disabling automatic client-side validation 98 | 99 | There are four ways to cancel the automatic HTML5 validation. 100 | 101 | ### 1. Per form (via form_for option) 102 | 103 | Set `auto_html5_validation: false` to `form_for` parameter. 104 | 105 | * View 106 | ```erb 107 | <%= form_for @user, auto_html5_validation: false do |f| %> 108 | ... 109 | <% end %> 110 | ``` 111 | 112 | ### 2. Per model instance (via model attribute) 113 | 114 | Set `auto_html5_validation = false` attribute to ActiveModelish object. 115 | 116 | * Controller 117 | ```ruby 118 | @user = User.new auto_html5_validation: false 119 | ``` 120 | 121 | * View 122 | ```erb 123 | <%= form_for @user do |f| %> 124 | ... 125 | <% end %> 126 | ``` 127 | 128 | ### 3. Per model class (via model class attribute) 129 | 130 | Set `auto_html5_validation = false` to ActiveModelish class' class variable. 131 | This configuration will never be propagated to inherited children classes. 132 | 133 | * Model 134 | ```ruby 135 | class User < ActiveRecord::Base 136 | self.auto_html5_validation = false 137 | end 138 | ``` 139 | 140 | * Controller 141 | ```ruby 142 | @user = User.new 143 | ``` 144 | 145 | * View 146 | ```erb 147 | <%= form_for @user do |f| %> 148 | ... 149 | <% end %> 150 | ``` 151 | 152 | ### 4. Globally (via HTML5Validators module configuration) 153 | 154 | Set `config.enabled = false` to Html5Validators module. 155 | Maybe you want to put this in your test_helper, or add a controller filter as 156 | follows for development mode. 157 | 158 | * Controller 159 | ```ruby 160 | # an example filter that disables the validator if the request has {h5v: 'disable'} params 161 | around_action do |controller, block| 162 | h5v_enabled_was = Html5Validators.enabled 163 | Html5Validators.enabled = false if params[:h5v] == 'disable' 164 | block.call 165 | Html5Validators.enabled = h5v_enabled_was 166 | end 167 | ``` 168 | 169 | ## Supported versions 170 | 171 | * Ruby 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 3.0, 3.1, 3.2, 3.3 (trunk) 172 | 173 | * Rails 3.2.x, 4.0.x, 4.1, 4.2, 5.0, 5.1, 5.2, 6.0, 6.1, 7.0, 7.1 (edge) 174 | 175 | * HTML5 compatible browsers 176 | 177 | 178 | ## Installation 179 | 180 | Put this line into your Gemfile: 181 | ```ruby 182 | gem 'html5_validators' 183 | ``` 184 | 185 | Then bundle: 186 | ``` 187 | % bundle 188 | ``` 189 | 190 | ## Notes 191 | 192 | When accessed by an HTML5 incompatible legacy browser, these extra attributes 193 | will just be ignored. 194 | 195 | ## Todo 196 | 197 | * more validations 198 | 199 | 200 | ## Copyright 201 | 202 | Copyright (c) 2011 Akira Matsuda. See MIT-LICENSE for further details. 203 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'bundler/gem_tasks' 3 | require 'rake/testtask' 4 | 5 | Rake::TestTask.new do |t| 6 | t.libs << 'test' 7 | if Gem.loaded_specs['activerecord'] 8 | t.test_files = Dir['test/**/*_test.rb'] 9 | else 10 | t.test_files = Dir['test/**/*_test.rb'] - Dir['test/**/*active_record*'] - Dir['test/**/*active_record*/*.rb'] 11 | end 12 | t.warning = true 13 | t.verbose = true 14 | end 15 | 16 | task default: :test 17 | -------------------------------------------------------------------------------- /html5_validators.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # frozen_string_literal: true 3 | $:.push File.expand_path('../lib', __FILE__) 4 | require 'html5_validators/version' 5 | 6 | Gem::Specification.new do |s| 7 | s.name = 'html5_validators' 8 | s.version = Html5Validators::VERSION 9 | s.platform = Gem::Platform::RUBY 10 | s.authors = ['Akira Matsuda'] 11 | s.email = ['ronnie@dio.jp'] 12 | s.homepage = 'https://github.com/amatsuda/html5_validators' 13 | s.summary = 'Automatic client side validation using HTML5 Form Validation' 14 | s.description = 'A gem/plugin for Rails 3+ that enables client-side validation using ActiveModel + HTML5' 15 | s.license = 'MIT' 16 | 17 | s.files = `git ls-files`.split("\n") 18 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 19 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 20 | s.require_paths = ['lib'] 21 | s.add_development_dependency 'test-unit-activesupport' 22 | s.add_development_dependency 'test-unit-capybara' 23 | s.add_development_dependency 'selenium-webdriver' 24 | s.add_development_dependency 'puma' 25 | s.add_development_dependency 'capybara', '>= 2' 26 | s.add_development_dependency 'sqlite3' 27 | s.add_development_dependency 'rake' 28 | s.add_dependency 'activemodel' 29 | end 30 | -------------------------------------------------------------------------------- /lib/html5_validators.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails' 4 | 5 | module Html5Validators 6 | @enabled = true 7 | 8 | class << self 9 | attr_accessor :enabled 10 | end 11 | 12 | class Railtie < ::Rails::Railtie #:nodoc: 13 | initializer 'html5_validators' do 14 | config.after_initialize do 15 | require 'html5_validators/active_model/helper_methods' 16 | require 'html5_validators/active_model/validations' 17 | end 18 | 19 | ActiveSupport.on_load(:active_record) do 20 | require 'html5_validators/active_record/base' 21 | end 22 | 23 | ActiveSupport.on_load(:action_view) do 24 | if ActionPack::VERSION::STRING >= '4' 25 | require 'html5_validators/action_view/form_helpers' 26 | else 27 | require 'html5_validators/action_view/form_helpers_rails3' 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/html5_validators/action_view/form_helpers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Html5Validators 4 | module ActionViewExtension 5 | module FormHelper 6 | def form_for(record, options = {}, &block) 7 | if record.respond_to?(:auto_html5_validation=) 8 | if !Html5Validators.enabled || (options[:auto_html5_validation] == false) 9 | record.auto_html5_validation = false 10 | end 11 | end 12 | super 13 | end 14 | 15 | if Rails::VERSION::STRING >= '5.1' 16 | def form_with(model: nil, scope: nil, url: nil, format: nil, **options) 17 | if model && model.respond_to?(:auto_html5_validation=) 18 | if !Html5Validators.enabled || (options[:auto_html5_validation] == false) 19 | model.auto_html5_validation = false 20 | end 21 | end 22 | super 23 | end 24 | end 25 | end 26 | 27 | module PresenceValidator 28 | def render 29 | if object.is_a?(ActiveModel::Validations) && (object.auto_html5_validation != false) && (object.class.auto_html5_validation != false) 30 | if defined? @html_options 31 | @html_options["required"] ||= @html_options.fetch(:required) { object.attribute_required?(@method_name) } 32 | else 33 | @options["required"] ||= @options.fetch(:required) { object.attribute_required?(@method_name) } 34 | end 35 | end 36 | super 37 | end 38 | end 39 | 40 | module LengthValidator 41 | def render 42 | if object.is_a?(ActiveModel::Validations) && (object.auto_html5_validation != false) && (object.class.auto_html5_validation != false) 43 | @options["maxlength"] ||= @options[:maxlength] || object.attribute_maxlength(@method_name) 44 | @options["minlength"] ||= @options[:minlength] || object.attribute_minlength(@method_name) 45 | end 46 | super 47 | end 48 | end 49 | 50 | module NumericalityValidator 51 | def render 52 | if object.is_a?(ActiveModel::Validations) && (object.auto_html5_validation != false) && (object.class.auto_html5_validation != false) 53 | @options["max"] ||= @options["max"] || @options[:max] || object.attribute_max(@method_name) 54 | @options["min"] ||= @options["min"] || @options[:min] || object.attribute_min(@method_name) 55 | end 56 | super 57 | end 58 | end 59 | end 60 | end 61 | 62 | 63 | module ActionView 64 | Base.send :prepend, Html5Validators::ActionViewExtension::FormHelper 65 | 66 | module Helpers 67 | module Tags 68 | class TextField 69 | prepend Html5Validators::ActionViewExtension::NumericalityValidator 70 | prepend Html5Validators::ActionViewExtension::LengthValidator 71 | prepend Html5Validators::ActionViewExtension::PresenceValidator 72 | end 73 | 74 | class TextArea 75 | prepend Html5Validators::ActionViewExtension::LengthValidator 76 | prepend Html5Validators::ActionViewExtension::PresenceValidator 77 | end 78 | 79 | #TODO probably I have to add some more classes here 80 | [RadioButton, CheckBox, Select, DateSelect, TimeZoneSelect].each do |kls| 81 | kls.send :prepend, Html5Validators::ActionViewExtension::PresenceValidator 82 | end 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/html5_validators/action_view/form_helpers_rails3.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Legacy Rails 3.x support 4 | module ActionView 5 | module Helpers 6 | module FormHelper 7 | def form_for_with_auto_html5_validation_option(record, options = {}, &proc) 8 | if record.respond_to?(:auto_html5_validation=) 9 | if !Html5Validators.enabled || (options[:auto_html5_validation] == false) 10 | record.auto_html5_validation = false 11 | end 12 | end 13 | form_for_without_auto_html5_validation_option record, options, &proc 14 | end 15 | alias_method_chain :form_for, :auto_html5_validation_option 16 | end 17 | 18 | class InstanceTag 19 | def to_input_field_tag_with_html5_attributes(field_type, options = {}) 20 | if object.is_a?(ActiveModel::Validations) && (object.auto_html5_validation != false) && (object.class.auto_html5_validation != false) 21 | options["required"] ||= object.attribute_required?(method_name) 22 | options["maxlength"] ||= object.attribute_maxlength(method_name) 23 | options["minlength"] ||= object.attribute_minlength(method_name) 24 | options["max"] ||= object.attribute_max(method_name) 25 | options["min"] ||= object.attribute_min(method_name) 26 | end 27 | to_input_field_tag_without_html5_attributes field_type, options 28 | end 29 | alias_method_chain :to_input_field_tag, :html5_attributes 30 | 31 | def to_text_area_tag_with_html5_attributes(options = {}) 32 | if object.is_a?(ActiveModel::Validations) && (object.auto_html5_validation != false) && (object.class.auto_html5_validation != false) 33 | options["required"] ||= object.attribute_required?(method_name) 34 | options["maxlength"] ||= object.attribute_maxlength(method_name) 35 | options["minlength"] ||= object.attribute_minlength(method_name) 36 | end 37 | to_text_area_tag_without_html5_attributes options 38 | end 39 | alias_method_chain :to_text_area_tag, :html5_attributes 40 | 41 | def to_radio_button_tag_with_html5_attributes(tag_value, options = {}) 42 | if object.is_a?(ActiveModel::Validations) && (object.auto_html5_validation != false) && (object.class.auto_html5_validation != false) 43 | options["required"] ||= object.attribute_required?(method_name) 44 | end 45 | to_radio_button_tag_without_html5_attributes tag_value, options 46 | end 47 | alias_method_chain :to_radio_button_tag, :html5_attributes 48 | 49 | def to_check_box_tag_with_html5_attributes(options = {}, checked_value = "1", unchecked_value = "0") 50 | if object.is_a?(ActiveModel::Validations) && (object.auto_html5_validation != false) && (object.class.auto_html5_validation != false) 51 | options["required"] ||= object.attribute_required?(method_name) 52 | end 53 | to_check_box_tag_without_html5_attributes options, checked_value, unchecked_value 54 | end 55 | alias_method_chain :to_check_box_tag, :html5_attributes 56 | 57 | private 58 | 59 | def select_content_tag_with_html5_attributes(option_tags, options, html_options) 60 | if object.is_a?(ActiveModel::Validations) && (object.auto_html5_validation != false) && (object.class.auto_html5_validation != false) 61 | html_options["required"] ||= object.attribute_required?(method_name) 62 | end 63 | select_content_tag_without_html5_attributes option_tags, options, html_options 64 | end 65 | alias_method_chain :select_content_tag, :html5_attributes 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/html5_validators/active_model/helper_methods.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Html5Validators 4 | module DafaultValidationContext 5 | if defined?(::ActiveRecord::Base) && (::ActiveRecord::VERSION::MAJOR < 5) 6 | refine ::ActiveRecord::Base do 7 | def default_validation_context 8 | new_record? ? :create : :update 9 | end 10 | end 11 | end 12 | refine Object do 13 | def default_validation_context 14 | nil 15 | end 16 | end 17 | end 18 | end 19 | 20 | using Html5Validators::DafaultValidationContext 21 | 22 | module ActiveModel 23 | module Validations 24 | module HelperMethods 25 | def attribute_required?(attribute) 26 | self.class.validators.grep(PresenceValidator).any? do |v| 27 | if v.attributes.include?(attribute.to_sym) && (v.options.keys & [:if, :unless]).empty? 28 | !(on = v.options[:on]) || Array(on).include?(default_validation_context) 29 | end 30 | end 31 | end 32 | 33 | def attribute_maxlength(attribute) 34 | self.class.validators.grep(LengthValidator).select {|v| 35 | if v.attributes.include?(attribute.to_sym) && (v.options.keys & [:maximum, :is]).any? && (v.options.keys & [:if, :unless, :tokenizer]).empty? 36 | !(on = v.options[:on]) || Array(on).include?(default_validation_context) 37 | end 38 | }.map {|v| v.options.slice(:maximum, :is)}.map(&:values).flatten.max 39 | end 40 | 41 | def attribute_minlength(attribute) 42 | self.class.validators.grep(LengthValidator).select {|v| 43 | if v.attributes.include?(attribute.to_sym) && (v.options.keys & [:minimum, :is]).any? && (v.options.keys & [:if, :unless, :allow_nil, :allow_blank, :tokenizer]).empty? 44 | !(on = v.options[:on]) || Array(on).include?(default_validation_context) 45 | end 46 | }.map {|v| v.options.slice(:minimum, :is)}.map(&:values).flatten.min 47 | end 48 | 49 | def attribute_max(attribute) 50 | self.class.validators.grep(NumericalityValidator).select {|v| 51 | if v.attributes.include?(attribute.to_sym) && (v.options.keys & [:less_than, :less_than_or_equal_to]).any? && (v.options.keys & [:if, :unless, :allow_nil, :allow_blank]).empty? 52 | !(on = v.options[:on]) || Array(on).include?(default_validation_context) 53 | end 54 | }.map {|v| v.options.slice(:less_than, :less_than_or_equal_to)}.map(&:values).flatten.max 55 | end 56 | 57 | def attribute_min(attribute) 58 | self.class.validators.grep(NumericalityValidator).select {|v| 59 | if v.attributes.include?(attribute.to_sym) && (v.options.keys & [:greater_than, :greater_than_or_equal_to]).any? && (v.options.keys & [:if, :unless, :allow_nil, :allow_blank]).empty? 60 | !(on = v.options[:on]) || Array(on).include?(default_validation_context) 61 | end 62 | }.map {|v| v.options.slice(:greater_than, :greater_than_or_equal_to)}.map(&:values).flatten.min 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/html5_validators/active_model/validations.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Html5Validators 4 | module ActiveModelExtension 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | cattr_accessor :auto_html5_validation, instance_accessor: false, instance_reader: false, instance_writer: false 9 | end 10 | end 11 | end 12 | 13 | module ActiveModel 14 | module Validations 15 | attr_accessor :auto_html5_validation 16 | end 17 | end 18 | 19 | ActiveModel::Validations.send(:include, Html5Validators::ActiveModelExtension) 20 | -------------------------------------------------------------------------------- /lib/html5_validators/active_record/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Html5Validators 4 | module ActiveRecordExtension 5 | extend ActiveSupport::Concern 6 | 7 | # Future subclasses will pick up the model extension 8 | module ClassMethods 9 | def inherited(kls) 10 | super 11 | class << kls 12 | attr_accessor :auto_html5_validation 13 | end 14 | kls.auto_html5_validation = true 15 | end 16 | end 17 | 18 | included do 19 | # Existing subclasses pick up the model extension as well 20 | descendants.each do |kls| 21 | class << kls 22 | attr_accessor :auto_html5_validation 23 | end 24 | kls.auto_html5_validation = true 25 | end 26 | end 27 | end 28 | end 29 | 30 | ActiveRecord::Base.send(:include, Html5Validators::ActiveRecordExtension) 31 | -------------------------------------------------------------------------------- /lib/html5_validators/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Html5Validators 4 | VERSION = '1.9.0' 5 | end 6 | -------------------------------------------------------------------------------- /test/fake_app.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'action_controller/railtie' 4 | require 'active_model' 5 | begin 6 | require 'active_record' 7 | rescue LoadError 8 | end 9 | 10 | # config 11 | class Html5ValidatorsTestApp < Rails::Application 12 | config.secret_token = "You know I'm born to lose, and gambling's for fools, But that's the way I like it baby, I don't wanna live for ever, And don't forget the joker!" 13 | config.session_store :cookie_store, key: '_myapp_session' 14 | config.active_support.deprecation = :log 15 | config.eager_load = false 16 | end 17 | Rails.application.initialize! 18 | 19 | # routes 20 | Rails.application.routes.draw do 21 | resources :people, only: [:new, :create] do 22 | collection do 23 | get :new_without_html5_validation 24 | get :new_with_required_true 25 | get :new_with_required_false 26 | end 27 | end 28 | resources :items, only: [:new, :create] do 29 | collection do 30 | get :new_without_html5_validation 31 | get :new_with_required_true 32 | get :new_with_required_false 33 | end 34 | end 35 | end 36 | 37 | # models 38 | if defined? ActiveRecord 39 | ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') 40 | 41 | # migrations 42 | ActiveRecord::Base.connection.instance_eval do 43 | create_table :people do |t| 44 | t.string :name 45 | t.string :password 46 | t.string :email 47 | t.integer :age 48 | t.text :bio 49 | t.string :blood_type 50 | t.boolean :terms_of_service 51 | t.string :user_type 52 | end 53 | end 54 | 55 | class ApplicationRecord < ActiveRecord::Base 56 | self.abstract_class = true 57 | end 58 | class Person < ApplicationRecord 59 | end 60 | end 61 | 62 | class Item 63 | if ActiveModel::VERSION::STRING >= '4' 64 | include ActiveModel::Model 65 | else 66 | include ActiveModel::Validations 67 | include ActiveModel::Conversion 68 | def persisted?; false; end 69 | end 70 | 71 | attr_accessor :name, :description 72 | end 73 | 74 | # controllers 75 | class ApplicationController < ActionController::Base; end 76 | class PeopleController < ApplicationController 77 | def new 78 | @person = Person.new 79 | render inline: <<-ERB 80 | <%= form_for @person, html: { id: 'form_for' } do |f| %> 81 | <%= f.text_field :name %> 82 | <%= f.password_field :password %> 83 | <%= f.text_area :bio %> 84 | <%= f.select :blood_type, %w(A B O AB), include_blank: true %> 85 | <%= f.check_box :terms_of_service %> 86 | <%= f.radio_button :user_type, 'normal' %> 87 | <%= f.radio_button :user_type, 'admin' %> 88 | <% end %> 89 | 90 | <% if Rails::VERSION::STRING >= '5.1' %> 91 | <%= form_with model: @person, id: 'form_with' do |f| %> 92 | <%= f.text_field :name, id: 'person_name' %> 93 | <%= f.password_field :password, id: 'person_password' %> 94 | <%= f.text_area :bio, id: 'person_bio' %> 95 | <%= f.select :blood_type, %w(A B O AB), include_blank: true %> 96 | <%= f.check_box :terms_of_service %> 97 | <%= f.radio_button :user_type, 'normal' %> 98 | <%= f.radio_button :user_type, 'admin' %> 99 | <% end %> 100 | <% end %> 101 | ERB 102 | end 103 | 104 | def new_without_html5_validation 105 | @person = Person.new 106 | render inline: <<-ERB 107 | <%= form_for @person, html: { id: 'form_for' }, auto_html5_validation: false do |f| %> 108 | <%= f.text_field :name %> 109 | <%= f.text_field :email %> 110 | <% end %> 111 | 112 | <% if Rails::VERSION::STRING >= '5.1' %> 113 | <%= form_with model: @person, id: 'form_with', auto_html5_validation: false do |f| %> 114 | <%= f.text_field :name, id: 'person_name' %> 115 | <%= f.text_field :email, id: 'person_email' %> 116 | <% end %> 117 | <% end %> 118 | ERB 119 | end 120 | 121 | def new_with_required_true 122 | @person = Person.new 123 | render inline: <<-ERB 124 | <%= form_for @person, html: { id: 'form_for' } do |f| %> 125 | <%= f.text_field :email, required: true %> 126 | <% end %> 127 | 128 | <% if Rails::VERSION::STRING >= '5.1' %> 129 | <%= form_with model: @person, id: 'form_with' do |f| %> 130 | <%= f.text_field :email, required: true, id: 'person_email' %> 131 | <% end %> 132 | <% end %> 133 | ERB 134 | end 135 | 136 | def new_with_required_false 137 | @person = Person.new 138 | render inline: <<-ERB 139 | <%= form_for @person, html: { id: 'form_for' } do |f| %> 140 | <%= f.text_field :email, required: false %> 141 | <% end %> 142 | 143 | <% if Rails::VERSION::STRING >= '5.1' %> 144 | <%= form_with model: @person, id: 'form_with' do |f| %> 145 | <%= f.text_field :email, required: false, id: 'person_email' %> 146 | <% end %> 147 | <% end %> 148 | ERB 149 | end 150 | end 151 | class ItemsController < ApplicationController 152 | def new 153 | @item = Item.new 154 | render inline: <<-ERB 155 | <%= form_for @item, html: { id: 'form_for' } do |f| %> 156 | <%= f.text_field :name %> 157 | <%= f.text_area :description %> 158 | <% end %> 159 | 160 | <% if Rails::VERSION::STRING >= '5.1' %> 161 | <%= form_with model: @item, id: 'form_with' do |f| %> 162 | <%= f.text_field :name, id: 'item_name' %> 163 | <%= f.text_area :description, id: 'item_description' %> 164 | <% end %> 165 | <% end %> 166 | ERB 167 | end 168 | 169 | def new_without_html5_validation 170 | @item = Item.new 171 | render inline: <<-ERB 172 | <%= form_for @item, html: { id: 'form_for' }, auto_html5_validation: false do |f| %> 173 | <%= f.text_field :name %> 174 | <%= f.text_area :description %> 175 | <% end %> 176 | 177 | <% if Rails::VERSION::STRING >= '5.1' %> 178 | <%= form_with model: @item, id: 'form_with', auto_html5_validation: false do |f| %> 179 | <%= f.text_field :name, id: 'item_name' %> 180 | <%= f.text_area :description, id: 'item_description' %> 181 | <% end %> 182 | <% end %> 183 | ERB 184 | end 185 | 186 | def new_with_required_true 187 | @item = Item.new 188 | render inline: <<-ERB 189 | <%= form_for @item, html: { id: 'form_for' } do |f| %> 190 | <%= f.text_field :name, required: true %> 191 | <% end %> 192 | 193 | <% if Rails::VERSION::STRING >= '5.1' %> 194 | <%= form_with model: @item, id: 'form_with' do |f| %> 195 | <%= f.text_field :name, required: true, id: 'item_name' %> 196 | <% end %> 197 | <% end %> 198 | ERB 199 | end 200 | 201 | def new_with_required_false 202 | @item = Item.new 203 | render inline: <<-ERB 204 | <%= form_for @item, html: { id: 'form_for' } do |f| %> 205 | <%= f.text_field :name, required: false %> 206 | <% end %> 207 | 208 | <% if Rails::VERSION::STRING >= '5.1' %> 209 | <%= form_with model: @item, id: 'form_with' do |f| %> 210 | <%= f.text_field :name, required: false, id: 'item_name' %> 211 | <% end %> 212 | <% end %> 213 | ERB 214 | end 215 | end 216 | 217 | # helpers 218 | module ApplicationHelper; end 219 | -------------------------------------------------------------------------------- /test/features/active_model_validation_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test_helper' 4 | 5 | class ActiveModelValidationTest < ActionDispatch::IntegrationTest 6 | FORM_ID_LIST = if Rails::VERSION::STRING >= '5.1' 7 | %w[#form_for #form_with].freeze 8 | else 9 | %w[#form_for].freeze 10 | end 11 | 12 | teardown do 13 | Item._validators.clear 14 | end 15 | 16 | sub_test_case 'without validation' do 17 | test 'new form' do 18 | visit '/items/new' 19 | FORM_ID_LIST.each do |form| 20 | assert page.has_css? "#{form} input#item_name" 21 | assert page.has_no_css? "#{form} input#item_name[required=required]" 22 | end 23 | end 24 | 25 | test 'new_without_html5_validation form' do 26 | visit '/items/new_without_html5_validation' 27 | FORM_ID_LIST.each do |form| 28 | assert page.has_css? "#{form} textarea#item_description" 29 | assert page.has_no_css? "#{form} textarea#item_description[required=required]" 30 | end 31 | end 32 | end 33 | 34 | sub_test_case 'with required validation' do 35 | setup do 36 | Item.validates_presence_of :name, :description 37 | end 38 | test 'new form' do 39 | visit '/items/new' 40 | FORM_ID_LIST.each do |form| 41 | assert_equal 'required', find("#{form} input#item_name")[:required] 42 | assert_equal 'required', find("#{form} textarea#item_description")[:required] 43 | end 44 | end 45 | test 'new_without_html5_validation form' do 46 | visit '/items/new_without_html5_validation' 47 | FORM_ID_LIST.each do |form| 48 | assert_nil find("#{form} input#item_name")[:required] 49 | end 50 | end 51 | test 'new_with_required_true form' do 52 | visit '/items/new_with_required_true' 53 | FORM_ID_LIST.each do |form| 54 | assert_equal 'required', find("#{form} input#item_name")[:required] 55 | end 56 | end 57 | test 'new_with_required_false form' do 58 | visit '/items/new_with_required_false' 59 | FORM_ID_LIST.each do |form| 60 | assert_nil find("#{form} input#item_name")[:required] 61 | end 62 | end 63 | sub_test_case 'disabling html5_validation in class level' do 64 | setup do 65 | Item.class_eval do |kls| 66 | kls.auto_html5_validation = false 67 | end 68 | end 69 | teardown do 70 | Item.class_eval do |kls| 71 | kls.auto_html5_validation = nil 72 | end 73 | end 74 | test 'new form' do 75 | visit '/items/new' 76 | FORM_ID_LIST.each do |form| 77 | assert_nil find("#{form} input#item_name")[:required] 78 | end 79 | end 80 | end 81 | 82 | sub_test_case 'disabling html5_validations in gem' do 83 | setup do 84 | Html5Validators.enabled = false 85 | end 86 | teardown do 87 | Html5Validators.enabled = true 88 | end 89 | test 'new form' do 90 | visit '/items/new' 91 | FORM_ID_LIST.each do |form| 92 | assert_nil find("#{form} input#item_name")[:required] 93 | assert_nil find("#{form} textarea#item_description")[:required] 94 | end 95 | end 96 | end 97 | end 98 | 99 | sub_test_case 'with maxlength validation' do 100 | setup do 101 | Item.validates_length_of :name, maximum: 20 102 | Item.validates_length_of :description, maximum: 100 103 | end 104 | 105 | test 'new form' do 106 | visit '/items/new' 107 | FORM_ID_LIST.each do |form| 108 | assert_equal '20', find("#{form} input#item_name")[:maxlength] 109 | assert_equal '100', find("#{form} textarea#item_description")[:maxlength] 110 | end 111 | end 112 | end 113 | 114 | sub_test_case 'with minlength validation' do 115 | setup do 116 | Item.validates_length_of :name, minimum: 3 117 | Item.validates_length_of :description, minimum: 10 118 | end 119 | 120 | test 'new form' do 121 | visit '/items/new' 122 | FORM_ID_LIST.each do |form| 123 | assert_equal '3', find("#{form} input#item_name")[:minlength] 124 | assert_equal '10', find("#{form} textarea#item_description")[:minlength] 125 | end 126 | end 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /test/features/active_record_validation_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test_helper' 4 | 5 | class ActiveRecordValidationTest < ActionDispatch::IntegrationTest 6 | FORM_ID_LIST = if Rails::VERSION::STRING >= '5.1' 7 | %w[#form_for #form_with].freeze 8 | else 9 | %w[#form_for].freeze 10 | end 11 | 12 | teardown do 13 | Person._validators.clear 14 | end 15 | 16 | sub_test_case 'without validation' do 17 | test 'new form' do 18 | visit '/people/new' 19 | FORM_ID_LIST.each do |form| 20 | assert page.has_css? "#{form} input#person_name" 21 | assert page.has_no_css? "#{form} input#person_name[required=required]" 22 | end 23 | end 24 | 25 | test 'new_without_html5_validation form' do 26 | visit '/people/new_without_html5_validation' 27 | FORM_ID_LIST.each do |form| 28 | assert page.has_css? "#{form} input#person_email" 29 | assert page.has_no_css? "#{form} input#person_email[required=required]" 30 | end 31 | end 32 | end 33 | 34 | sub_test_case 'with required validation' do 35 | setup do 36 | Person.validates_presence_of :name, :password, :bio, :blood_type, :terms_of_service, :user_type 37 | end 38 | test 'new form' do 39 | visit '/people/new' 40 | FORM_ID_LIST.each do |form| 41 | assert_equal 'required', find("#{form} input#person_name")[:required] 42 | assert_equal 'required', find("#{form} input#person_password")[:required] 43 | assert_equal 'required', find("#{form} textarea#person_bio")[:required] 44 | assert_equal 'required', find("#{form} select[name='person[blood_type]']")[:required] 45 | assert_equal 'required', find("#{form} input[type=checkbox][name='person[terms_of_service]']")[:required] 46 | assert_equal 2, all("#{form} input[name='person[user_type]'][required=required]").size 47 | end 48 | end 49 | test 'new_without_html5_validation form' do 50 | visit '/people/new_without_html5_validation' 51 | FORM_ID_LIST.each do |form| 52 | assert_nil find("#{form} input#person_name")[:required] 53 | end 54 | end 55 | test 'new_with_required_true form' do 56 | visit '/people/new_with_required_true' 57 | FORM_ID_LIST.each do |form| 58 | assert_equal 'required', find("#{form} input#person_email")[:required] 59 | end 60 | end 61 | test 'new_with_required_false form' do 62 | visit '/people/new_with_required_false' 63 | FORM_ID_LIST.each do |form| 64 | assert_nil find("#{form} input#person_email")[:required] 65 | end 66 | end 67 | sub_test_case 'disabling html5_validation in class level' do 68 | setup do 69 | Person.class_eval do |kls| 70 | kls.auto_html5_validation = false 71 | end 72 | end 73 | teardown do 74 | Person.class_eval do |kls| 75 | kls.auto_html5_validation = nil 76 | end 77 | end 78 | test 'new form' do 79 | visit '/people/new' 80 | FORM_ID_LIST.each do |form| 81 | assert_nil find("#{form} input#person_name")[:required] 82 | end 83 | end 84 | end 85 | 86 | sub_test_case 'disabling html5_validations in gem' do 87 | setup do 88 | Html5Validators.enabled = false 89 | end 90 | teardown do 91 | Html5Validators.enabled = true 92 | end 93 | test 'new form' do 94 | visit '/people/new' 95 | FORM_ID_LIST.each do |form| 96 | assert_nil find("#{form} input#person_name")[:required] 97 | assert_nil find("#{form} input#person_password")[:required] 98 | assert_nil find("#{form} textarea#person_bio")[:required] 99 | assert_nil find("#{form} select[name='person[blood_type]']")[:required] 100 | assert_nil find("#{form} input[type=checkbox][name='person[terms_of_service]']")[:required] 101 | all("#{form} input[name='person[user_type]']").each do |radio| 102 | assert_nil radio[:required] 103 | end 104 | end 105 | end 106 | end 107 | end 108 | 109 | sub_test_case 'with maxlength validation' do 110 | setup do 111 | Person.validates_length_of :name, maximum: 20 112 | Person.validates_length_of :bio, maximum: 100 113 | end 114 | 115 | test 'new form' do 116 | visit '/people/new' 117 | FORM_ID_LIST.each do |form| 118 | assert_equal '20', find("#{form} input#person_name")[:maxlength] 119 | assert_equal '100', find("#{form} textarea#person_bio")[:maxlength] 120 | end 121 | end 122 | end 123 | 124 | sub_test_case 'with minlength validation' do 125 | setup do 126 | Person.validates_length_of :name, minimum: 3 127 | Person.validates_length_of :bio, minimum: 10 128 | end 129 | 130 | test 'new form' do 131 | visit '/people/new' 132 | FORM_ID_LIST.each do |form| 133 | assert_equal '3', find("#{form} input#person_name")[:minlength] 134 | assert_equal '10', find("#{form} textarea#person_bio")[:minlength] 135 | end 136 | end 137 | end 138 | 139 | sub_test_case 'validation with context' do 140 | sub_test_case 'with an active context' do 141 | setup do 142 | Person.validates_presence_of :name, on: :create 143 | Person.validates_length_of :bio, maximum: 100, on: :create 144 | end 145 | test 'new form' do 146 | visit '/people/new' 147 | FORM_ID_LIST.each do |form| 148 | assert_equal 'required', find("#{form} input#person_name")[:required] 149 | assert_equal '100', find("#{form} textarea#person_bio")[:maxlength] 150 | end 151 | end 152 | end 153 | 154 | sub_test_case 'without an active context' do 155 | setup do 156 | Person.validates_presence_of :name, on: :update 157 | end 158 | test 'new form' do 159 | visit '/people/new' 160 | FORM_ID_LIST.each do |form| 161 | assert_nil find("#{form} input#person_name")[:required] 162 | assert_nil find("#{form} textarea#person_bio")[:maxlength] 163 | end 164 | end 165 | end 166 | end 167 | end 168 | -------------------------------------------------------------------------------- /test/models/active_record/inherited_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test_helper' 4 | 5 | if defined? ActiveRecord 6 | class Html5Validators::ActiveRecordExtensionTest < ActiveSupport::TestCase 7 | test 'An AR model' do 8 | assert_respond_to Class.new(ActiveRecord::Base), :auto_html5_validation 9 | end 10 | 11 | test "Changing a model's auto_html5_validation value doesn't affect other model's auto_html5_validation value" do 12 | cow = Class.new ApplicationRecord 13 | horse = Class.new ApplicationRecord 14 | cow.auto_html5_validation = false 15 | 16 | assert_equal true, horse.auto_html5_validation 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV['RAILS_ENV'] ||= 'test' 4 | 5 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 6 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 7 | # require logger before rails, or Rails 6 fails to boot 8 | require 'logger' 9 | # load Rails first 10 | require 'rails' 11 | require 'bundler/setup' 12 | Bundler.require 13 | 14 | begin 15 | require 'rackup/handler' 16 | # Work around "uninitialized constant Rack::Handler" on Capybara here: https://github.com/teamcapybara/capybara/blob/0480f90168a40780d1398c75031a255c1819dce8/lib/capybara/registrations/servers.rb#L31 17 | ::Rack::Handler = ::Rackup::Handler unless defined?(::Rack::Handler) 18 | rescue LoadError # Rails < 7.1 19 | require 'rack/handler' 20 | end 21 | 22 | require 'capybara' 23 | require 'selenium/webdriver' 24 | 25 | # needs to load the app before loading rspec/rails => capybara 26 | require 'fake_app' 27 | 28 | require 'active_support/test_case' 29 | require 'test/unit/active_support' 30 | require 'test/unit/capybara' 31 | require 'capybara/rails' 32 | require 'capybara/dsl' 33 | ActionDispatch::IntegrationTest.send :include, Capybara::DSL 34 | 35 | # Requires supporting files with custom matchers and macros, etc, 36 | # in ./support/ and its subdirectories. 37 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} 38 | 39 | begin 40 | require "action_dispatch/system_test_case" 41 | rescue LoadError 42 | Capybara.register_driver :chrome do |app| 43 | options = Selenium::WebDriver::Chrome::Options.new(args: %w[no-sandbox headless disable-gpu]) 44 | Capybara::Selenium::Driver.new(app, browser: :chrome, options: options) 45 | end 46 | Capybara.javascript_driver = :chrome 47 | else 48 | if ActionPack::VERSION::STRING > '5.2' 49 | ActionDispatch::SystemTestCase.driven_by :selenium, using: :headless_chrome 50 | else 51 | ActionDispatch::SystemTestCase.driven_by :selenium_chrome_headless 52 | end 53 | end 54 | --------------------------------------------------------------------------------