bootstrap_form 5.1.0 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (168) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +21 -8
  3. data/.gitignore +4 -2
  4. data/.rubocop.yml +2 -1
  5. data/.yarnrc +5 -0
  6. data/CHANGELOG.md +11 -3
  7. data/CONTRIBUTING.md +101 -14
  8. data/Dangerfile +4 -4
  9. data/Dockerfile +5 -5
  10. data/Gemfile +5 -21
  11. data/README.md +68 -21
  12. data/RELEASING.md +12 -1
  13. data/Rakefile +7 -6
  14. data/bootstrap_form.gemspec +6 -8
  15. data/docker-compose.yml +6 -24
  16. data/gemfiles/6.0.gemfile +1 -1
  17. data/gemfiles/6.1.gemfile +1 -1
  18. data/gemfiles/7.0.gemfile +2 -3
  19. data/gemfiles/common.gemfile +28 -0
  20. data/gemfiles/edge.gemfile +3 -2
  21. data/lib/bootstrap_form/components/labels.rb +3 -9
  22. data/lib/bootstrap_form/components/validation.rb +36 -19
  23. data/lib/bootstrap_form/form_builder.rb +3 -0
  24. data/lib/bootstrap_form/form_group.rb +9 -6
  25. data/lib/bootstrap_form/form_group_builder.rb +10 -7
  26. data/lib/bootstrap_form/helpers/bootstrap.rb +7 -8
  27. data/lib/bootstrap_form/helpers/field.rb +26 -0
  28. data/lib/bootstrap_form/helpers.rb +1 -0
  29. data/lib/bootstrap_form/inputs/check_box.rb +22 -6
  30. data/lib/bootstrap_form/inputs/radio_button.rb +3 -3
  31. data/lib/bootstrap_form/version.rb +2 -1
  32. data/lib/bootstrap_form.rb +1 -1
  33. metadata +14 -146
  34. data/demo/.postcssrc.yml +0 -3
  35. data/demo/.ruby-version +0 -1
  36. data/demo/Gemfile +0 -80
  37. data/demo/Gemfile.lock +0 -261
  38. data/demo/Procfile.dev +0 -2
  39. data/demo/README.md +0 -17
  40. data/demo/Rakefile +0 -6
  41. data/demo/app/assets/builds/.keep +0 -0
  42. data/demo/app/assets/builds/application.js.LICENSE.txt +0 -9
  43. data/demo/app/assets/config/manifest.js +0 -2
  44. data/demo/app/assets/stylesheets/actiontext.css +0 -31
  45. data/demo/app/assets/stylesheets/actiontext.scss +0 -38
  46. data/demo/app/assets/stylesheets/application.scss +0 -1
  47. data/demo/app/controllers/application_controller.rb +0 -2
  48. data/demo/app/controllers/bootstrap_controller.rb +0 -29
  49. data/demo/app/controllers/users_controller.rb +0 -9
  50. data/demo/app/helpers/bootstrap_helper.rb +0 -27
  51. data/demo/app/javascript/application.js +0 -3
  52. data/demo/app/javascript/channels/consumer.js +0 -6
  53. data/demo/app/javascript/channels/index.js +0 -5
  54. data/demo/app/javascript/packs/application.js +0 -11
  55. data/demo/app/models/address.rb +0 -3
  56. data/demo/app/models/application_record.rb +0 -3
  57. data/demo/app/models/faux_user.rb +0 -9
  58. data/demo/app/models/skill.rb +0 -15
  59. data/demo/app/models/super_user.rb +0 -2
  60. data/demo/app/models/user.rb +0 -25
  61. data/demo/app/views/active_storage/blobs/_blob.html.erb +0 -14
  62. data/demo/app/views/bootstrap/form.html.erb +0 -67
  63. data/demo/app/views/layouts/action_text/contents/_content.html.erb +0 -3
  64. data/demo/app/views/layouts/application.html.erb +0 -76
  65. data/demo/bin/bundle +0 -3
  66. data/demo/bin/dev +0 -9
  67. data/demo/bin/rails +0 -4
  68. data/demo/bin/rake +0 -4
  69. data/demo/bin/setup +0 -36
  70. data/demo/bin/update +0 -31
  71. data/demo/bin/webpack +0 -15
  72. data/demo/bin/webpack-dev-server +0 -15
  73. data/demo/bin/yarn +0 -11
  74. data/demo/config/application.rb +0 -21
  75. data/demo/config/boot.rb +0 -5
  76. data/demo/config/database.yml +0 -21
  77. data/demo/config/environment.rb +0 -5
  78. data/demo/config/environments/development.rb +0 -60
  79. data/demo/config/environments/production.rb +0 -48
  80. data/demo/config/environments/test.rb +0 -46
  81. data/demo/config/initializers/application_controller_renderer.rb +0 -8
  82. data/demo/config/initializers/backtrace_silencers.rb +0 -7
  83. data/demo/config/initializers/cookies_serializer.rb +0 -5
  84. data/demo/config/initializers/filter_parameter_logging.rb +0 -4
  85. data/demo/config/initializers/inflections.rb +0 -16
  86. data/demo/config/initializers/mime_types.rb +0 -4
  87. data/demo/config/initializers/wrap_parameters.rb +0 -14
  88. data/demo/config/locales/en.yml +0 -33
  89. data/demo/config/puma.rb +0 -56
  90. data/demo/config/routes.rb +0 -6
  91. data/demo/config/spring.rb +0 -6
  92. data/demo/config/storage.yml +0 -35
  93. data/demo/config/webpack/development.js +0 -5
  94. data/demo/config/webpack/environment.js +0 -3
  95. data/demo/config/webpack/production.js +0 -5
  96. data/demo/config/webpack/test.js +0 -5
  97. data/demo/config/webpacker.yml +0 -92
  98. data/demo/config.ru +0 -5
  99. data/demo/db/schema.rb +0 -84
  100. data/demo/doc/screenshots/bootstrap/index/00_horizontal_form.png +0 -0
  101. data/demo/doc/screenshots/bootstrap/index/01_with_validation_error.png +0 -0
  102. data/demo/doc/screenshots/bootstrap/index/02_inline_form.png +0 -0
  103. data/demo/doc/screenshots/bootstrap/index/03_simple_action_text_example.png +0 -0
  104. data/demo/doc/screenshots/bootstrap/index/04_floating_labels.png +0 -0
  105. data/demo/doc/screenshots/bootstrap/readme/00_example.png +0 -0
  106. data/demo/doc/screenshots/bootstrap/readme/01_example.png +0 -0
  107. data/demo/doc/screenshots/bootstrap/readme/02_example.png +0 -0
  108. data/demo/doc/screenshots/bootstrap/readme/03_example.png +0 -0
  109. data/demo/doc/screenshots/bootstrap/readme/04_example.png +0 -0
  110. data/demo/doc/screenshots/bootstrap/readme/05_example.png +0 -0
  111. data/demo/doc/screenshots/bootstrap/readme/06_example.png +0 -0
  112. data/demo/doc/screenshots/bootstrap/readme/07_example.png +0 -0
  113. data/demo/doc/screenshots/bootstrap/readme/08_example.png +0 -0
  114. data/demo/doc/screenshots/bootstrap/readme/09_example.png +0 -0
  115. data/demo/doc/screenshots/bootstrap/readme/10_example.png +0 -0
  116. data/demo/doc/screenshots/bootstrap/readme/11_example.png +0 -0
  117. data/demo/doc/screenshots/bootstrap/readme/12_example.png +0 -0
  118. data/demo/doc/screenshots/bootstrap/readme/13_example.png +0 -0
  119. data/demo/doc/screenshots/bootstrap/readme/14_example.png +0 -0
  120. data/demo/doc/screenshots/bootstrap/readme/15_example.png +0 -0
  121. data/demo/doc/screenshots/bootstrap/readme/16_example.png +0 -0
  122. data/demo/doc/screenshots/bootstrap/readme/17_example.png +0 -0
  123. data/demo/doc/screenshots/bootstrap/readme/18_example.png +0 -0
  124. data/demo/doc/screenshots/bootstrap/readme/19_example.png +0 -0
  125. data/demo/doc/screenshots/bootstrap/readme/20_example.png +0 -0
  126. data/demo/doc/screenshots/bootstrap/readme/21_example.png +0 -0
  127. data/demo/doc/screenshots/bootstrap/readme/22_example.png +0 -0
  128. data/demo/doc/screenshots/bootstrap/readme/23_example.png +0 -0
  129. data/demo/doc/screenshots/bootstrap/readme/24_example.png +0 -0
  130. data/demo/doc/screenshots/bootstrap/readme/25_example.png +0 -0
  131. data/demo/doc/screenshots/bootstrap/readme/26_example.png +0 -0
  132. data/demo/doc/screenshots/bootstrap/readme/27_example.png +0 -0
  133. data/demo/doc/screenshots/bootstrap/readme/28_example.png +0 -0
  134. data/demo/doc/screenshots/bootstrap/readme/29_example.png +0 -0
  135. data/demo/doc/screenshots/bootstrap/readme/30_example.png +0 -0
  136. data/demo/doc/screenshots/bootstrap/readme/31_example.png +0 -0
  137. data/demo/doc/screenshots/bootstrap/readme/32_example.png +0 -0
  138. data/demo/doc/screenshots/bootstrap/readme/33_example.png +0 -0
  139. data/demo/doc/screenshots/bootstrap/readme/34_example.png +0 -0
  140. data/demo/doc/screenshots/bootstrap/readme/35_example.png +0 -0
  141. data/demo/doc/screenshots/bootstrap/readme/36_example.png +0 -0
  142. data/demo/doc/screenshots/bootstrap/readme/37_example.png +0 -0
  143. data/demo/doc/screenshots/bootstrap/readme/38_example.png +0 -0
  144. data/demo/doc/screenshots/bootstrap/readme/39_example.png +0 -0
  145. data/demo/doc/screenshots/bootstrap/readme/40_example.png +0 -0
  146. data/demo/doc/screenshots/bootstrap/readme/41_example.png +0 -0
  147. data/demo/doc/screenshots/bootstrap/readme/42_example.png +0 -0
  148. data/demo/doc/screenshots/bootstrap/readme/43_example.png +0 -0
  149. data/demo/doc/screenshots/bootstrap/readme/44_example.png +0 -0
  150. data/demo/doc/screenshots/bootstrap/readme/45_example.png +0 -0
  151. data/demo/doc/screenshots/bootstrap/readme/46_example.png +0 -0
  152. data/demo/doc/screenshots/bootstrap/readme/47_example.png +0 -0
  153. data/demo/doc/screenshots/bootstrap/readme/48_example.png +0 -0
  154. data/demo/doc/screenshots/bootstrap/readme/49_example.png +0 -0
  155. data/demo/doc/screenshots/bootstrap/readme/50_example.png +0 -0
  156. data/demo/log/.keep +0 -0
  157. data/demo/package.json +0 -21
  158. data/demo/public/favicon.ico +0 -0
  159. data/demo/test/application_system_test_case.rb +0 -8
  160. data/demo/test/controllers/bootstrap_controller_test.rb +0 -8
  161. data/demo/test/controllers/users_controller_test.rb +0 -13
  162. data/demo/test/fixtures/action_text/rich_texts.yml +0 -4
  163. data/demo/test/fixtures/users.yml +0 -2
  164. data/demo/test/system/bootstrap_test.rb +0 -84
  165. data/demo/test/test_helper.rb +0 -10
  166. data/demo/webpack.config.js +0 -20
  167. data/demo/yarn.lock +0 -7176
  168. data/gemfiles/5.2.gemfile +0 -4
