simple_form 3.4.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 (106) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +118 -8
  3. data/MIT-LICENSE +2 -1
  4. data/README.md +235 -67
  5. data/lib/generators/simple_form/install_generator.rb +1 -0
  6. data/lib/generators/simple_form/templates/README +2 -3
  7. data/lib/generators/simple_form/templates/_form.html.erb +2 -0
  8. data/lib/generators/simple_form/templates/_form.html.haml +2 -0
  9. data/lib/generators/simple_form/templates/_form.html.slim +1 -0
  10. data/lib/generators/simple_form/templates/config/initializers/simple_form.rb +14 -7
  11. data/lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb +360 -74
  12. data/lib/generators/simple_form/templates/config/initializers/simple_form_foundation.rb +20 -8
  13. data/lib/simple_form/action_view_extensions/builder.rb +1 -0
  14. data/lib/simple_form/action_view_extensions/form_helper.rb +1 -0
  15. data/lib/simple_form/components/errors.rb +15 -2
  16. data/lib/simple_form/components/hints.rb +1 -0
  17. data/lib/simple_form/components/html5.rb +1 -0
  18. data/lib/simple_form/components/label_input.rb +2 -1
  19. data/lib/simple_form/components/labels.rb +12 -7
  20. data/lib/simple_form/components/maxlength.rb +4 -17
  21. data/lib/simple_form/components/min_max.rb +1 -0
  22. data/lib/simple_form/components/minlength.rb +5 -18
  23. data/lib/simple_form/components/pattern.rb +1 -0
  24. data/lib/simple_form/components/placeholders.rb +2 -1
  25. data/lib/simple_form/components/readonly.rb +1 -0
  26. data/lib/simple_form/components.rb +1 -0
  27. data/lib/simple_form/error_notification.rb +1 -0
  28. data/lib/simple_form/form_builder.rb +104 -29
  29. data/lib/simple_form/helpers/autofocus.rb +1 -0
  30. data/lib/simple_form/helpers/disabled.rb +1 -0
  31. data/lib/simple_form/helpers/readonly.rb +1 -0
  32. data/lib/simple_form/helpers/required.rb +1 -0
  33. data/lib/simple_form/helpers/validators.rb +2 -1
  34. data/lib/simple_form/helpers.rb +1 -0
  35. data/lib/simple_form/inputs/base.rb +24 -5
  36. data/lib/simple_form/inputs/block_input.rb +1 -0
  37. data/lib/simple_form/inputs/boolean_input.rb +4 -2
  38. data/lib/simple_form/inputs/collection_check_boxes_input.rb +3 -2
  39. data/lib/simple_form/inputs/collection_input.rb +6 -7
  40. data/lib/simple_form/inputs/collection_radio_buttons_input.rb +2 -1
  41. data/lib/simple_form/inputs/collection_select_input.rb +1 -0
  42. data/lib/simple_form/inputs/color_input.rb +14 -0
  43. data/lib/simple_form/inputs/date_time_input.rb +1 -0
  44. data/lib/simple_form/inputs/file_input.rb +1 -0
  45. data/lib/simple_form/inputs/grouped_collection_select_input.rb +1 -0
  46. data/lib/simple_form/inputs/hidden_input.rb +1 -0
  47. data/lib/simple_form/inputs/numeric_input.rb +1 -0
  48. data/lib/simple_form/inputs/password_input.rb +1 -0
  49. data/lib/simple_form/inputs/priority_input.rb +1 -4
  50. data/lib/simple_form/inputs/range_input.rb +1 -0
  51. data/lib/simple_form/inputs/rich_text_area_input.rb +12 -0
  52. data/lib/simple_form/inputs/string_input.rb +2 -1
  53. data/lib/simple_form/inputs/text_input.rb +1 -0
  54. data/lib/simple_form/inputs.rb +3 -0
  55. data/lib/simple_form/map_type.rb +1 -0
  56. data/lib/simple_form/railtie.rb +1 -0
  57. data/lib/simple_form/tags.rb +7 -2
  58. data/lib/simple_form/version.rb +2 -1
  59. data/lib/simple_form/wrappers/builder.rb +1 -0
  60. data/lib/simple_form/wrappers/leaf.rb +2 -1
  61. data/lib/simple_form/wrappers/many.rb +1 -0
  62. data/lib/simple_form/wrappers/root.rb +9 -2
  63. data/lib/simple_form/wrappers/single.rb +2 -1
  64. data/lib/simple_form/wrappers.rb +1 -0
  65. data/lib/simple_form.rb +79 -11
  66. data/test/action_view_extensions/builder_test.rb +28 -9
  67. data/test/action_view_extensions/form_helper_test.rb +3 -2
  68. data/test/components/custom_components_test.rb +62 -0
  69. data/test/components/label_test.rb +33 -8
  70. data/test/form_builder/association_test.rb +33 -2
  71. data/test/form_builder/button_test.rb +1 -0
  72. data/test/form_builder/error_notification_test.rb +1 -0
  73. data/test/form_builder/error_test.rb +12 -0
  74. data/test/form_builder/general_test.rb +75 -13
  75. data/test/form_builder/hint_test.rb +6 -0
  76. data/test/form_builder/input_field_test.rb +30 -10
  77. data/test/form_builder/label_test.rb +10 -4
  78. data/test/form_builder/wrapper_test.rb +32 -5
  79. data/test/generators/simple_form_generator_test.rb +4 -3
  80. data/test/inputs/boolean_input_test.rb +17 -0
  81. data/test/inputs/collection_check_boxes_input_test.rb +38 -18
  82. data/test/inputs/collection_radio_buttons_input_test.rb +48 -28
  83. data/test/inputs/collection_select_input_test.rb +46 -43
  84. data/test/inputs/color_input_test.rb +10 -0
  85. data/test/inputs/datetime_input_test.rb +7 -16
  86. data/test/inputs/disabled_test.rb +14 -0
  87. data/test/inputs/discovery_test.rb +22 -0
  88. data/test/inputs/file_input_test.rb +1 -0
  89. data/test/inputs/general_test.rb +3 -2
  90. data/test/inputs/grouped_collection_select_input_test.rb +11 -10
  91. data/test/inputs/hidden_input_test.rb +1 -0
  92. data/test/inputs/numeric_input_test.rb +2 -1
  93. data/test/inputs/priority_input_test.rb +7 -14
  94. data/test/inputs/readonly_test.rb +1 -0
  95. data/test/inputs/required_test.rb +1 -0
  96. data/test/inputs/rich_text_area_input_test.rb +15 -0
  97. data/test/inputs/string_input_test.rb +10 -16
  98. data/test/inputs/text_input_test.rb +1 -0
  99. data/test/simple_form_test.rb +1 -0
  100. data/test/support/discovery_inputs.rb +8 -0
  101. data/test/support/misc_helpers.rb +22 -1
  102. data/test/support/mock_controller.rb +7 -1
  103. data/test/support/models.rb +80 -18
  104. data/test/test_helper.rb +9 -4
  105. metadata +49 -55
  106. data/lib/simple_form/i18n_cache.rb +0 -22
