simple_form 1.5.2 → 2.0.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 (108) hide show
  1. data/CHANGELOG.md +234 -0
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +816 -0
  4. data/lib/generators/simple_form/install_generator.rb +15 -1
  5. data/lib/generators/simple_form/templates/README +12 -0
  6. data/lib/generators/simple_form/templates/_form.html.erb +2 -2
  7. data/lib/generators/simple_form/templates/_form.html.haml +2 -2
  8. data/lib/generators/simple_form/templates/_form.html.slim +4 -4
  9. data/lib/generators/simple_form/templates/config/initializers/simple_form.rb.tt +176 -0
  10. data/lib/simple_form/action_view_extensions/builder.rb +206 -59
  11. data/lib/simple_form/action_view_extensions/form_helper.rb +30 -23
  12. data/lib/simple_form/components/errors.rb +6 -24
  13. data/lib/simple_form/components/hints.rb +7 -21
  14. data/lib/simple_form/components/html5.rb +26 -0
  15. data/lib/simple_form/components/labels.rb +22 -14
  16. data/lib/simple_form/components/maxlength.rb +41 -0
  17. data/lib/simple_form/components/min_max.rb +49 -0
  18. data/lib/simple_form/components/pattern.rb +34 -0
  19. data/lib/simple_form/components/placeholders.rb +5 -17
  20. data/lib/simple_form/components/readonly.rb +22 -0
  21. data/lib/simple_form/components.rb +11 -1
  22. data/lib/simple_form/core_ext/hash.rb +16 -0
  23. data/lib/simple_form/error_notification.rb +9 -3
  24. data/lib/simple_form/form_builder.rb +105 -28
  25. data/lib/simple_form/helpers/autofocus.rb +11 -0
  26. data/lib/simple_form/helpers/disabled.rb +15 -0
  27. data/lib/simple_form/helpers/readonly.rb +15 -0
  28. data/lib/simple_form/helpers/required.rb +10 -11
  29. data/lib/simple_form/helpers/validators.rb +4 -4
  30. data/lib/simple_form/helpers.rb +7 -4
  31. data/lib/simple_form/inputs/base.rb +53 -81
  32. data/lib/simple_form/inputs/boolean_input.rb +46 -4
  33. data/lib/simple_form/inputs/collection_check_boxes_input.rb +21 -0
  34. data/lib/simple_form/inputs/collection_input.rb +27 -13
  35. data/lib/simple_form/inputs/collection_radio_buttons_input.rb +67 -0
  36. data/lib/simple_form/inputs/collection_select_input.rb +14 -0
  37. data/lib/simple_form/inputs/date_time_input.rb +10 -6
  38. data/lib/simple_form/inputs/grouped_collection_select_input.rb +41 -0
  39. data/lib/simple_form/inputs/hidden_input.rb +3 -6
  40. data/lib/simple_form/inputs/numeric_input.rb +3 -51
  41. data/lib/simple_form/inputs/password_input.rb +1 -2
  42. data/lib/simple_form/inputs/priority_input.rb +2 -2
  43. data/lib/simple_form/inputs/range_input.rb +1 -3
  44. data/lib/simple_form/inputs/string_input.rb +6 -8
  45. data/lib/simple_form/inputs/text_input.rb +1 -2
  46. data/lib/simple_form/inputs.rb +17 -13
  47. data/lib/simple_form/version.rb +1 -1
  48. data/lib/simple_form/wrappers/builder.rb +103 -0
  49. data/lib/simple_form/wrappers/many.rb +69 -0
  50. data/lib/simple_form/wrappers/root.rb +34 -0
  51. data/lib/simple_form/wrappers/single.rb +18 -0
  52. data/lib/simple_form/wrappers.rb +8 -0
  53. data/lib/simple_form.rb +118 -48
  54. data/test/action_view_extensions/builder_test.rb +285 -102
  55. data/test/action_view_extensions/form_helper_test.rb +32 -10
  56. data/test/components/label_test.rb +44 -5
  57. data/test/form_builder/association_test.rb +177 -0
  58. data/test/form_builder/button_test.rb +47 -0
  59. data/test/{error_notification_test.rb → form_builder/error_notification_test.rb} +18 -1
  60. data/test/form_builder/error_test.rb +121 -0
  61. data/test/form_builder/general_test.rb +356 -0
  62. data/test/form_builder/hint_test.rb +123 -0
  63. data/test/form_builder/input_field_test.rb +63 -0
  64. data/test/form_builder/label_test.rb +65 -0
  65. data/test/form_builder/wrapper_test.rb +149 -0
  66. data/test/generators/simple_form_generator_test.rb +32 -0
  67. data/test/inputs/boolean_input_test.rb +101 -0
  68. data/test/inputs/collection_check_boxes_input_test.rb +224 -0
  69. data/test/inputs/collection_radio_buttons_input_test.rb +326 -0
  70. data/test/inputs/collection_select_input_test.rb +241 -0
  71. data/test/inputs/datetime_input_test.rb +99 -0
  72. data/test/inputs/disabled_test.rb +38 -0
  73. data/test/inputs/discovery_test.rb +61 -0
  74. data/test/inputs/file_input_test.rb +16 -0
  75. data/test/inputs/general_test.rb +69 -0
  76. data/test/inputs/grouped_collection_select_input_test.rb +118 -0
  77. data/test/inputs/hidden_input_test.rb +30 -0
  78. data/test/inputs/numeric_input_test.rb +167 -0
  79. data/test/inputs/priority_input_test.rb +43 -0
  80. data/test/inputs/readonly_test.rb +61 -0
  81. data/test/inputs/required_test.rb +113 -0
  82. data/test/inputs/string_input_test.rb +140 -0
  83. data/test/inputs/text_input_test.rb +24 -0
  84. data/test/support/misc_helpers.rb +53 -12
  85. data/test/support/mock_controller.rb +2 -2
  86. data/test/support/models.rb +20 -5
  87. data/test/test_helper.rb +11 -12
  88. metadata +124 -96
  89. data/.gitignore +0 -3
  90. data/.gitmodules +0 -3
  91. data/.travis.yml +0 -15
  92. data/CHANGELOG.rdoc +0 -159
  93. data/Gemfile +0 -9
  94. data/README.rdoc +0 -466
  95. data/Rakefile +0 -27
  96. data/lib/generators/simple_form/templates/config/initializers/simple_form.rb +0 -93
  97. data/lib/simple_form/components/wrapper.rb +0 -38
  98. data/lib/simple_form/helpers/has_errors.rb +0 -15
  99. data/lib/simple_form/helpers/maxlength.rb +0 -24
  100. data/lib/simple_form/helpers/pattern.rb +0 -28
  101. data/simple_form.gemspec +0 -25
  102. data/test/components/error_test.rb +0 -56
  103. data/test/components/hint_test.rb +0 -74
  104. data/test/components/wrapper_test.rb +0 -63
  105. data/test/custom_components.rb +0 -7
  106. data/test/form_builder_test.rb +0 -930
  107. data/test/inputs_test.rb +0 -995
  108. /data/test/{discovery_inputs.rb → support/discovery_inputs.rb} +0 -0
