simple_form 4.0.1 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +68 -8
  3. data/MIT-LICENSE +2 -1
  4. data/README.md +47 -51
  5. data/lib/generators/simple_form/templates/README +2 -3
  6. data/lib/generators/simple_form/templates/config/initializers/simple_form.rb +1 -7
  7. data/lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb +26 -25
  8. data/lib/generators/simple_form/templates/config/initializers/simple_form_foundation.rb +1 -1
  9. data/lib/simple_form.rb +22 -5
  10. data/lib/simple_form/components/errors.rb +5 -1
  11. data/lib/simple_form/components/labels.rb +3 -5
  12. data/lib/simple_form/components/maxlength.rb +0 -4
  13. data/lib/simple_form/components/minlength.rb +0 -4
  14. data/lib/simple_form/form_builder.rb +30 -10
  15. data/lib/simple_form/inputs.rb +2 -0
  16. data/lib/simple_form/inputs/base.rb +4 -4
  17. data/lib/simple_form/inputs/boolean_input.rb +1 -0
  18. data/lib/simple_form/inputs/collection_check_boxes_input.rb +1 -1
  19. data/lib/simple_form/inputs/collection_input.rb +3 -5
  20. data/lib/simple_form/inputs/color_input.rb +14 -0
  21. data/lib/simple_form/inputs/priority_input.rb +0 -4
  22. data/lib/simple_form/inputs/rich_text_area_input.rb +12 -0
  23. data/lib/simple_form/inputs/string_input.rb +1 -1
  24. data/lib/simple_form/tags.rb +6 -2
  25. data/lib/simple_form/version.rb +1 -1
  26. data/lib/simple_form/wrappers/root.rb +8 -3
  27. data/test/action_view_extensions/builder_test.rb +22 -4
  28. data/test/components/label_test.rb +0 -4
  29. data/test/form_builder/association_test.rb +6 -0
  30. data/test/form_builder/error_test.rb +6 -0
  31. data/test/form_builder/general_test.rb +23 -19
  32. data/test/form_builder/input_field_test.rb +3 -9
  33. data/test/form_builder/label_test.rb +1 -1
  34. data/test/form_builder/wrapper_test.rb +8 -1
  35. data/test/inputs/boolean_input_test.rb +8 -0
  36. data/test/inputs/collection_check_boxes_input_test.rb +8 -4
  37. data/test/inputs/collection_radio_buttons_input_test.rb +8 -4
  38. data/test/inputs/collection_select_input_test.rb +6 -4
  39. data/test/inputs/color_input_test.rb +10 -0
  40. data/test/inputs/datetime_input_test.rb +2 -12
  41. data/test/inputs/disabled_test.rb +13 -0
  42. data/test/inputs/discovery_test.rb +21 -0
  43. data/test/inputs/priority_input_test.rb +6 -14
  44. data/test/inputs/rich_text_area_input_test.rb +15 -0
  45. data/test/inputs/string_input_test.rb +8 -15
  46. data/test/support/discovery_inputs.rb +7 -0
  47. data/test/support/misc_helpers.rb +8 -2
  48. data/test/support/models.rb +29 -6
  49. data/test/test_helper.rb +7 -4
  50. metadata +44 -40
  51. data/lib/simple_form/i18n_cache.rb +0 -23
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # Uncomment this and change the path if necessary to include your own
4
4
  # components.
5
- # See https://github.com/plataformatec/simple_form#custom-components to know
5
+ # See https://github.com/heartcombo/simple_form#custom-components to know
6
6
  # more about custom components.
7
7
  # Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f }
8
8
  #
data/lib/simple_form.rb CHANGED
@@ -35,7 +35,18 @@ to
35
35
 
36
36
  def %{name}(wrapper_options)
37
37
 