@@ -1,13 +1,21 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Uncomment this and change the path if necessary to include your own
4
+ # components.
5
+ # See https://github.com/heartcombo/simple_form#custom-components to know
6
+ # more about custom components.
7
+ # Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f }
8
+ #
1
9
  # Use this setup block to configure all options available in SimpleForm.
2
10
  SimpleForm.setup do |config|
3
11
  # Don't forget to edit this file to adapt it to your needs (specially
4
12
  # all the grid-related classes)
5
13
  #
6
14
  # Please note that hints are commented out by default since Foundation
7
- # does't provide styles for hints. You will need to provide your own CSS styles for hints.
15
+ # doesn't provide styles for hints. You will need to provide your own CSS styles for hints.
8
16
  # Uncomment them to enable hints.
9
17
 
10
- config.wrappers :vertical_form, class: :input, hint_class: :field_with_hint, error_class: :error do |b|
18
+ config.wrappers :vertical_form, class: :input, hint_class: :field_with_hint, error_class: :error, valid_class: :valid do |b|
11
19
  b.use :html5
12
20
  b.use :placeholder
13
21
  b.optional :maxlength
@@ -21,7 +29,7 @@ SimpleForm.setup do |config|
21
29
  # b.use :hint, wrap_with: { tag: :span, class: :hint }