@@ -1,6 +1,6 @@
1
1
  module SimpleForm
2
2
  module ActionViewExtensions
3
- # This module creates simple form wrappers around default form_for and fields_for.
3
+ # This module creates SimpleForm wrappers around default form_for and fields_for.
4
4
  #
5
5
  # Example:
6
6
  #
@@ -21,34 +21,16 @@ module SimpleForm
21
21
  html_tag
22
22
  end
23
23
 
24
- def with_custom_field_error_proc(&block)
25
- @@default_field_error_proc = ::ActionView::Base.field_error_proc
26
- ::ActionView::Base.field_error_proc = FIELD_ERROR_PROC
27
- result = yield
28
- ::ActionView::Base.field_error_proc = @@default_field_error_proc
29
- result
30
- end
31
-
32
24
  def simple_form_for(record, options={}, &block)
33
25
  options[:builder] ||= SimpleForm::FormBuilder
34
26
  options[:html] ||= {}
35
27
  unless options[:html].key?(:novalidate)
36
28
  options[:html][:novalidate] = !SimpleForm.browser_validations
37
29
  end
38
- options[:html][:class] = [SimpleForm.form_class, css_class(record, options[:html])].compact.join(" ")
39
- with_custom_field_error_proc do
40
- form_for(record, options, &block)
41
- end
42
- end
30
+ options[:html][:class] = [SimpleForm.form_class, simple_form_css_class(record, options)].compact.join(" ")
43
31
 