38
- See https://github.com/plataformatec/simple_form/pull/997 for more information.
38
+ See https://github.com/heartcombo/simple_form/pull/997 for more information.
39
+ WARN
40
+
41
+ FILE_METHODS_DEPRECATION_WARN = <<-WARN
42
+ [SIMPLE_FORM] SimpleForm.file_methods is deprecated and has no effect.
43
+
44
+ Since version 5, Simple Form now supports automatically discover of file inputs for the following Gems: activestorage, carrierwave, paperclip, refile and shrine.
45
+ If you are using a custom method that is not from one of the supported Gems, please change your forms to pass the input type explicitly:
46
+
47
+ <%= form.input :avatar, as: :file %>
48
+
49
+ See http://blog.plataformatec.com.br/2019/09/incorrect-access-control-in-simple-form-cve-2019-16676 for more information.
39
50
  WARN
40
51
 
41
52
  @@configured = false
@@ -120,10 +131,6 @@ See https://github.com/plataformatec/simple_form/pull/997 for more information.
120
131
  mattr_accessor :browser_validations
121
132
  @@browser_validations = true
122
133
 
123
- # Collection of methods to detect if a file type was given.
124
- mattr_accessor :file_methods
125
- @@file_methods = %i[mounted_as file? public_filename attached?]
126
-
127
134
  # Custom mappings for input types. This should be a hash containing a regexp
128
135
  # to match as key, and the input type that will be used when the field name
129
136
  # matches the regexp as value, such as { /count/ => :integer }.
@@ -265,6 +272,16 @@ See https://github.com/plataformatec/simple_form/pull/997 for more information.
265
272
  @@form_class = value
266
273
  end
267
274
 
275
+ def self.file_methods=(file_methods)
276
+ ActiveSupport::Deprecation.warn(FILE_METHODS_DEPRECATION_WARN, caller)
277
+ @@file_methods = file_methods
278
+ end
279
+
280
+ def self.file_methods
281
+ ActiveSupport::Deprecation.warn(FILE_METHODS_DEPRECATION_WARN, caller)
282
+ @@file_methods
283
+ end
284
+
268
285
  # Default way to setup Simple Form. Run rails generate simple_form:install
269
286
  # to create a fresh initializer with all configuration values.
270
287
  def self.setup
@@ -11,7 +11,7 @@ module SimpleForm
11
11
  end
12
12
 
13
13
  def has_errors?
14
- object && object.respond_to?(:errors) && errors.present?
14
+ object_with_errors? || object.nil? && has_custom_error?
15
15
  end
16
16
 
17
17
  def has_value?
@@ -34,6 +34,10 @@ module SimpleForm
34
34
  has_custom_error? ? options[:error] : full_errors.send(error_method)
35
35
  end
36
36
 
37
+ def object_with_errors?
38
+ object && object.respond_to?(:errors) && errors.present?
39
+ end
40
+
37
41
  def error_method
38
42
  options[:error_method] || SimpleForm.error_method
39
43
  end
@@ -6,11 +6,9 @@ module SimpleForm
6
6
 
7
7
  module ClassMethods #:nodoc:
8
8
  def translate_required_html