data/Rakefile CHANGED
@@ -17,12 +17,13 @@ end
17
17
 
18
18
  require 'bundler/gem_tasks'
19
19
 
20
- require 'rake/testtask'
20
+ require "minitest/test_task"
21
21
 
22
- Rake::TestTask.new(:test) do |t|
23
- t.libs << 'test'
24
- t.pattern = 'test/**/*_test.rb'
25
- t.verbose = false
22
+ Minitest::TestTask.create(:test) do |t|
23
+ t.libs << "test"
24
+ t.libs << "lib"
25
+ t.warning = false
26
+ t.test_globs = ["test/**/*_test.rb"]
26
27
  end
27
28
 
28
29
  # This automatically updates GitHub Releases whenever we `rake release` the gem
@@ -34,4 +35,4 @@ end
34
35
  desc 'Run RuboCop checks'
35
36
  RuboCop::RakeTask.new(:rubocop)
36
37
 
37
- task default: %i[test rubocop]
38
+ task default: %i[test rubocop:autocorrect]
@@ -3,30 +3,28 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
4
  require "bootstrap_form/version"
5
5
 
6
- REQUIRED_RAILS_VERSION = ">= 5.2".freeze
7
-
8
6
  Gem::Specification.new do |s|
9
7
  s.name = "bootstrap_form"