22
30
  end
23
31
 
24
- config.wrappers :horizontal_form, tag: 'div', class: 'row', hint_class: :field_with_hint, error_class: :error do |b|
32
+ config.wrappers :horizontal_form, tag: 'div', class: 'row', hint_class: :field_with_hint, error_class: :error, valid_class: :valid do |b|
25
33
  b.use :html5
26
34
  b.use :placeholder
27
35
  b.optional :maxlength
@@ -31,7 +39,7 @@ SimpleForm.setup do |config|
31
39
  b.optional :readonly
32
40
 
33
41
  b.wrapper :label_wrapper, tag: :div, class: 'small-3 columns' do |ba|
34
- ba.use :label, class: 'right inline'
42
+ ba.use :label, class: 'text-right inline'
35
43
  end
36
44
 
37
45
  b.wrapper :right_input_wrapper, tag: :div, class: 'small-9 columns' do |ba|
@@ -46,7 +54,7 @@ SimpleForm.setup do |config|
46
54
  b.optional :readonly
47
55
 
48
56
  b.wrapper :container_wrapper, tag: 'div', class: 'small-offset-3 small-9 columns' do |ba|
49
- ba.wrapper :tag => 'label', :class => 'checkbox' do |bb|
57
+ ba.wrapper tag: 'label', class: 'checkbox' do |bb|
50
58
  bb.use :input
51
59
  bb.use :label_text
52
60
  end
@@ -63,7 +71,7 @@ SimpleForm.setup do |config|
63
71
  # Note that you need to adapt this wrapper to your needs. If you need a 4
64
72
  # columns form then change the wrapper class to 'small-3', if you need
65
73
  # only two use 'small-6' and so on.
66
- config.wrappers :inline_form, tag: 'div', class: 'column small-4', hint_class: :field_with_hint, error_class: :error do |b|
74
+ config.wrappers :inline_form, tag: 'div', class: 'column small-4', hint_class: :field_with_hint, error_class: :error, valid_class: :valid do |b|
67
75
  b.use :html5
68
76
  b.use :placeholder
69
77
  b.optional :maxlength
@@ -82,7 +90,7 @@ SimpleForm.setup do |config|
82
90
  # Examples of use:
83
91
  # - wrapper_html: {class: 'row'}, custom_wrapper_html: {class: 'column small-12'}
84
92
  # - custom_wrapper_html: {class: 'column small-3 end'}
85
- config.wrappers :customizable_wrapper, tag: 'div', error_class: :error do |b|
93
+ config.wrappers :customizable_wrapper, tag: 'div', error_class: :error, valid_class: :valid do |b|
86
94
  b.use :html5
87
95
  b.optional :readonly
88
96
 
@@ -98,7 +106,7 @@ SimpleForm.setup do |config|
98
106
  config.button_class = 'button'
99
107
 
100
108
  # Set this to div to make the checkbox and radio properly work
101
- # otherwise simple_form adds a label tag instead of a div arround
109
+ # otherwise simple_form adds a label tag instead of a div around
102
110
  # the nested label
103
111
  config.item_wrapper_tag = :div
104
112
 
@@ -107,4 +115,8 @@ SimpleForm.setup do |config|
107
115
 