9
- i18n_cache :translate_required_html do
10
- I18n.t(:"required.html", scope: i18n_scope, default:
11
- %(<abbr title="#{translate_required_text}">#{translate_required_mark}</abbr>)
12
- )
13
- end
9
+ I18n.t(:"required.html", scope: i18n_scope, default:
10
+ %(<abbr title="#{translate_required_text}">#{translate_required_mark}</abbr>)
11
+ )
14
12
  end
15
13
 
16
14
  def translate_required_text
@@ -24,10 +24,6 @@ module SimpleForm
24
24
  find_validator(:length)
25
25
  end
26
26
 
27
- def has_tokenizer?(length_validator)
28
- length_validator.options[:tokenizer]
29
- end
30
-
31
27
  def maximum_length_value_from(length_validator)
32
28
  if length_validator
33
29
  length_validator.options[:is] || length_validator.options[:maximum]
@@ -24,10 +24,6 @@ module SimpleForm
24
24
  find_validator(:length)
25
25
  end
26
26
 
27
- def has_tokenizer?(length_validator)
28
- length_validator.options[:tokenizer]
29
- end
30
-
31
27
  def minimum_length_value_from(length_validator)
32
28
  if length_validator
33
29
  length_validator.options[:is] || length_validator.options[:minimum]
@@ -26,6 +26,7 @@ module SimpleForm
26
26
  map_type :range, to: SimpleForm::Inputs::RangeInput
27
27
  map_type :check_boxes, to: SimpleForm::Inputs::CollectionCheckBoxesInput
28
28
  map_type :radio_buttons, to: SimpleForm::Inputs::CollectionRadioButtonsInput
29
+ map_type :rich_text_area, to: SimpleForm::Inputs::RichTextAreaInput
29
30
  map_type :select, to: SimpleForm::Inputs::CollectionSelectInput
30
31
  map_type :grouped_select, to: SimpleForm::Inputs::GroupedCollectionSelectInput
31
32
  map_type :date, :time, :datetime, to: SimpleForm::Inputs::DateTimeInput
@@ -165,7 +166,7 @@ module SimpleForm
165
166
  components = (wrapper.components.map(&:namespace) & ATTRIBUTE_COMPONENTS)
166
167
 
167
168
  options = options.dup
168
- options[:input_html] = options.except(:as, :boolean_style, :collection, :label_method, :value_method, :prompt, *components)
169
+ options[:input_html] = options.except(:as, :boolean_style, :collection, :disabled, :label_method, :value_method, :prompt, *components)
169
170
  options = @defaults.deep_dup.deep_merge(options) if @defaults
170
171
 
171
172
  input = find_input(attribute_name, options)
@@ -511,7 +512,7 @@ module SimpleForm
511
512
  when :has_one
512
513
  raise ArgumentError, ":has_one associations are not supported by f.association"
513
514
  else
514
- if options[:as] == :select
515
+ if options[:as] == :select || options[:as] == :grouped_select
515
516
  html_options = options[:input_html] ||= {}
516
517
  html_options[:multiple] = true unless html_options.key?(:multiple)
517
518
  end
@@ -552,12 +553,12 @@ module SimpleForm
552
553
  :datetime
553
554
  when :string, :citext, nil
554
555
  case attribute_name.to_s
555
- when /password/ then :password
556
- when /time_zone/ then :time_zone
557
- when /country/ then :country
558
- when /email/ then :email
559
- when /phone/ then :tel
560
- when /url/ then :url
556
+ when /(?:\b|\W|_)password(?:\b|\W|_)/ then :password
557
+ when /(?:\b|\W|_)time_zone(?:\b|\W|_)/ then :time_zone
558
+ when /(?:\b|\W|_)country(?:\b|\W|_)/ then :country
559
+ when /(?:\b|\W|_)email(?:\b|\W|_)/ then :email
560
+ when /(?:\b|\W|_)phone(?:\b|\W|_)/ then :tel
561
+ when /(?:\b|\W|_)url(?:\b|\W|_)/ then :url
561
562
  else
562
563
  file_method?(attribute_name) ? :file : (input_type || :string)
563
564
  end
@@ -572,9 +573,28 @@ module SimpleForm
572
573
  }.try(:last) if SimpleForm.input_mappings
573
574
  end
574
575
 
576
+ # Internal: Try to discover whether an attribute corresponds to a file or not.
577
+ #
578
+ # Most upload Gems add some kind of attributes to the ActiveRecord's model they are included in.
579
+ # This method tries to guess if an attribute belongs to some of these Gems by checking the presence
580
+ # of their methods using `#respond_to?`.
581
+ #
582
+ # Note: This does not support multiple file upload inputs, as this is very application-specific.
583
+ #
584
+ # The order here was chosen based on the popularity of Gems:
585
+ #
586
+ # - `#{attribute_name}_attachment` - ActiveStorage >= `5.2` and Refile >= `0.2.0` <= `0.4.0`
587
+ # - `remote_#{attribute_name}_url` - Refile >= `0.3.0` and CarrierWave >= `0.2.2`
588
+ # - `#{attribute_name}_attacher` - Refile >= `0.4.0` and Shrine >= `0.9.0`
589
+ # - `#{attribute_name}_file_name` - Paperclip ~> `2.0` (added for backwards compatibility)
590
+ #
591
+ # Returns a Boolean.
575
592
  def file_method?(attribute_name)
576
- file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
577
- file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
593
+ @object.respond_to?("#{attribute_name}_attachment") ||
594
+ @object.respond_to?("#{attribute_name}_attachments") ||
595
+ @object.respond_to?("remote_#{attribute_name}_url") ||
596
+ @object.respond_to?("#{attribute_name}_attacher") ||
597
+ @object.respond_to?("#{attribute_name}_file_name")
578
598
  end
579
599
 
580
600
  def find_attribute_column(attribute_name)
@@ -10,6 +10,7 @@ module SimpleForm
10
10
  autoload :CollectionInput
11
11
  autoload :CollectionRadioButtonsInput
12
12
  autoload :CollectionSelectInput
13
+ autoload :ColorInput
13
14
  autoload :DateTimeInput
14
15
  autoload :FileInput
15
16
  autoload :GroupedCollectionSelectInput
@@ -18,6 +19,7 @@ module SimpleForm
18
19
  autoload :PasswordInput
19
20
  autoload :PriorityInput
20
21
  autoload :RangeInput
22
+ autoload :RichTextAreaInput
21
23
  autoload :StringInput
22
24
  autoload :TextInput
23
25
  end
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- require 'simple_form/i18n_cache'
3
2
  require 'active_support/core_ext/string/output_safety'
4
3
  require 'action_view/helpers'
5
4
 
@@ -9,8 +8,6 @@ module SimpleForm
9
8
  include ERB::Util
10
9
  include ActionView::Helpers::TranslationHelper
11
10
 
12
- extend I18nCache
13
-
14
11
  include SimpleForm::Helpers::Autofocus
15
12
  include SimpleForm::Helpers::Disabled
16
13
  include SimpleForm::Helpers::Readonly
@@ -72,7 +69,10 @@ module SimpleForm
72
69
  @html_classes = SimpleForm.additional_classes_for(:input) { additional_classes }
73
70
 
74
71
  @input_html_classes = @html_classes.dup
75
- if SimpleForm.input_class && !input_html_classes.empty?
72
+
73
+ input_html_classes = self.input_html_classes
74
+
75
+ if SimpleForm.input_class && input_html_classes.any?
76
76
  input_html_classes << SimpleForm.input_class
77
77
  end
78
78
 
@@ -63,6 +63,7 @@ module SimpleForm
63
63
  return "" if !include_hidden? || !unchecked_value
64
64
  options = { value: unchecked_value, id: nil, disabled: input_html_options[:disabled] }
65
65
  options[:name] = input_html_options[:name] if input_html_options.key?(:name)
66
+ options[:form] = input_html_options[:form] if input_html_options.key?(:form)
66
67
 
67
68
  @builder.hidden_field(attribute_name, options)
68
69
  end
@@ -5,7 +5,7 @@ module SimpleForm
5
5
  protected
6
6
 
7
7
  # Checkbox components do not use the required html tag.
8
- # More info: https://github.com/plataformatec/simple_form/issues/340#issuecomment-2871956
8
+ # More info: https://github.com/heartcombo/simple_form/issues/340#issuecomment-2871956
9
9
  def has_required?
10
10
  false
11
11
  end
@@ -10,10 +10,8 @@ module SimpleForm
10
10
  # Texts can be translated using i18n in "simple_form.yes" and
11
11
  # "simple_form.no" keys. See the example locale file.
12
12
  def self.boolean_collection
13
- i18n_cache :boolean_collection do
14
- [ [I18n.t(:"simple_form.yes", default: 'Yes'), true],
15
- [I18n.t(:"simple_form.no", default: 'No'), false] ]
16
- end
13
+ [ [I18n.t(:"simple_form.yes", default: 'Yes'), true],
14
+ [I18n.t(:"simple_form.no", default: 'No'), false] ]
17
15
  end
18
16
 
19
17
  def input(wrapper_options = nil)
@@ -41,7 +39,7 @@ module SimpleForm
41
39
  end
42
40
 
43
41
  def has_required?
44
- super && (input_options[:include_blank] || input_options[:prompt] || multiple?)
42
+ super && (input_options[:include_blank] || input_options[:prompt].present? || multiple?)
45
43
  end
46
44
 
47
45
  # Check if :include_blank must be included by default.
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ module SimpleForm
3
+ module Inputs
4
+ class ColorInput < Base
5
+ def input(wrapper_options = nil)
6
+ input_html_options[:type] ||= "color" if html5?
7
+
8
+ merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
9
+
10
+ @builder.text_field(attribute_name, merged_input_options)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -15,10 +15,6 @@ module SimpleForm
15
15
 
16
16
  protected
17
17
 
18
- def has_required?
19
- false
20
- end
21
-
22
18
  def skip_include_blank?
23
19
  super || input_priority.present?
24
20
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ module SimpleForm
3
+ module Inputs
4
+ class RichTextAreaInput < Base
5
+ def input(wrapper_options = nil)
6
+ merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
7
+
8
+ @builder.rich_text_area(attribute_name, merged_input_options)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -18,7 +18,7 @@ module SimpleForm
18
18
  private
19
19
 
20
20
  def string?
21
- input_type == :string
21
+ input_type == :string || input_type == :citext
22
22
  end
23
23
  end
24
24
  end
@@ -48,7 +48,9 @@ module SimpleForm
48
48
  private
49
49
 
50
50
  def render_component(builder)
51
- builder.radio_button + builder.label(class: "collection_radio_buttons")
51
+ label_class = "#{@options[:item_label_class]} collection_radio_buttons".strip
52
+
53
+ builder.radio_button + builder.label(class: label_class)
52
54
  end
53
55
  end
54
56
 
@@ -62,7 +64,9 @@ module SimpleForm
62
64
  private
63
65
 
64
66
  def render_component(builder)
65
- builder.check_box + builder.label(class: "collection_check_boxes")
67
+ label_class = "#{@options[:item_label_class]} collection_check_boxes".strip
68
+
69
+ builder.check_box + builder.label(class: label_class)
66
70
  end
67
71
  end
68
72
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module SimpleForm
3
- VERSION = "4.0.1".freeze
3
+ VERSION = "5.1.0".freeze
4
4
  end
@@ -28,11 +28,16 @@ module SimpleForm
28
28
  css += SimpleForm.additional_classes_for(:wrapper) do
29
29
  input.additional_classes + [input.input_class]
30
30
  end
31
- css << (options[:wrapper_error_class] || @defaults[:error_class]) if input.has_errors?
32
- css << (options[:wrapper_hint_class] || @defaults[:hint_class]) if input.has_hint?
33
- css << (options[:wrapper_valid_class] || @defaults[:valid_class]) if input.valid?
31
+ css << html_class(:error_class, options) { input.has_errors? }
32
+ css << html_class(:hint_class, options) { input.has_hint? }
33
+ css << html_class(:valid_class, options) { input.valid? }
34
34
  css.compact
35
35
  end
36
+
37
+ def html_class(key, options)
38
+ css = (options[:"wrapper_#{key}"] || @defaults[key])
39
+ css if css && yield
40
+ end
36
41
  end
37
42
  end
38
43
  end
@@ -45,8 +45,17 @@ class BuilderTest < ActionView::TestCase
45
45
 
46
46
  test "collection radio sanitizes collection values for labels correctly" do
47
47
  with_collection_radio_buttons @user, :name, ['$0.99', '$1.99'], :to_s, :to_s
48
- assert_select 'label.collection_radio_buttons[for=user_name_099]', '$0.99'
49
- assert_select 'label.collection_radio_buttons[for=user_name_199]', '$1.99'
48
+
49
+ # Rails 6 changed the way it sanitizes the values
50
+ # https://github.com/rails/rails/blob/6-0-stable/actionview/lib/action_view/helpers/tags/base.rb#L141
51
+ # https://github.com/rails/rails/blob/5-2-stable/actionview/lib/action_view/helpers/tags/base.rb#L141
52
+ if ActionView::VERSION::MAJOR == 5
53
+ assert_select 'label.collection_radio_buttons[for=user_name_099]', '$0.99'
54
+ assert_select 'label.collection_radio_buttons[for=user_name_199]', '$1.99'
55
+ else
56
+ assert_select 'label.collection_radio_buttons[for=user_name_0_99]', '$0.99'
57
+ assert_select 'label.collection_radio_buttons[for=user_name_1_99]', '$1.99'
58
+ end
50
59
  end
51
60
 
52
61
  test "collection radio checks the correct value to local variables" do
@@ -292,8 +301,17 @@ class BuilderTest < ActionView::TestCase
292
301
 
293
302
  test "collection check box sanitizes collection values for labels correctly" do
294
303
  with_collection_check_boxes @user, :name, ['$0.99', '$1.99'], :to_s, :to_s
295
- assert_select 'label.collection_check_boxes[for=user_name_099]', '$0.99'
296
- assert_select 'label.collection_check_boxes[for=user_name_199]', '$1.99'
304
+
305
+ # Rails 6 changed the way it sanitizes the values
306
+ # https://github.com/rails/rails/blob/6-0-stable/actionview/lib/action_view/helpers/tags/base.rb#L141
307
+ # https://github.com/rails/rails/blob/5-2-stable/actionview/lib/action_view/helpers/tags/base.rb#L141
308
+ if ActionView::VERSION::MAJOR == 5
309
+ assert_select 'label.collection_check_boxes[for=user_name_099]', '$0.99'
310
+ assert_select 'label.collection_check_boxes[for=user_name_199]', '$1.99'
311
+ else
312
+ assert_select 'label.collection_check_boxes[for=user_name_0_99]', '$0.99'
313
+ assert_select 'label.collection_check_boxes[for=user_name_1_99]', '$1.99'
314
+ end
297
315
  end
298
316
 
299
317
  test "collection check box checks the correct value to local variables" do
@@ -4,10 +4,6 @@ require 'test_helper'
4
4
 
5
5
  # Isolated tests for label without triggering f.label.
6
6
  class IsolatedLabelTest < ActionView::TestCase
7
- setup do
8
- SimpleForm::Inputs::Base.reset_i18n_cache :translate_required_html
9
- end
10
-
11
7
  def with_label_for(object, attribute_name, type, options = {})
12
8
  with_concat_form_for(object) do |f|
13
9
  options[:reflection] = Association.new(Company, :company, {}) if options.delete(:setup_association)
@@ -243,4 +243,10 @@ class AssociationTest < ActionView::TestCase
243
243
  assert_equal({ as: :check_boxes, collection_wrapper_tag: :ul, item_wrapper_tag: :li },
244
244
  options)
245
245
  end
246
+
247
+ test 'builder with group select considers multiple select by default' do
248
+ with_association_for @user, :tags, as: :grouped_select, group_method: :group_method
249
+
250
+ assert_select 'select[multiple="multiple"].grouped_select'
251
+ end
246
252
  end