10
8
  s.version = BootstrapForm::VERSION
11
9
  s.authors = ["Stephen Potenza", "Carlos Lopes"]
12
10
  s.email = ["potenza@gmail.com", "carlos.el.lopes@gmail.com"]
13
11
  s.homepage = "https://github.com/bootstrap-ruby/bootstrap_form"
14
- s.summary = "Rails form builder that makes it easy to style forms using "\
12
+ s.summary = "Rails form builder that makes it easy to style forms using " \
15
13
  "Bootstrap 5"
16
- s.description = "bootstrap_form is a rails form builder that makes it super "\
14
+ s.description = "bootstrap_form is a rails form builder that makes it super " \
17
15
  "easy to create beautiful-looking forms using Bootstrap 5"
18
16
  s.license = "MIT"
19
17
  s.metadata = { "rubygems_mfa_required" => "true" }
20
18
 
21
19
  s.files = `git ls-files -z`.split("\x0").reject do |f|
22
- f.match(%r{^(test)/})
20
+ f.match(%r{^(test)/|^(demo)/})
23
21
  end
24
22
 
25
23
  s.bindir = "exe"
26
24
  s.require_paths = ["lib"]
27
25
 
28
- s.required_ruby_version = ">= 2.7"
26
+ s.required_ruby_version = ">= 3.0"
29
27
 