108
116
  # The default wrapper to be used by the FormBuilder.
109
117
  config.default_wrapper = :vertical_form
118
+
119
+ # Defines validation classes to the input_field. By default it's nil.
120
+ # config.input_field_valid_class = 'is-valid'
121
+ # config.input_field_error_class = 'is-invalid'
110
122
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module ActionViewExtensions
3
4
  # A collection of methods required by simple_form but added to rails default form.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module ActionViewExtensions
3
4
  # This module creates SimpleForm wrappers around default form_for and fields_for.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  module Errors
@@ -10,7 +11,15 @@ module SimpleForm
10
11
  end
11
12
 
12
13
  def has_errors?
13
- object && object.respond_to?(:errors) && errors.present?
14
+ object_with_errors? || object.nil? && has_custom_error?
15
+ end
16
+
17
+ def has_value?
18
+ object && object.respond_to?(attribute_name) && object.send(attribute_name).present?
19
+ end
20
+
21
+ def valid?
22
+ !has_errors? && has_value?
14
23
  end
15
24
 
16
25
  protected
@@ -25,6 +34,10 @@ module SimpleForm
25
34
  has_custom_error? ? options[:error] : full_errors.send(error_method)
26
35
  end
27
36
 
37
+ def object_with_errors?
38
+ object && object.respond_to?(:errors) && errors.present?
39
+ end
40
+
28
41
  def error_method
29
42
  options[:error_method] || SimpleForm.error_method
30
43
  end
@@ -38,7 +51,7 @@ module SimpleForm
38
51
  end
39
52
 
40
53
  def errors_on_attribute
41
- object.errors[attribute_name]
54
+ object.errors[attribute_name] || []
42
55
  end
43
56
 
44
57
  def full_errors_on_attribute
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  # Needs to be enabled in order to do automatic lookups.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  module HTML5
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  module LabelInput
@@ -20,7 +21,7 @@ module SimpleForm
20
21
  def deprecated_component(namespace, wrapper_options)
21
22
  method = method(namespace)
22
23
 
23
- if method.arity == 0
24
+ if method.arity.zero?
24
25
  ActiveSupport::Deprecation.warn(SimpleForm::CUSTOM_INPUT_DEPRECATION_WARN % { name: namespace })
25
26
 
26
27
  method.call
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  module Labels
@@ -5,19 +6,23 @@ module SimpleForm
5
6
 
6
7
  module ClassMethods #:nodoc:
7
8
  def translate_required_html