44
- def css_class(record, html_options)
45
- if html_options.key?(:class)
46
- html_options[:class]
47
- elsif record.is_a?(String) || record.is_a?(Symbol)
48
- record
49
- else
50
- record = record.last if record.is_a?(Array)
51
- dom_class(record)
32
+ with_simple_form_field_error_proc do
33
+ form_for(record, options, &block)
52
34
  end
53
35
  end
54
36
 
@@ -56,10 +38,35 @@ module SimpleForm
56
38
  options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
57
39
  options[:builder] ||= SimpleForm::FormBuilder
58
40
 
59
- with_custom_field_error_proc do
41
+ with_simple_form_field_error_proc do
60
42
  fields_for(record_name, record_object, options, &block)
61
43
  end
62
44
  end
45
+
46
+ private
47
+
48
+ def with_simple_form_field_error_proc
49
+ @@default_field_error_proc = ::ActionView::Base.field_error_proc
50
+ ::ActionView::Base.field_error_proc = FIELD_ERROR_PROC
51
+ result = yield
52
+ ::ActionView::Base.field_error_proc = @@default_field_error_proc
53
+ result
54
+ end
55
+
56
+ def simple_form_css_class(record, options)
57
+ html_options = options[:html]
58
+ as = options[:as]
59
+
60
+ if html_options.key?(:class)
61
+ html_options[:class]
62
+ elsif record.is_a?(String) || record.is_a?(Symbol)
63
+ as || record
64
+ else
65
+ record = record.last if record.is_a?(Array)
66
+ action = record.respond_to?(:persisted?) && record.persisted? ? :edit : :new
67
+ as ? "#{action}_#{as}" : dom_class(record, action)
68
+ end
69
+ end
63
70
  end
64
71
  end
65
72
  end
@@ -1,42 +1,24 @@
1
1
  module SimpleForm
2
2
  module Components
3
3
  module Errors
4
- include SimpleForm::Helpers::HasErrors
5
-
6
4
  def error
7
- enabled_error
5
+ error_text if has_errors?
8
6
  end
9
7
 
10
- def error_tag
11
- options[:error_tag] || SimpleForm.error_tag
8
+ def has_errors?
9
+ object && object.respond_to?(:errors) && errors.present?
12
10
  end
13
11
 
12
+ protected
13
+
14
14
  def error_text
15
- if options[:error_prefix]
16
- options[:error_prefix] + " " + errors.send(error_method)
17
- else
18
- errors.send(error_method)
19
- end
15
+ "#{options[:error_prefix]} #{errors.send(error_method)}".lstrip.html_safe
20
16
  end
21
17
 
22
18
  def error_method
23
19
  options[:error_method] || SimpleForm.error_method
24
20
  end
25
21
 
26
- def error_html_options
27
- html_options_for(:error, [SimpleForm.error_class])
28
- end
29
-
30
- protected
31
-
32
- def enabled_error
33
- template.content_tag(error_tag, error_text, error_html_options) if has_errors?
34
- end
35
-
36
- def disabled_error
37
- nil
38
- end
39
-
40
22
  def errors
41
23
  @errors ||= (errors_on_attribute + errors_on_association).compact
42
24
  end
@@ -1,30 +1,16 @@
1
1
  module SimpleForm
2
2
  module Components
3
+ # Needs to be enabled in order to do automatic lookups.
3
4
  module Hints
4
5
  def hint
5
- enabled_hint
6
+ @hint ||= begin
7
+ hint = options[:hint]
8
+ hint.is_a?(String) ? hint : translate(:hints)
9
+ end
6
10
  end