30
- s.add_dependency("actionpack", REQUIRED_RAILS_VERSION)
31
- s.add_dependency("activemodel", REQUIRED_RAILS_VERSION)
28
+ s.add_dependency("actionpack", BootstrapForm::REQUIRED_RAILS_VERSION)
29
+ s.add_dependency("activemodel", BootstrapForm::REQUIRED_RAILS_VERSION)
32
30
  end
data/docker-compose.yml CHANGED
@@ -5,9 +5,10 @@ services:
5
5
  build:
6
6
  context: .
7
7
  args:
8
- NODE_MAJOR: '12'
9
- YARN_VERSION: '1.22.4'
10
- image: bootstrap-form:0.0.1
8
+ NODE_MAJOR: "12"
9
+ YARN_VERSION: "1.22.4"
10
+ RUBY_VERSION: ${RUBY_VERSION}
11
+ image: bootstrap-form:latest-$RUBY_VERSION
11
12
  tmpfs:
12
13
  - /tmp
13
14
 
@@ -17,15 +18,6 @@ services:
17
18
  tty: true
18
19
  volumes:
19
20
  - .:/app:cached
20
- # - rails_cache:/app/tmp/cache
21
- # - bundle:/app/vendor/bundle
22
- # - node_modules:/app/node_modules
23
- # - packs:/app/public/packs
24
- - /etc/passwd:/etc/passwd:ro
25
- # One or the other of the following lines might be redundant, or one might be
26
- # better than the other.
27
- - ~/.ssh:${HOME}/.ssh
28
- - ${SSH_AUTH_SOCK}:/ssh-agent
29
21
  environment:
30
22
  - SSH_AUTH_SOCK=/ssh-agent
31
23
  - NODE_ENV=development
@@ -34,16 +26,6 @@ services:
34
26
  - WEBPACKER_DEV_SERVER_HOST=webpacker
35
27
  - WEB_CONCURRENCY=1
36
28
  - HISTFILE=/app/.bash_history
29
+ ports:
30
+ - "3000:3000"
37
31
  command: /bin/bash
38
-
39
- # server:
40
- # <<: *shell
41
- # command: sh -c "cd demo/app && bundle exec rails server -b 0.0.0.0"
42
- # ports:
43
- # - '3000:3000'
44
-
45
- # volumes:
46
- # bundle:
47
- # node_modules:
48
- # rails_cache:
49
- # packs:
data/gemfiles/6.0.gemfile CHANGED
@@ -1,4 +1,4 @@
1
- gems = "#{File.dirname __dir__}/Gemfile"
1
+ gems = "#{__dir__}/common.gemfile"
2
2
  eval File.read(gems), binding, gems # rubocop: disable Security/Eval