8
- i18n_cache :translate_required_html do
9
- I18n.t(:"simple_form.required.html", default:
10
- %[<abbr title="#{translate_required_text}">#{translate_required_mark}</abbr>]
11
- )
12
- end
9
+ I18n.t(:"required.html", scope: i18n_scope, default:
10
+ %(<abbr title="#{translate_required_text}">#{translate_required_mark}</abbr>)
11
+ )
13
12
  end
14
13
 
15
14
  def translate_required_text
16
- I18n.t(:"simple_form.required.text", default: 'required')
15
+ I18n.t(:"required.text", scope: i18n_scope, default: 'required')
17
16
  end
18
17
 
19
18
  def translate_required_mark
20
- I18n.t(:"simple_form.required.mark", default: '*')
19
+ I18n.t(:"required.mark", scope: i18n_scope, default: '*')
20
+ end
21
+
22
+ private
23
+
24
+ def i18n_scope
25
+ SimpleForm.i18n_scope
21
26
  end
22
27
  end
23
28
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  # Needs to be enabled in order to do automatic lookups.
@@ -23,23 +24,9 @@ module SimpleForm
23
24
  find_validator(:length)
24
25
  end
25
26
 
26
- def has_tokenizer?(length_validator)
27
- length_validator.options[:tokenizer]
28
- end
29
-
30
- # Use validation with tokenizer if version of Rails is less than 5,
31
- # if not validate without the tokenizer, if version is greater than Rails 4.
32
- if ActionPack::VERSION::STRING < '5'
33
- def maximum_length_value_from(length_validator)
34
- if length_validator && !has_tokenizer?(length_validator)
35
- length_validator.options[:is] || length_validator.options[:maximum]
36
- end
37
- end
38
- elsif ActionPack::VERSION::STRING >= '5'
39
- def maximum_length_value_from(length_validator)
40
- if length_validator
41
- length_validator.options[:is] || length_validator.options[:maximum]
42
- end
27
+ def maximum_length_value_from(length_validator)
28
+ if length_validator
29
+ length_validator.options[:is] || length_validator.options[:maximum]
43
30
  end
44
31
  end
45
32
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  module MinMax
@@ -1,9 +1,10 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  # Needs to be enabled in order to do automatic lookups.
4
5
  module Minlength
5
6
  def minlength(wrapper_options = nil)
6
- input_html_options[:minlength] ||= minimum_length_from_validation || limit
7
+ input_html_options[:minlength] ||= minimum_length_from_validation
7
8
  nil
8
9
  end
9
10
 
@@ -23,23 +24,9 @@ module SimpleForm
23
24
  find_validator(:length)
24
25
  end
25
26
 
26
- def has_tokenizer?(length_validator)
27
- length_validator.options[:tokenizer]
28
- end
29
-
30
- # Use validation with tokenizer if version of Rails is less than 5,
31
- # if not validate without the tokenizer, if version is greater than Rails 4.
32
- if ActionPack::VERSION::STRING < '5'
33
- def minimum_length_value_from(length_validator)
34
- if length_validator && !has_tokenizer?(length_validator)
35
- length_validator.options[:is] || length_validator.options[:minimum]
36
- end
37
- end
38
- elsif ActionPack::VERSION::STRING >= '5'
39
- def minimum_length_value_from(length_validator)
40
- if length_validator
41
- length_validator.options[:is] || length_validator.options[:minimum]
42
- end
27
+ def minimum_length_value_from(length_validator)
28
+ if length_validator
29
+ length_validator.options[:is] || length_validator.options[:minimum]
43
30
  end
44
31
  end
45
32
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  # Needs to be enabled in order to do automatic lookups.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  # Needs to be enabled in order to do automatic lookups.
@@ -7,7 +8,7 @@ module SimpleForm
7
8
  nil
8
9
  end
9
10
 
10
- def placeholder_text
11
+ def placeholder_text(wrapper_options = nil)
11
12
  placeholder = options[:placeholder]
12
13
  placeholder.is_a?(String) ? placeholder : translate_from_namespace(:placeholders)
13
14
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  # Needs to be enabled in order to do automatic lookups.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  # Components are a special type of helpers that can work on their own.
3
4
  # For example, by using a component, it will automatically change the
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  class ErrorNotification
3
4
  delegate :object, :object_name, :template, to: :@builder
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'active_support/core_ext/object/deep_dup'
2
3
  require 'simple_form/map_type'
3
4
  require 'simple_form/tags'
@@ -12,25 +13,26 @@ module SimpleForm
12
13
  'update' => 'edit'
13
14
  }
14
15
 
15
- ATTRIBUTE_COMPONENTS = [:html5, :min_max, :maxlength, :minlength, :placeholder, :pattern, :readonly]
16
+ ATTRIBUTE_COMPONENTS = %i[html5 min_max maxlength minlength placeholder pattern readonly]
16
17
 
17
18
  extend MapType
18
19
  include SimpleForm::Inputs
19
20
 
20
- map_type :text, to: SimpleForm::Inputs::TextInput
21
- map_type :file, to: SimpleForm::Inputs::FileInput
22
- map_type :string, :email, :search, :tel, :url, :uuid, to: SimpleForm::Inputs::StringInput
23
- map_type :password, to: SimpleForm::Inputs::PasswordInput
24
- map_type :integer, :decimal, :float, to: SimpleForm::Inputs::NumericInput
25
- map_type :range, to: SimpleForm::Inputs::RangeInput
26
- map_type :check_boxes, to: SimpleForm::Inputs::CollectionCheckBoxesInput
27
- map_type :radio_buttons, to: SimpleForm::Inputs::CollectionRadioButtonsInput
28
- map_type :select, to: SimpleForm::Inputs::CollectionSelectInput
29
- map_type :grouped_select, to: SimpleForm::Inputs::GroupedCollectionSelectInput
30
- map_type :date, :time, :datetime, to: SimpleForm::Inputs::DateTimeInput
31
- map_type :country, :time_zone, to: SimpleForm::Inputs::PriorityInput
32
- map_type :boolean, to: SimpleForm::Inputs::BooleanInput
33
- map_type :hidden, to: SimpleForm::Inputs::HiddenInput
21
+ map_type :text, :hstore, :json, :jsonb, to: SimpleForm::Inputs::TextInput
22
+ map_type :file, to: SimpleForm::Inputs::FileInput
23
+ map_type :string, :email, :search, :tel, :url, :uuid, :citext, to: SimpleForm::Inputs::StringInput
24
+ map_type :password, to: SimpleForm::Inputs::PasswordInput
25
+ map_type :integer, :decimal, :float, to: SimpleForm::Inputs::NumericInput
26
+ map_type :range, to: SimpleForm::Inputs::RangeInput
27
+ map_type :check_boxes, to: SimpleForm::Inputs::CollectionCheckBoxesInput
28
+ map_type :radio_buttons, to: SimpleForm::Inputs::CollectionRadioButtonsInput
29
+ map_type :rich_text_area, to: SimpleForm::Inputs::RichTextAreaInput
30
+ map_type :select, to: SimpleForm::Inputs::CollectionSelectInput
31
+ map_type :grouped_select, to: SimpleForm::Inputs::GroupedCollectionSelectInput
32
+ map_type :date, :time, :datetime, to: SimpleForm::Inputs::DateTimeInput
33
+ map_type :country, :time_zone, to: SimpleForm::Inputs::PriorityInput
34
+ map_type :boolean, to: SimpleForm::Inputs::BooleanInput
35
+ map_type :hidden, to: SimpleForm::Inputs::HiddenInput
34
36
 
35
37
  def self.discovery_cache
36
38
  @discovery_cache ||= {}
@@ -38,6 +40,7 @@ module SimpleForm
38
40
 
39
41
  def initialize(*) #:nodoc:
40
42
  super
43
+ @object = convert_to_model(@object)
41
44
  @defaults = options[:defaults]
42
45
  @wrapper = SimpleForm.wrapper(options[:wrapper] || SimpleForm.default_wrapper)
43
46
  end
@@ -136,16 +139,39 @@ module SimpleForm
136
139
  # <input class="string required" id="user_name" maxlength="100"
137
140
  # name="user[name]" type="text" value="Carlos" />
138
141
  #
142
+ # It also support validation classes once it is configured.
143
+ #
144
+ # # config/initializers/simple_form.rb
145
+ # SimpleForm.setup do |config|
146
+ # config.input_field_valid_class = 'is-valid'
147
+ # config.input_field_error_class = 'is-invalid'
148
+ # end
149
+ #
150
+ # simple_form_for @user do |f|
151
+ # f.input_field :name
152
+ # end
153
+ #
154
+ # When the validation happens, the input will be rendered with
155
+ # the class configured according to the validation:
156
+ #
157
+ # - when the input is valid:
158
+ #
159
+ # <input class="is-valid string required" id="user_name" value="Carlos" />
160
+ #
161
+ # - when the input is invalid:
162
+ #
163
+ # <input class="is-invalid string required" id="user_name" value="" />
164
+ #
139
165
  def input_field(attribute_name, options = {})
140
166
  components = (wrapper.components.map(&:namespace) & ATTRIBUTE_COMPONENTS)
141
167
 
142
168
  options = options.dup
143
- 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)
144
170
  options = @defaults.deep_dup.deep_merge(options) if @defaults