7
11
 
8
- private
9
-
10
- def enabled_hint
11
- template.content_tag(hint_tag, hint_text, hint_html_options) unless hint_text.blank?
12
- end
13
-
14
- def disabled_hint
15
- nil
16
- end
17
-
18
- def hint_tag
19
- options[:hint_tag] || SimpleForm.hint_tag
20
- end
21
-
22
- def hint_text
23
- @hint_text ||= options[:hint] || translate(:hints)
24
- end
25
-
26
- def hint_html_options
27
- html_options_for(:hint, [SimpleForm.hint_class])
12
+ def has_hint?
13
+ hint.present?
28
14
  end
29
15
  end
30
16
  end
@@ -0,0 +1,26 @@
1
+ module SimpleForm
2
+ module Components
3
+ module HTML5
4
+ def initialize(*)
5
+ @html5 = false
6
+ end
7
+
8
+ def html5
9
+ @html5 = true
10
+ input_html_options[:required] = true if has_required?
11
+ nil
12
+ end
13
+
14
+ def html5?
15
+ @html5
16
+ end
17
+
18
+ def has_required?
19
+ # We need to check browser_validations because
20
+ # some browsers are still checking required even
21
+ # if novalidate was given.
22
+ required_field? && SimpleForm.browser_validations
23
+ end
24
+ end
25
+ end
26
+ end
@@ -22,7 +22,11 @@ module SimpleForm
22
22
  end
23
23
 
24
24
  def label
25
- enabled_label
25
+ if generate_label_for_attribute?
26
+ @builder.label(label_target, label_text, label_html_options)
27
+ else
28
+ template.label_tag(nil, label_text, label_html_options)
29
+ end
26
30
  end
27
31
 
28
32
  def label_text
@@ -34,20 +38,18 @@ module SimpleForm
34
38
  end
35
39
 
36
40
  def label_html_options
37
- label_options = html_options_for(:label, [input_type, required_class, SimpleForm.label_class])
38
- label_options[:for] = options[:input_html][:id] if options.key?(:input_html) && options[:input_html].key?(:id)
39
- label_options
40
- end
41
-
42
- protected
41
+ label_html_classes = SimpleForm.additional_classes_for(:label) {
42
+ [input_type, required_class, SimpleForm.label_class].compact
43
+ }
43
44
 
44
- def enabled_label
45
- @builder.label(label_target, label_text, label_html_options)
45
+ label_options = html_options_for(:label, label_html_classes)
46
+ if options.key?(:input_html) && options[:input_html].key?(:id)
47
+ label_options[:for] = options[:input_html][:id]
48
+ end
49
+ label_options
46
50
  end
47
51
 
48
- def disabled_label
49
- ""
50
- end
52
+ protected
51
53
 
52
54
  def raw_label_text #:nodoc:
53
55
  options[:label] || label_translation
@@ -55,17 +57,23 @@ module SimpleForm
55
57
 
56
58
  # Default required text when attribute is required.
57
59
  def required_label_text #:nodoc:
58
- attribute_required? ? self.class.translate_required_html.dup : ''
60
+ required_field? ? self.class.translate_required_html.dup : ''
59
61
  end
60
62
 
61
63
  # First check labels translation and then human attribute name.
62
64
  def label_translation #:nodoc:
63
- translate(:labels) || if object.class.respond_to?(:human_attribute_name)
65
+ if SimpleForm.translate_labels && (translated_label = translate(:labels))
66
+ translated_label
67
+ elsif object.class.respond_to?(:human_attribute_name)
64
68
  object.class.human_attribute_name(reflection_or_attribute_name.to_s)
65
69
  else
66
70
  attribute_name.to_s.humanize
67
71
  end
68
72
  end
73
+
74
+ def generate_label_for_attribute?
75
+ true
76
+ end
69
77
  end
70
78
  end
71
79
  end