3
3
 
4
4
  gem "rails", "~> 6.0.0"
data/gemfiles/6.1.gemfile CHANGED
@@ -1,4 +1,4 @@
1
- gems = "#{File.dirname __dir__}/Gemfile"
1
+ gems = "#{__dir__}/common.gemfile"
2
2
  eval File.read(gems), binding, gems # rubocop: disable Security/Eval
3
3
 
4
4
  gem "rails", "~> 6.1.0"
data/gemfiles/7.0.gemfile CHANGED
@@ -1,6 +1,5 @@
1
- gems = "#{File.dirname __dir__}/Gemfile"
1
+ gems = "#{__dir__}/common.gemfile"
2
2
  eval File.read(gems), binding, gems # rubocop: disable Security/Eval
3
3
 
4
- # 7.0.0 has an issue with Ruby 3.1.
5
- # And a test case has an issue with 7.0.1
6
4
  gem "rails", "~> 7.0.2"
5
+ gem "sprockets-rails", require: "sprockets/railtie"
@@ -0,0 +1,28 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec path: File.dirname(__dir__)
4
+
5
+ # To test with different Rails versions, use the files in `./gemfiles`
6
+
7
+ group :development do
8
+ gem "htmlbeautifier"
9
+ gem "puma"
10
+ gem "rubocop-performance", require: false
11
+ gem "rubocop-rails", require: false
12
+ end
13
+
14
+ group :test do
15
+ gem "diffy"
16
+ gem "equivalent-xml"
17
+ gem "mocha"
18
+ gem "sqlite3"
19
+ end
20
+
21
+ group :development, :test do
22
+ gem "debug"
23
+ gem "pry-byebug"
24
+ end
25
+
26
+ group :ci do
27
+ gem "danger"
28
+ end
@@ -1,4 +1,5 @@
1
- gems = "#{File.dirname __dir__}/Gemfile"
1
+ gems = "#{__dir__}/common.gemfile"
2
2
  eval File.read(gems), binding, gems # rubocop: disable Security/Eval
3
3
 
4
- gem "rails", git: "https://github.com/rails/rails.git"
4
+ gem "rails", git: "https://github.com/rails/rails.git", branch: "main"
5
+ gem "sprockets-rails", require: "sprockets/railtie"
@@ -24,14 +24,8 @@ module BootstrapForm
24
24
 
25
25
  def label_classes(name, options, custom_label_col, group_layout)
26
26
  classes = ["form-label", options[:class], label_layout_classes(custom_label_col, group_layout)]
27
-
28
- case options.delete(:required)
29
- when true
30
- classes << "required"
31
- when nil, :default
32
- classes << "required" if required_attribute?(object, name)
33
- end
34
-
27
+ classes << "required" if required_field_options(options, name)[:required]
28
+ options.delete(:required)
35
29
  classes << "text-danger" if label_errors && error?(name)
36
30
  classes.flatten.compact
37
31
  end
@@ -46,7 +40,7 @@ module BootstrapForm
46
40
 
47
41
  def label_text(name, options)
48
42
  if label_errors && error?(name)
49
- (options[:text] || object.class.human_attribute_name(name)).to_s.concat(" #{get_error_messages(name)}")
43
+ (options[:text] || object.class.human_attribute_name(name)).to_s + " #{get_error_messages(name)}"
50
44
  else
51
45
  options[:text] || object&.class.try(:human_attribute_name, name)
52
46
  end
@@ -8,35 +8,45 @@ module BootstrapForm
8
8
  private
9
9
 
10
10
  def error?(name)
11
- object.respond_to?(:errors) && !(name.nil? || object.errors[name].empty?)
11
+ name && object.respond_to?(:errors) && (object.errors[name].any? || association_error?(name))
12
+ end
13
+
14
+ def association_error?(name)
15
+ object.class.reflections.any? do |association_name, a|
16
+ next unless a.is_a?(ActiveRecord::Reflection::BelongsToReflection)
17
+ next unless a.foreign_key == name.to_s
18
+
19
+ object.errors[association_name].any?
20
+ end
12
21
  end
13
22
 
14
23
  def required_attribute?(obj, attribute)