145
171
 
146
172
  input = find_input(attribute_name, options)
147
173
  wrapper = find_wrapper(input.input_type, options)
148
- components = components.concat([:input]).map { |component| SimpleForm::Wrappers::Leaf.new(component) }
174
+ components = build_input_field_components(components.push(:input))
149
175
 
150
176
  SimpleForm::Wrappers::Root.new(components, wrapper.options.merge(wrapper: false)).render input
151
177
  end
@@ -461,13 +487,17 @@ module SimpleForm
461
487
  relation = reflection.klass.all
462
488
 
463
489
  if reflection.respond_to?(:scope) && reflection.scope
464
- relation = reflection.klass.instance_exec(&reflection.scope)
490
+ if reflection.scope.parameters.any?
491
+ relation = reflection.klass.instance_exec(object, &reflection.scope)
492
+ else
493
+ relation = reflection.klass.instance_exec(&reflection.scope)
494
+ end
465
495
  else
466
496
  order = reflection.options[:order]
467
497
  conditions = reflection.options[:conditions]
468
498
  conditions = object.instance_exec(&conditions) if conditions.respond_to?(:call)
469
499
 
470
- relation = relation.where(conditions)
500
+ relation = relation.where(conditions) if relation.respond_to?(:where)
471
501
  relation = relation.order(order) if relation.respond_to?(:order)