@@ -0,0 +1,41 @@
1
+ module SimpleForm
2
+ module Components
3
+ # Needs to be enabled in order to do automatic lookups.
4
+ module Maxlength
5
+ def maxlength
6
+ input_html_options[:maxlength] ||= maximum_length_from_validation || limit
7
+ nil
8
+ end
9
+
10
+ private
11
+
12
+ def maximum_length_from_validation
13
+ maxlength = options[:maxlength]
14
+ if maxlength.is_a?(String) || maxlength.is_a?(Integer)
15
+ maxlength
16
+ else
17
+ length_validator = find_length_validator
18
+
19
+ if length_validator && !has_tokenizer?(length_validator)
20
+ length_validator.options[:is] || length_validator.options[:maximum]
21
+ end
22
+ end
23
+ end
24
+
25
+ def find_length_validator
26
+ find_validator(ActiveModel::Validations::LengthValidator)
27
+ end
28
+
29
+ def has_tokenizer?(length_validator)
30
+ tokenizer = length_validator.options[:tokenizer]
31
+
32
+ # TODO: Remove this check when we drop Rails 3.0 support
33
+ if ActiveModel::Validations::LengthValidator.const_defined?(:DEFAULT_TOKENIZER)
34
+ tokenizer && tokenizer != ActiveModel::Validations::LengthValidator::DEFAULT_TOKENIZER
35
+ else
36
+ tokenizer
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,49 @@
1
+ module SimpleForm
2
+ module Components
3
+ module MinMax
4
+ def min_max
5
+ if numeric_validator = find_numericality_validator
6
+ validator_options = numeric_validator.options
7
+ input_html_options[:min] ||= minimum_value(validator_options)
8
+ input_html_options[:max] ||= maximum_value(validator_options)
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def integer?
15
+ input_type == :integer
16
+ end
17
+
18
+ def minimum_value(validator_options)
19
+ if integer? && validator_options.key?(:greater_than)
20
+ evaluate_numericality_validator_option(validator_options[:greater_than]) + 1
21
+ else
22
+ evaluate_numericality_validator_option(validator_options[:greater_than_or_equal_to])
23
+ end
24
+ end
25
+
26
+ def maximum_value(validator_options)
27
+ if integer? && validator_options.key?(:less_than)
28
+ evaluate_numericality_validator_option(validator_options[:less_than]) - 1
29
+ else
30
+ evaluate_numericality_validator_option(validator_options[:less_than_or_equal_to])
31
+ end
32
+ end
33
+
34
+ def find_numericality_validator
35
+ find_validator(ActiveModel::Validations::NumericalityValidator)
36
+ end
37
+
38
+ def evaluate_numericality_validator_option(option)
39
+ if option.is_a?(Numeric)
40
+ option
41
+ elsif option.is_a?(Symbol)
42
+ object.send(option)
43
+ elsif option.respond_to?(:call)
44
+ option.call(object)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,34 @@
1
+ module SimpleForm
2
+ module Components
3
+ # Needs to be enabled in order to do automatic lookups.
4
+ module Pattern
5
+ def pattern
6
+ input_html_options[:pattern] ||= pattern_source
7
+ nil
8
+ end
9
+
10
+ private
11
+
12
+ def pattern_source
13
+ pattern = options[:pattern]
14
+ if pattern.is_a?(String)
15
+ pattern
16
+ elsif pattern_validator = find_pattern_validator
17
+ evaluate_format_validator_option(pattern_validator.options[:with]).source
18
+ end
19
+ end
20
+
21
+ def find_pattern_validator
22
+ find_validator(ActiveModel::Validations::FormatValidator)
23
+ end
24
+
25
+ def evaluate_format_validator_option(option)
26
+ if option.respond_to?(:call)
27
+ option.call(object)
28
+ else
29
+ option
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,28 +1,16 @@
1
1
  module SimpleForm
2
2
  module Components
3
+ # Needs to be enabled in order to do automatic lookups.
3
4
  module Placeholders
4
5
  def placeholder
5
- disabled_placeholder
6
- end
7
-
8
- private
9
-
10
- def enabled_placeholder
11
- input_html_options[:placeholder] ||= placeholder_text if placeholder_present?
12
- nil
13
- end
14
-
15
- def disabled_placeholder
6
+ input_html_options[:placeholder] ||= placeholder_text
16
7
  nil