15
24
  return false unless obj && attribute
16
25
 
17
26
  target = obj.instance_of?(Class) ? obj : obj.class
27
+ return false unless target.respond_to? :validators_on
18
28
 
19
- target_validators = if target.respond_to? :validators_on
20
- target.validators_on(attribute).map(&:class)
21
- else
22
- []
23
- end
24
-
25
- presence_validator?(target_validators)
29
+ presence_validator?(target_validators(target, attribute)) ||
30
+ required_association?(target, attribute)
26
31
  end
27
32
 
28
- def presence_validator?(target_validators)
29
- has_presence_validator = target_validators.include?(
30
- ActiveModel::Validations::PresenceValidator
31
- )
32
-
33
- if defined? ActiveRecord::Validations::PresenceValidator
34
- has_presence_validator |= target_validators.include?(
35
- ActiveRecord::Validations::PresenceValidator
36
- )
33
+ def required_association?(target, attribute)
34
+ target.reflections.find do |name, a|
35
+ next unless a.is_a?(ActiveRecord::Reflection::BelongsToReflection)
36
+ next unless a.foreign_key == attribute.to_s
37
+
38
+ presence_validator?(target_validators(target, name))
37
39
  end
40
+ end
41
+
42
+ def target_validators(target, attribute)
43
+ target.validators_on(attribute).map(&:class)
44
+ end
38
45
 
39
- has_presence_validator
46
+ def presence_validator?(target_validators)
47
+ target_validators.include?(ActiveModel::Validations::PresenceValidator) ||
48
+ (defined?(ActiveRecord::Validations::PresenceValidator) &&
49
+ target_validators.include?(ActiveRecord::Validations::PresenceValidator))
40
50
  end
41
51
 
42
52
  def inline_error?(name)
@@ -54,7 +64,14 @@ module BootstrapForm
54
64
  end
55
65
 
56
66
  def get_error_messages(name)
57
- object.errors[name].join(", ")
67
+ messages = object.errors[name]
68
+ object.class.reflections.each do |association_name, a|
69
+ next unless a.is_a?(ActiveRecord::Reflection::BelongsToReflection)
70
+ next unless a.foreign_key == name.to_s
71
+
72
+ messages << object.errors[association_name]
73
+ end
74
+ messages.join(", ")
58
75
  end
59
76
  end
60
77
  end
@@ -1,10 +1,12 @@
1
1
  # require 'bootstrap_form/aliasing'
2
2
 
3
+ # rubocop:disable Metrics/ClassLength
3
4
  module BootstrapForm
4
5
  class FormBuilder < ActionView::Helpers::FormBuilder
5
6
  attr_reader :layout, :label_col, :control_col, :has_error, :inline_errors,
6
7
  :label_errors, :acts_like_form_tag
7
8
 
9
+ include BootstrapForm::Helpers::Field
8
10
  include BootstrapForm::Helpers::Bootstrap
9
11
 
10
12
  include BootstrapForm::FormGroupBuilder
@@ -130,3 +132,4 @@ module BootstrapForm
130
132
  end
131
133
  end
132
134
  end
135
+ # rubocop:enable Metrics/ClassLength
@@ -32,15 +32,18 @@ module BootstrapForm
32
32
  end
33
33
  end
34
34
 
35
- def form_group_content(label, help_text, options, &block)
35
+ def form_group_content(label, help_text, options, &block) # rubocop:disable Metrics/AbcSize
36
+ label ||= ActiveSupport::SafeBuffer.new
36
37
  if group_layout_horizontal?(options[:layout])
37
- concat(label).concat(tag.div(capture(&block) + help_text, class: form_group_control_class(options)))
38
+ label + tag.div(capture(&block) + help_text, class: form_group_control_class(options))
38
39
  else
40
+ content = ActiveSupport::SafeBuffer.new
39
41
  # Floating labels need to be rendered after the field
40
- concat(label) unless options[:floating]
41
- concat(capture(&block))
42
- concat(label) if options[:floating]
43
- concat(help_text) if help_text
42
+ content << label unless options[:floating]
43
+ content << capture(&block)
44
+ content << label if options[:floating]
45
+ content << help_text if help_text
46
+ content
44
47
  end
45
48
  end
46
49
 