472
502
  end
473
503
 
@@ -482,7 +512,7 @@ module SimpleForm
482
512
  when :has_one
483
513
  raise ArgumentError, ":has_one associations are not supported by f.association"
484
514
  else
485
- if options[:as] == :select
515
+ if options[:as] == :select || options[:as] == :grouped_select
486
516
  html_options = options[:input_html] ||= {}
487
517
  html_options[:multiple] = true unless html_options.key?(:multiple)
488
518
  end
@@ -521,14 +551,14 @@ module SimpleForm
521
551
  case input_type
522
552
  when :timestamp
523
553
  :datetime
524
- when :string, nil
554
+ when :string, :citext, nil
525
555
  case attribute_name.to_s
526
- when /password/ then :password
527
- when /time_zone/ then :time_zone
528
- when /country/ then :country
529
- when /email/ then :email
530
- when /phone/ then :tel
531
- 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
532
562
  else
533
563
  file_method?(attribute_name) ? :file : (input_type || :string)
534
564
  end
@@ -543,9 +573,28 @@ module SimpleForm
543
573
  }.try(:last) if SimpleForm.input_mappings
544
574
  end
545
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.
546
592
  def file_method?(attribute_name)
547
- file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
548
- 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")
549
598
  end
550
599
 
551
600
  def find_attribute_column(attribute_name)
@@ -640,5 +689,31 @@ module SimpleForm
640
689
 
641
690
  nil
642
691
  end
692
+
693
+ def build_input_field_components(components)
694
+ components.map do |component|
695
+ if component == :input
696
+ SimpleForm::Wrappers::Leaf.new(component, build_input_field_options)
697
+ else
698
+ SimpleForm::Wrappers::Leaf.new(component)
699
+ end
700
+ end
701
+ end
702
+
703
+ def build_input_field_options
704
+ input_field_options = {}
705
+ valid_class = SimpleForm.input_field_valid_class
706
+ error_class = SimpleForm.input_field_error_class
707
+
708
+ if error_class.present?
709
+ input_field_options[:error_class] = error_class
710
+ end
711
+
712
+ if valid_class.present?
713
+ input_field_options[:valid_class] = valid_class
714
+ end
715
+
716
+ input_field_options
717
+ end
643
718
  end
