simple_form 4.1.0 → 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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -9
  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 -4
  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/labels.rb +3 -5
  11. data/lib/simple_form/components/maxlength.rb +0 -4
  12. data/lib/simple_form/components/minlength.rb +0 -4
  13. data/lib/simple_form/form_builder.rb +23 -3
  14. data/lib/simple_form/inputs.rb +1 -0
  15. data/lib/simple_form/inputs/base.rb +0 -3
  16. data/lib/simple_form/inputs/collection_check_boxes_input.rb +1 -1
  17. data/lib/simple_form/inputs/collection_input.rb +3 -5
  18. data/lib/simple_form/inputs/priority_input.rb +0 -4
  19. data/lib/simple_form/inputs/rich_text_area_input.rb +12 -0
  20. data/lib/simple_form/tags.rb +6 -2
  21. data/lib/simple_form/version.rb +1 -1
  22. data/lib/simple_form/wrappers/root.rb +8 -3
  23. data/test/action_view_extensions/builder_test.rb +22 -4
  24. data/test/components/label_test.rb +0 -4
  25. data/test/form_builder/association_test.rb +6 -0
  26. data/test/form_builder/general_test.rb +17 -19
  27. data/test/form_builder/label_test.rb +1 -1
  28. data/test/form_builder/wrapper_test.rb +8 -1
  29. data/test/inputs/collection_check_boxes_input_test.rb +8 -4
  30. data/test/inputs/collection_radio_buttons_input_test.rb +8 -4
  31. data/test/inputs/collection_select_input_test.rb +6 -4
  32. data/test/inputs/priority_input_test.rb +4 -4
  33. data/test/inputs/rich_text_area_input_test.rb +15 -0
  34. data/test/support/misc_helpers.rb +2 -2
  35. data/test/support/models.rb +28 -2
  36. data/test/test_helper.rb +7 -4
  37. metadata +16 -15
  38. 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
@@ -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
@@ -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
@@ -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)
@@ -19,6 +19,7 @@ module SimpleForm
19
19
  autoload :PasswordInput
20
20
  autoload :PriorityInput
21
21
  autoload :RangeInput
22
+ autoload :RichTextAreaInput
22
23
  autoload :StringInput
23
24
  autoload :TextInput
24
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
@@ -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.
@@ -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
@@ -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.1.0".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
@@ -239,31 +239,29 @@ class FormBuilderTest < ActionView::TestCase
239
239
  assert_select 'form select#user_updated_at_1i.datetime'
240
240
  end
241
241
 
242
- test 'builder generates file for file columns' do
243
- @user.avatar = MiniTest::Mock.new
244
- @user.avatar.expect(:public_filename, true)
245
- @user.avatar.expect(:!, false)
246
-
247
- with_form_for @user, :avatar
248
- assert_select 'form input#user_avatar.file'
242
+ test 'builder generates file input for ActiveStorage >= 5.2 and Refile >= 0.2.0 <= 0.4.0' do
243
+ with_form_for UserWithAttachment.build, :avatar
244
+ assert_select 'form input#user_with_attachment_avatar.file'
249
245
  end
250
246
 
251
- test 'builder generates file for activestorage entries' do
252
- @user.avatar = MiniTest::Mock.new
253
- @user.avatar.expect(:attached?, false)
254
- @user.avatar.expect(:!, false)
247
+ test 'builder generates file input for ActiveStorage::Attached::Many' do
248
+ with_form_for UserWithAttachment.build, :avatars
249
+ assert_select 'form input#user_with_attachment_avatars.file'
250
+ end
255
251
 
256
- with_form_for @user, :avatar
257
- assert_select 'form input#user_avatar.file'
252
+ test 'builder generates file input for Refile >= 0.3.0 and CarrierWave >= 0.2.2' do
253
+ with_form_for UserWithAttachment.build, :cover
254
+ assert_select 'form input#user_with_attachment_cover.file'
258
255
  end
259
256
 
260
- test 'builder generates file for attributes that are real db columns but have file methods' do
261
- @user.home_picture = MiniTest::Mock.new
262
- @user.home_picture.expect(:mounted_as, true)
263
- @user.home_picture.expect(:!, false)
257
+ test 'builder generates file input for Refile >= 0.4.0 and Shrine >= 0.9.0' do
258
+ with_form_for UserWithAttachment.build, :profile_image
259
+ assert_select 'form input#user_with_attachment_profile_image.file'
260
+ end
264
261
 
265
- with_form_for @user, :home_picture
266
- assert_select 'form input#user_home_picture.file'
262
+ test 'builder generates file input for Paperclip ~> 2.0' do
263
+ with_form_for UserWithAttachment.build, :portrait
264
+ assert_select 'form input#user_with_attachment_portrait.file'
267
265
  end
268
266
 
269
267
  test 'build generates select if a collection is given' do
@@ -19,7 +19,7 @@ class LabelTest < ActionView::TestCase
19
19
  assert_select 'label.string[for=user_name]', /Name/
20
20
  end
21
21
 
22
- test 'builder generates a label for the boolean attrbiute' do
22
+ test 'builder generates a label for the boolean attribute' do
23
23
  with_label_for @user, :name, as: :boolean
24
24
  assert_select 'label.boolean[for=user_name]', /Name/
25
25
  assert_no_select 'label[as=boolean]'
@@ -59,6 +59,13 @@ class WrapperTest < ActionView::TestCase
59
59
  assert_no_select 'input.is-invalid'
60
60
  end
61
61
 
62
+ test 'wrapper does not determine if valid class is needed when it is set to nil' do
63
+ @user.instance_eval { undef errors }
64
+ with_form_for @user, :name, wrapper: custom_wrapper_with_input_valid_class(valid_class: nil)
65
+
66
+ assert_no_select 'div.field_without_errors'
67
+ end
68
+
62
69
  test 'wrapper adds hint class for attribute with a hint' do
63
70
  with_form_for @user, :name, hint: 'hint'
64
71
  assert_select 'div.field_with_hint'
@@ -161,7 +168,7 @@ class WrapperTest < ActionView::TestCase
161
168
  test 'custom wrappers can have full error message on attributes' do
162
169
  swap_wrapper :default, custom_wrapper_with_full_error do
163
170
  with_form_for @user, :name
164
- assert_select 'span.error', "Name cannot be blank"
171
+ assert_select 'span.error', "Super User Name! cannot be blank"
165
172
  end
166
173
  end
167
174
 
@@ -3,10 +3,6 @@
3
3
  require 'test_helper'
4
4
 
5
5
  class CollectionCheckBoxesInputTest < ActionView::TestCase
6
- setup do
7
- SimpleForm::Inputs::CollectionCheckBoxesInput.reset_i18n_cache :boolean_collection
8
- end
9
-
10
6
  test 'input check boxes does not include for attribute by default' do
11
7
  with_input_for @user, :gender, :check_boxes, collection: %i[male female]
12
8
  assert_select 'label'
@@ -316,4 +312,12 @@ class CollectionCheckBoxesInputTest < ActionView::TestCase
316
312
  assert_select 'span.checkbox > label', '200'
317
313
  end
318
314
  end
315
+
316
+ test 'input check boxes with inline style support label custom classes' do
317
+ swap SimpleForm, boolean_style: :inline do
318
+ with_input_for @user, :gender, :check_boxes, collection: %i[male female], item_label_class: 'beautiful-label'
319
+
320
+ assert_select 'label.beautiful-label', count: 2
321
+ end
322
+ end
319
323
  end