@@ -28,8 +28,7 @@ module BootstrapForm
28
28
  def form_group_builder_options(options, method)
29
29
  options.symbolize_keys!
30
30
  options = convert_form_tag_options(method, options) if acts_like_form_tag
31
- options[:required] = form_group_required(options) if !options[:skip_label] && options.key?(:skip_required)
32
- options
31
+ options.merge!(required_field_options(options, method))
33
32
  end
34
33
 
35
34
  def convert_form_tag_options(method, options={})
@@ -77,11 +76,15 @@ module BootstrapForm
77
76
  classes
78
77
  end
79
78
 
80
- def form_group_required(options)
81
- return unless options.key?(:skip_required)
82
-
83
- warn "`:skip_required` is deprecated, use `:required: false` instead"
84
- options[:skip_required] ? false : :default
79
+ def form_group_required(options, method)
80
+ if options[:skip_required]
81
+ warn "`:skip_required` is deprecated, use `:required: false` instead"
82
+ false
83
+ elsif options.key?(:required)
84
+ options[:required]
85
+ else
86
+ required_attribute?(object, method)
87
+ end
85
88
  end
86
89
 
87
90
  def form_group_css_options(method, html_options, options)
@@ -30,8 +30,7 @@ module BootstrapForm
30
30
  if options[:error_summary] == false
31
31
  title
32
32
  else
33
- concat tag.p title
34
- concat error_summary
33
+ tag.p(title) + error_summary
35
34
  end
36
35
  end
37
36
  end
@@ -39,9 +38,9 @@ module BootstrapForm
39
38
  def error_summary
40
39
  return unless object.errors.any?
41
40
 
42
- tag.ul class: "rails-bootstrap-forms-error-summary" do
43
- object.errors.full_messages.each do |error|
44
- concat tag.li(error)
41
+ tag.ul(class: "rails-bootstrap-forms-error-summary") do
42
+ object.errors.full_messages.reduce(ActiveSupport::SafeBuffer.new) do |acc, error|
43
+ acc << tag.li(error)
45
44
  end
46
45
  end
47
46
  end
@@ -83,12 +82,12 @@ module BootstrapForm
83
82
  end
84
83
 
85
84
  def prepend_and_append_input(name, options, &block)
86
- options = options.extract!(:prepend, :append, :input_group_class)
85
+ options = options.extract!(:prepend, :append, :input_group_class).compact
87
86
 
88
87
  input = capture(&block) || ActiveSupport::SafeBuffer.new
89
88
 
90
89
  input = attach_input(options, :prepend) + input + attach_input(options, :append)
91
- input += generate_error(name)
90
+ input << generate_error(name)
92
91
  options.present? &&
93
92
  input = tag.div(input, class: ["input-group", options[:input_group_class]].compact)
94
93
  input
@@ -100,7 +99,7 @@ module BootstrapForm
100
99
  end
101
100
 
102
101
  def input_group_content(content)
103
- return content if /btn/.match?(content)
102
+ return content if content.include?("btn")
104
103
 
105
104
  tag.span(content, class: "input-group-text")
106
105
  end
@@ -0,0 +1,26 @@
1
+ module BootstrapForm
2
+ module Helpers
3
+ module Field
4
+ def required_field_options(options, method)
5
+ required = required_field?(options, method)
6
+ {}.tap do |option|
7
+ option[:required] = required
8
+ option[:aria] = { required: true } if required
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def required_field?(options, method)
15
+ if options[:skip_required]
16
+ warn "`:skip_required` is deprecated, use `:required: false` instead"
17
+ false
18
+ elsif options.key?(:required)
19
+ options[:required]
20
+ else
21
+ required_attribute?(object, method)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -5,5 +5,6 @@ module BootstrapForm
5
5
  extend ActiveSupport::Autoload
6
6
 
7
7
  autoload :Bootstrap
8
+ autoload :Field
8
9
  end
9
10
  end
@@ -9,14 +9,11 @@ module BootstrapForm
9
9
  included do
10
10
  def check_box_with_bootstrap(name, options={}, checked_value="1", unchecked_value="0", &block)
11
11
  options = options.symbolize_keys!
12
- check_box_options = options.except(:class, :label, :label_class, :error_message, :help,
13
- :inline, :hide_label, :skip_label, :wrapper, :wrapper_class, :switch)
14
- check_box_options[:class] = check_box_classes(name, options)
15
12
 