644
719
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Helpers
3
4
  module Autofocus
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Helpers
3
4
  module Disabled
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Helpers
3
4
  module Readonly
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Helpers
3
4
  module Required
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Helpers
3
4
  module Validators
@@ -24,7 +25,7 @@ module SimpleForm
24
25
  end
25
26
 
26
27
  def action_validator_match?(validator)
27
- return true if !validator.options.include?(:on)
28
+ return true unless validator.options.include?(:on)
28
29
 
29
30
  case validator.options[:on]
30
31
  when :save
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  # Helpers are made of several helpers that cannot be turned on automatically.
3
4
  # For instance, disabled cannot be turned on automatically, it requires the
@@ -1,4 +1,4 @@
1
- require 'simple_form/i18n_cache'
1
+ # frozen_string_literal: true
2
2
  require 'active_support/core_ext/string/output_safety'
3
3
  require 'action_view/helpers'
4
4
 
@@ -8,8 +8,6 @@ module SimpleForm
8
8
  include ERB::Util
9
9
  include ActionView::Helpers::TranslationHelper
10
10
 
11
- extend I18nCache
12
-
13
11
  include SimpleForm::Helpers::Autofocus
14
12
  include SimpleForm::Helpers::Disabled
15
13
  include SimpleForm::Helpers::Readonly
@@ -71,7 +69,10 @@ module SimpleForm
71
69
  @html_classes = SimpleForm.additional_classes_for(:input) { additional_classes }
72
70
 
73
71
  @input_html_classes = @html_classes.dup
74
- 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?
75
76
  input_html_classes << SimpleForm.input_class
76
77
  end
77
78
 
@@ -95,7 +96,7 @@ module SimpleForm
95
96
  end
96
97
 
97
98
  def input_class
98
- "#{lookup_model_names.join("_")}_#{reflection_or_attribute_name}"
99
+ "#{lookup_model_names.join('_')}_#{reflection_or_attribute_name}"
99
100
  end
100
101
 
101
102
  private
@@ -190,6 +191,8 @@ module SimpleForm
190
191
 
191
192
  def merge_wrapper_options(options, wrapper_options)
192
193
  if wrapper_options
194
+ wrapper_options = set_input_classes(wrapper_options)
195
+
193
196
  wrapper_options.merge(options) do |key, oldval, newval|
194
197
  case key.to_s
195
198
  when "class"
@@ -205,6 +208,22 @@ module SimpleForm
205
208
  end
206
209
  end
207
210
 
211
+ def set_input_classes(wrapper_options)
212
+ wrapper_options = wrapper_options.dup
213
+ error_class = wrapper_options.delete(:error_class)
214
+ valid_class = wrapper_options.delete(:valid_class)
215
+
216
+ if error_class.present? && has_errors?
217
+ wrapper_options[:class] = "#{wrapper_options[:class]} #{error_class}"
218
+ end
219
+
220
+ if valid_class.present? && valid?
221
+ wrapper_options[:class] = "#{wrapper_options[:class]} #{valid_class}"
222
+ end
223
+
224
+ wrapper_options
225
+ end
226
+
208
227
  def i18n_scope
209
228
  SimpleForm.i18n_scope
210
229
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Inputs
3
4
  class BlockInput < Base
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Inputs
3
4
  class BooleanInput < Base
@@ -59,9 +60,10 @@ module SimpleForm
59
60
  # we need the hidden field to be *outside* the label (otherwise it
60
61
  # generates invalid html - html5 only).
61
62
  def build_hidden_field_for_checkbox
62
- return "" unless include_hidden?
63
+ return "" if !include_hidden? || !unchecked_value
63
64
  options = { value: unchecked_value, id: nil, disabled: input_html_options[:disabled] }
64
- options[:name] = input_html_options[:name] if input_html_options.has_key?(:name)
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)
65
67
 
66
68
  @builder.hidden_field(attribute_name, options)
67
69
  end