17
8
  end
18
9
 
19
- def placeholder_present?
20
- options[:placeholder] != false && placeholder_text.present?
21
- end
22
-
23
10
  def placeholder_text
24
- @placeholder_text ||= options[:placeholder] || translate(:placeholders)
11
+ placeholder = options[:placeholder]
12
+ placeholder.is_a?(String) ? placeholder : translate(:placeholders)
25
13
  end
26
14
  end
27
15
  end
28
- end
16
+ end
@@ -0,0 +1,22 @@
1
+ module SimpleForm
2
+ module Components
3
+ # Needs to be enabled in order to do automatic lookups.
4
+ module Readonly
5
+ def readonly
6
+ if readonly_attribute? && !has_readonly?
7
+ input_html_options[:readonly] ||= true
8
+ input_html_classes << :readonly
9
+ end
10
+ nil
11
+ end
12
+
13
+ private
14
+
15
+ def readonly_attribute?
16
+ object.class.respond_to?(:readonly_attributes) &&
17
+ object.persisted? &&
18
+ object.class.readonly_attributes.include?(attribute_name)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,10 +1,20 @@
1
1
  module SimpleForm
2
+ # Components are a special type of helpers that can work on their own.
3
+ # For example, by using a component, it will automatically change the
4
+ # output under given circumstances without user input. For example,
5
+ # the disabled helper always need a :disabled => true option given
6
+ # to the input in order to be enabled. On the other hand, things like
7
+ # hints can generate output automatically by doing I18n lookups.
2
8
  module Components
3
9
  autoload :Errors, 'simple_form/components/errors'
4
10
  autoload :Hints, 'simple_form/components/hints'
11
+ autoload :HTML5, 'simple_form/components/html5'
5
12
  autoload :LabelInput, 'simple_form/components/label_input'
6
13
  autoload :Labels, 'simple_form/components/labels'
14
+ autoload :MinMax, 'simple_form/components/min_max'
15
+ autoload :Maxlength, 'simple_form/components/maxlength'
16
+ autoload :Pattern, 'simple_form/components/pattern'
7
17
  autoload :Placeholders, 'simple_form/components/placeholders'
8
- autoload :Wrapper, 'simple_form/components/wrapper'
18
+ autoload :Readonly, 'simple_form/components/readonly'
9
19
  end
10
20
  end
@@ -0,0 +1,16 @@
1
+ # TODO: Delete this file when we drop support for Rails 3.0
2
+ # This method is already implemented in active_support 3.1
3
+
4
+ unless Hash.new.respond_to?(:deep_dup)
5
+ class Hash
6
+ # Returns a deep copy of hash.
7
+ def deep_dup
8
+ duplicate = self.dup
9
+ duplicate.each_pair do |k,v|
10
+ tv = duplicate[k]
11
+ duplicate[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_dup : v
12
+ end
13
+ duplicate
14
+ end
15
+ end
16
+ end
@@ -1,7 +1,6 @@
1
1
  module SimpleForm
2
2
  class ErrorNotification
3
3
  delegate :object, :object_name, :template, :to => :@builder
4
- include SimpleForm::Helpers::HasErrors
5
4
 
6
5
  def initialize(builder, options)
7
6
  @builder = builder
@@ -17,8 +16,16 @@ module SimpleForm
17
16
 
18
17
  protected
19
18
 
19
+ def errors
20
+ object.errors
21
+ end
22
+
23
+ def has_errors?
24
+ object && object.respond_to?(:errors) && errors.present?
25
+ end
26
+
20
27
  def error_message
21
- @message || translate_error_notification
28
+ (@message || translate_error_notification).html_safe
22
29
  end
23
30
 
24
31
  def error_notification_tag
@@ -27,7 +34,6 @@ module SimpleForm
27
34
 
28
35
  def html_options
29
36
  @options[:class] = "#{SimpleForm.error_notification_class} #{@options[:class]}".strip
30
- @options[:id] = SimpleForm.error_notification_id if SimpleForm.error_notification_id
31
37
  @options
32
38
  end
33
39