16
13
  tag.div(class: check_box_wrapper_class(options), **options[:wrapper].to_h.except(:class)) do
17
- html = check_box_without_bootstrap(name, check_box_options, checked_value, unchecked_value)
18
- html.concat(check_box_label(name, options, checked_value, &block)) unless options[:skip_label]
19
- html.concat(generate_error(name)) if options[:error_message]
14
+ html = check_box_without_bootstrap(name, check_box_options(name, options), checked_value, unchecked_value)
15
+ html << check_box_label(name, options, checked_value, &block) unless options[:skip_label]
16
+ html << generate_error(name) if options[:error_message]
20
17
  html
21
18
  end
22
19
  end
@@ -26,6 +23,13 @@ module BootstrapForm
26
23
 
27
24
  private
28
25
 
26
+ def check_box_options(name, options)
27
+ check_box_options = options.except(:class, :label, :label_class, :error_message, :help,
28
+ :inline, :hide_label, :skip_label, :wrapper, :wrapper_class, :switch)
29
+ check_box_options[:class] = check_box_classes(name, options)
30
+ check_box_options.merge!(required_field_options(options, name))
31
+ end
32
+
29
33
  def check_box_label(name, options, checked_value, &block)
30
34
  label_name = if options[:multiple]
31
35
  check_box_value(name, checked_value)
@@ -59,6 +63,7 @@ module BootstrapForm
59
63
  def check_box_label_class(options)
60
64
  classes = ["form-check-label"]
61
65
  classes << options[:label_class]
66
+ classes << "required" if options[:required]
62
67
  classes << hide_class if options[:hide_label]
63
68
  classes.flatten.compact
64
69
  end
@@ -72,6 +77,17 @@ module BootstrapForm
72
77
  classes << options[:wrapper_class].presence
73
78
  classes.flatten.compact
74
79
  end
80
+
81
+ def checkbox_required(options, method)
82
+ if options[:skip_required]
83
+ warn "`:skip_required` is deprecated, use `:required: false` instead"
84
+ false
85
+ elsif options.key?(:required)
86
+ options[:required]
87
+ else
88
+ required_attribute?(object, method)
89
+ end
90
+ end
75
91
  end
76
92
  end
77
93
  end
@@ -13,8 +13,8 @@ module BootstrapForm
13
13
  wrapper_attributes[:class] = radio_button_wrapper_class(options)
14
14
  tag.div(**wrapper_attributes) do
15
15
  html = radio_button_without_bootstrap(name, value, radio_button_options(name, options))
16
- html.concat(radio_button_label(name, value, options)) unless options[:skip_label]
17
- html.concat(generate_error(name)) if options[:error_message]
16
+ html << radio_button_label(name, value, options) unless options[:skip_label]
17
+ html << generate_error(name) if options[:error_message]
18
18
  html
19
19
  end
20
20
  end
@@ -28,7 +28,7 @@ module BootstrapForm
28
28
  radio_button_options = options.except(:class, :label, :label_class, :error_message, :help,
29
29
  :inline, :hide_label, :skip_label, :wrapper, :wrapper_class)
30
30
  radio_button_options[:class] = radio_button_classes(name, options)
31
- radio_button_options
31
+ radio_button_options.merge!(required_field_options(options, name))
32
32
  end
33
33
 
34
34
  def radio_button_label(name, value, options)
@@ -1,3 +1,4 @@
1
1
  module BootstrapForm
2
- VERSION = "5.1.0".freeze
2
+ VERSION = "5.2.0".freeze
3
+ REQUIRED_RAILS_VERSION = ">= 6.0".freeze
3
4
  end
@@ -1,7 +1,7 @@
1
1
  # NOTE: The rich_text_area and rich_text_area_tag helpers are defined in a file with a different
2
2
  # name and not in the usual autoload-reachable way.
3
3
  # The following line is definitely need to make `bootstrap_form` work.
4
- if ::Rails::VERSION::STRING > "6"
4
+ if Rails::VERSION::STRING > "6"
5
5
  require "#{Gem::Specification.find_by_name('actiontext').gem_dir}/app/helpers/action_text/tag_helper"
6
6
  end
7
7
  require "action_view"