formtastic 3.0.0 → 5.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 (175) hide show
  1. checksums.yaml +5 -13
  2. data/.gitattributes +1 -0
  3. data/.github/workflows/test.yml +61 -0
  4. data/.gitignore +3 -2
  5. data/CHANGELOG.md +61 -0
  6. data/Gemfile.lock +140 -0
  7. data/MIT-LICENSE +1 -1
  8. data/{README.textile → README.md} +191 -168
  9. data/RELEASE_PROCESS +3 -1
  10. data/Rakefile +24 -8
  11. data/app/assets/stylesheets/formtastic.css +1 -1
  12. data/bin/appraisal +8 -0
  13. data/formtastic.gemspec +13 -17
  14. data/gemfiles/rails_6.0/Gemfile +5 -0
  15. data/gemfiles/rails_6.1/Gemfile +5 -0
  16. data/gemfiles/rails_7.0/Gemfile +5 -0
  17. data/gemfiles/rails_7.1/Gemfile +5 -0
  18. data/gemfiles/rails_edge/Gemfile +13 -0
  19. data/lib/formtastic/action_class_finder.rb +19 -0
  20. data/lib/formtastic/actions/base.rb +1 -0
  21. data/lib/formtastic/actions/button_action.rb +56 -53
  22. data/lib/formtastic/actions/buttonish.rb +1 -0
  23. data/lib/formtastic/actions/input_action.rb +60 -57
  24. data/lib/formtastic/actions/link_action.rb +69 -67
  25. data/lib/formtastic/actions.rb +7 -3
  26. data/lib/formtastic/deprecation.rb +6 -0
  27. data/lib/formtastic/engine.rb +4 -1
  28. data/lib/formtastic/form_builder.rb +32 -25
  29. data/lib/formtastic/helpers/action_helper.rb +22 -31
  30. data/lib/formtastic/helpers/actions_helper.rb +1 -0
  31. data/lib/formtastic/helpers/enum.rb +14 -0
  32. data/lib/formtastic/helpers/errors_helper.rb +3 -2
  33. data/lib/formtastic/helpers/fieldset_wrapper.rb +16 -11
  34. data/lib/formtastic/helpers/file_column_detection.rb +1 -0
  35. data/lib/formtastic/helpers/form_helper.rb +4 -3
  36. data/lib/formtastic/helpers/input_helper.rb +59 -80
  37. data/lib/formtastic/helpers/inputs_helper.rb +33 -27
  38. data/lib/formtastic/helpers/reflection.rb +5 -4
  39. data/lib/formtastic/helpers.rb +2 -2
  40. data/lib/formtastic/html_attributes.rb +13 -1
  41. data/lib/formtastic/i18n.rb +2 -1
  42. data/lib/formtastic/input_class_finder.rb +19 -0
  43. data/lib/formtastic/inputs/base/associations.rb +1 -0
  44. data/lib/formtastic/inputs/base/choices.rb +4 -3
  45. data/lib/formtastic/inputs/base/collections.rb +47 -11
  46. data/lib/formtastic/inputs/base/database.rb +8 -5
  47. data/lib/formtastic/inputs/base/datetime_pickerish.rb +1 -0
  48. data/lib/formtastic/inputs/base/errors.rb +7 -6
  49. data/lib/formtastic/inputs/base/fileish.rb +1 -0
  50. data/lib/formtastic/inputs/base/hints.rb +2 -1
  51. data/lib/formtastic/inputs/base/html.rb +12 -10
  52. data/lib/formtastic/inputs/base/labelling.rb +3 -2
  53. data/lib/formtastic/inputs/base/naming.rb +5 -4
  54. data/lib/formtastic/inputs/base/numeric.rb +1 -0
  55. data/lib/formtastic/inputs/base/options.rb +3 -3
  56. data/lib/formtastic/inputs/base/placeholder.rb +1 -0
  57. data/lib/formtastic/inputs/base/stringish.rb +1 -0
  58. data/lib/formtastic/inputs/base/timeish.rb +9 -4
  59. data/lib/formtastic/inputs/base/validations.rb +39 -12
  60. data/lib/formtastic/inputs/base/wrapping.rb +2 -3
  61. data/lib/formtastic/inputs/base.rb +17 -12
  62. data/lib/formtastic/inputs/boolean_input.rb +2 -1
  63. data/lib/formtastic/inputs/check_boxes_input.rb +16 -24
  64. data/lib/formtastic/inputs/color_input.rb +1 -1
  65. data/lib/formtastic/inputs/country_input.rb +4 -1
  66. data/lib/formtastic/inputs/datalist_input.rb +42 -0
  67. data/lib/formtastic/inputs/date_picker_input.rb +1 -0
  68. data/lib/formtastic/inputs/date_select_input.rb +1 -0
  69. data/lib/formtastic/inputs/datetime_picker_input.rb +1 -0
  70. data/lib/formtastic/inputs/datetime_select_input.rb +1 -0
  71. data/lib/formtastic/inputs/email_input.rb +1 -0
  72. data/lib/formtastic/inputs/file_input.rb +3 -2
  73. data/lib/formtastic/inputs/hidden_input.rb +3 -2
  74. data/lib/formtastic/inputs/number_input.rb +1 -0
  75. data/lib/formtastic/inputs/password_input.rb +1 -0
  76. data/lib/formtastic/inputs/phone_input.rb +1 -0
  77. data/lib/formtastic/inputs/radio_input.rb +26 -21
  78. data/lib/formtastic/inputs/range_input.rb +1 -0
  79. data/lib/formtastic/inputs/search_input.rb +1 -0
  80. data/lib/formtastic/inputs/select_input.rb +32 -10
  81. data/lib/formtastic/inputs/string_input.rb +1 -0
  82. data/lib/formtastic/inputs/text_input.rb +1 -0
  83. data/lib/formtastic/inputs/time_picker_input.rb +1 -0
  84. data/lib/formtastic/inputs/time_select_input.rb +1 -0
  85. data/lib/formtastic/inputs/time_zone_input.rb +17 -6
  86. data/lib/formtastic/inputs/url_input.rb +1 -0
  87. data/lib/formtastic/inputs.rb +33 -28
  88. data/lib/formtastic/localized_string.rb +2 -1
  89. data/lib/formtastic/localizer.rb +23 -24
  90. data/lib/formtastic/namespaced_class_finder.rb +98 -0
  91. data/lib/formtastic/version.rb +2 -1
  92. data/lib/formtastic.rb +19 -14
  93. data/lib/generators/formtastic/form/form_generator.rb +8 -2
  94. data/lib/generators/formtastic/input/input_generator.rb +47 -0
  95. data/lib/generators/formtastic/install/install_generator.rb +2 -0
  96. data/lib/generators/templates/formtastic.rb +29 -7
  97. data/lib/generators/templates/input.rb +19 -0
  98. data/sample/basic_inputs.html +1 -1
  99. data/script/integration-template.rb +73 -0
  100. data/script/integration.sh +19 -0
  101. data/spec/action_class_finder_spec.rb +13 -0
  102. data/spec/actions/button_action_spec.rb +21 -20
  103. data/spec/actions/generic_action_spec.rb +134 -133
  104. data/spec/actions/input_action_spec.rb +20 -19
  105. data/spec/actions/link_action_spec.rb +30 -29
  106. data/spec/builder/custom_builder_spec.rb +39 -22
  107. data/spec/builder/error_proc_spec.rb +6 -5
  108. data/spec/builder/semantic_fields_for_spec.rb +46 -45
  109. data/spec/fast_spec_helper.rb +13 -0
  110. data/spec/generators/formtastic/form/form_generator_spec.rb +33 -32
  111. data/spec/generators/formtastic/input/input_generator_spec.rb +125 -0
  112. data/spec/generators/formtastic/install/install_generator_spec.rb +10 -9
  113. data/spec/helpers/action_helper_spec.rb +70 -97
  114. data/spec/helpers/actions_helper_spec.rb +43 -42
  115. data/spec/helpers/form_helper_spec.rb +56 -39
  116. data/spec/helpers/input_helper_spec.rb +314 -255
  117. data/spec/helpers/inputs_helper_spec.rb +217 -202
  118. data/spec/helpers/reflection_helper_spec.rb +7 -6
  119. data/spec/helpers/semantic_errors_helper_spec.rb +26 -25
  120. data/spec/i18n_spec.rb +30 -29
  121. data/spec/input_class_finder_spec.rb +11 -0
  122. data/spec/inputs/base/collections_spec.rb +78 -0
  123. data/spec/inputs/base/validations_spec.rb +481 -0
  124. data/spec/inputs/boolean_input_spec.rb +73 -72
  125. data/spec/inputs/check_boxes_input_spec.rb +174 -123
  126. data/spec/inputs/color_input_spec.rb +53 -64
  127. data/spec/inputs/country_input_spec.rb +23 -22
  128. data/spec/inputs/custom_input_spec.rb +3 -6
  129. data/spec/inputs/datalist_input_spec.rb +62 -0
  130. data/spec/inputs/date_picker_input_spec.rb +114 -113
  131. data/spec/inputs/date_select_input_spec.rb +76 -61
  132. data/spec/inputs/datetime_picker_input_spec.rb +123 -122
  133. data/spec/inputs/datetime_select_input_spec.rb +85 -68
  134. data/spec/inputs/email_input_spec.rb +17 -16
  135. data/spec/inputs/file_input_spec.rb +18 -17
  136. data/spec/inputs/hidden_input_spec.rb +32 -31
  137. data/spec/inputs/include_blank_spec.rb +10 -9
  138. data/spec/inputs/label_spec.rb +36 -31
  139. data/spec/inputs/number_input_spec.rb +212 -211
  140. data/spec/inputs/password_input_spec.rb +17 -16
  141. data/spec/inputs/phone_input_spec.rb +17 -16
  142. data/spec/inputs/placeholder_spec.rb +18 -17
  143. data/spec/inputs/radio_input_spec.rb +92 -65
  144. data/spec/inputs/range_input_spec.rb +136 -135
  145. data/spec/inputs/readonly_spec.rb +51 -0
  146. data/spec/inputs/search_input_spec.rb +16 -15
  147. data/spec/inputs/select_input_spec.rb +209 -102
  148. data/spec/inputs/string_input_spec.rb +51 -50
  149. data/spec/inputs/text_input_spec.rb +34 -33
  150. data/spec/inputs/time_picker_input_spec.rb +115 -114
  151. data/spec/inputs/time_select_input_spec.rb +84 -70
  152. data/spec/inputs/time_zone_input_spec.rb +58 -31
  153. data/spec/inputs/url_input_spec.rb +17 -16
  154. data/spec/inputs/with_options_spec.rb +9 -8
  155. data/spec/localizer_spec.rb +18 -17
  156. data/spec/namespaced_class_finder_spec.rb +91 -0
  157. data/spec/schema.rb +22 -0
  158. data/spec/spec_helper.rb +180 -249
  159. data/spec/support/custom_macros.rb +128 -98
  160. data/spec/support/deprecation.rb +2 -1
  161. data/spec/support/shared_examples.rb +13 -0
  162. data/spec/support/specialized_class_finder_shared_example.rb +28 -0
  163. data/spec/support/test_environment.rb +25 -10
  164. metadata +95 -136
  165. data/.travis.yml +0 -28
  166. data/Appraisals +0 -25
  167. data/CHANGELOG +0 -27
  168. data/gemfiles/rails_3.2.gemfile +0 -7
  169. data/gemfiles/rails_4.0.4.gemfile +0 -7
  170. data/gemfiles/rails_4.1.gemfile +0 -7
  171. data/gemfiles/rails_4.gemfile +0 -7
  172. data/gemfiles/rails_edge.gemfile +0 -10
  173. data/lib/formtastic/util.rb +0 -53
  174. data/spec/support/deferred_garbage_collection.rb +0 -21
  175. data/spec/util_spec.rb +0 -52
@@ -1,9 +1,19 @@
1
+ # frozen_string_literal: true
1
2
  module Formtastic
2
3
  class FormBuilder < ActionView::Helpers::FormBuilder
3
4
 
4
- def self.configure(name, value = nil)
5
+ # Defines a new configurable option
6
+ # @param [Symbol] name the configuration name
7
+ # @param [Object] default the configuration default value
8
+ # @private
9
+ #
10
+ # @!macro [new] configure
11
+ # @!scope class
12
+ # @!attribute [rw] $1
13
+ # @api public
14
+ def self.configure(name, default = nil)
5
15
  class_attribute(name)
6
- self.send(:"#{name}=", value)
16
+ self.send(:"#{name}=", default)
7
17
  end
8
18
 
9
19
  configure :custom_namespace
@@ -12,7 +22,7 @@ module Formtastic
12
22
  configure :default_text_area_width
13
23
  configure :all_fields_required_by_default, true
14
24
  configure :include_blank_for_select_by_default, true
15
- configure :required_string, proc { Formtastic::Util.html_safe(%{<abbr title="#{Formtastic::I18n.t(:required)}">*</abbr>}) }
25
+ configure :required_string, proc { %{<abbr title="#{Formtastic::I18n.t(:required)}">*</abbr>}.html_safe }
16
26
  configure :optional_string, ''
17
27
  configure :inline_errors, :sentence
18
28
  configure :label_str_method, :humanize
@@ -31,6 +41,15 @@ module Formtastic
31
41
  configure :default_hint_class, 'inline-hints'
32
42
  configure :use_required_attribute, false
33
43
  configure :perform_browser_validations, false
44
+ # Check {Formtastic::InputClassFinder} to see how are inputs resolved.
45
+ configure :input_namespaces, [::Object, ::Formtastic::Inputs]
46
+ configure :input_class_finder, Formtastic::InputClassFinder
47
+ # Check {Formtastic::ActionClassFinder} to see how are inputs resolved.
48
+ configure :action_namespaces, [::Object, ::Formtastic::Actions]
49
+ configure :action_class_finder, Formtastic::ActionClassFinder
50
+
51
+ configure :skipped_columns, [:created_at, :updated_at, :created_on, :updated_on, :lock_version, :version]
52
+ configure :priority_time_zones, []
34
53
 
35
54
  attr_reader :template
36
55
 
@@ -43,11 +62,11 @@ module Formtastic
43
62
  include Formtastic::Helpers::ActionHelper
44
63
  include Formtastic::Helpers::ActionsHelper
45
64
  include Formtastic::Helpers::ErrorsHelper
46
-
47
- # This is a wrapper around Rails' `ActionView::Helpers::FormBuilder#fields_for`, originally
65
+
66
+ # This is a wrapper around Rails' `ActionView::Helpers::FormBuilder#fields_for`, originally
48
67
  # provided to ensure that the `:builder` from `semantic_form_for` was passed down into
49
- # the nested `fields_for`. Rails 3 no longer requires us to do this, so this method is
50
- # provided purely for backwards compatibility and DSL consistency.
68
+ # the nested `fields_for`. Our supported versions of Rails no longer require us to do this,
69
+ # so this method is provided purely for backwards compatibility and DSL consistency.
51
70
  #
52
71
  # When constructing a `fields_for` form fragment *outside* of `semantic_form_for`, please use
53
72
  # `Formtastic::Helpers::FormHelper#semantic_fields_for`.
@@ -73,29 +92,17 @@ module Formtastic
73
92
  #
74
93
  # @todo is there a way to test the params structure of the Rails helper we wrap to ensure forward compatibility?
75
94
  def semantic_fields_for(record_or_name_or_array, *args, &block)
76
- # Add a :parent_builder to the args so that nested translations can be possible in Rails 3
77
- options = args.extract_options!
78
- options[:parent_builder] ||= self
79
-
80
- # Wrap the Rails helper
81
- fields_for(record_or_name_or_array, *(args << options), &block)
95
+ fields_for(record_or_name_or_array, *args, &block)
82
96
  end
83
-
84
- def initialize(object_name, object, template, options, block=nil)
85
- # rails 3 supported passing in the block parameter to FormBuilder
86
- # rails 4.0 deprecated the block parameter and does nothing with it
87
- # rails 4.1 removes the parameter completely
88
- if Util.rails3? || Util.rails4_0?
89
- super
90
- else # Must be rails4_1 or greater
91
- super object_name, object, template, options
92
- end
93
-
97
+
98
+ def initialize(object_name, object, template, options)
99
+ super
100
+
94
101
  if respond_to?('multipart=') && options.is_a?(Hash) && options[:html]
95
102
  self.multipart = options[:html][:multipart]
96
103
  end
97
104
  end
98
-
105
+
99
106
  end
100
107
 
101
108
  end
@@ -1,8 +1,8 @@
1
1
  # -*- coding: utf-8 -*-
2
+ # frozen_string_literal: true
2
3
  module Formtastic
3
4
  module Helpers
4
5
  module ActionHelper
5
-
6
6
  # Renders an action for the form (such as a subit/reset button, or a cancel link).
7
7
  #
8
8
  # Each action is wrapped in an `<li class="action">` tag with other classes added based on the
@@ -23,9 +23,9 @@ module Formtastic
23
23
  # reset: "Reset form"
24
24
  # submit: "Submit"
25
25
  #
26
- # For forms with an object present, the `update` key will be used if calling `persisted?` on
27
- # the object returns true (saving changes to a record), otherwise the `create` ey will be
28
- # used. The `submit` key is used as a fallback when there is no object or we cannot determine
26
+ # For forms with an object present, the `update` key will be used if calling `persisted?` on
27
+ # the object returns true (saving changes to a record), otherwise the `create` key will be
28
+ # used. The `submit` key is used as a fallback when there is no object or we cannot determine
29
29
  # if `create` or `update` is appropriate.
30
30
  #
31
31
  # @example Basic usage
@@ -80,46 +80,37 @@ module Formtastic
80
80
  options = options.dup # Allow options to be shared without being tainted by Formtastic
81
81
  options[:as] ||= default_action_type(method, options)
82
82
 
83
- klass = action_class(options[:as])
83
+ klass = namespaced_action_class(options[:as])
84
84
 
85
85
  klass.new(self, template, @object, @object_name, method, options).to_html
86
86
  end
87
87
 
88
88
  protected
89
89
 
90
- def default_action_type(method, options = {}) #:nodoc:
90
+ def default_action_type(method, options = {}) # @private
91
91
  case method
92
92
  when :submit then :input
93
- when :reset then :input
93
+ when :reset then :input
94
94
  when :cancel then :link
95
+ else method
95
96
  end
96
97
  end
97
98
 
98
- def action_class(as)
99
- @input_classes_cache ||= {}
100
- @input_classes_cache[as] ||= begin
101
- begin
102
- begin
103
- custom_action_class_name(as).constantize
104
- rescue NameError
105
- standard_action_class_name(as).constantize
106
- end
107
- rescue NameError
108
- raise Formtastic::UnknownActionError
109
- end
110
- end
111
- end
112
-
113
- # :as => :button # => ButtonAction
114
- def custom_action_class_name(as)
115
- "#{as.to_s.camelize}Action"
116
- end
117
-
118
- # :as => :button # => Formtastic::Actions::ButtonAction
119
- def standard_action_class_name(as)
120
- "Formtastic::Actions::#{as.to_s.camelize}Action"
99
+ # Takes the `:as` option and attempts to return the corresponding action
100
+ # class. In the case of `:as => :awesome` it will first attempt to find a
101
+ # top level `AwesomeAction` class (to allow the application to subclass
102
+ # and modify to suit), falling back to `Formtastic::Actions::AwesomeAction`.
103
+ #
104
+ # Custom action namespaces to look into can be configured via the
105
+ # {Formtastic::FormBuilder.action_namespaces} configuration setting.
106
+ # @see Helpers::InputHelper#namespaced_input_class
107
+ # @see Formtastic::ActionClassFinder
108
+ def namespaced_action_class(as)
109
+ @action_class_finder ||= action_class_finder.new(self)
110
+ @action_class_finder.find(as)
111
+ rescue Formtastic::ActionClassFinder::NotFoundError => e
112
+ raise Formtastic::UnknownActionError, "Unable to find action #{e.message}"
121
113
  end
122
-
123
114
  end
124
115
  end
125
116
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Formtastic
2
3
  module Helpers
3
4
  # ActionsHelper encapsulates the responsibilties of the {#actions} DSL for acting on
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ module Formtastic
3
+ module Helpers
4
+ # @private
5
+ module Enum
6
+ # Returns the enum (if defined) for the given method
7
+ def enum_for(method) # @private
8
+ if @object.respond_to?(:defined_enums)
9
+ @object.defined_enums[method.to_s]
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Formtastic
2
3
  module Helpers
3
4
  module ErrorsHelper
@@ -52,7 +53,7 @@ module Formtastic
52
53
  return nil if full_errors.blank?
53
54
  html_options[:class] ||= "errors"
54
55
  template.content_tag(:ul, html_options) do
55
- Formtastic::Util.html_safe(full_errors.map { |error| template.content_tag(:li, Formtastic::Util.html_safe(error)) }.join)
56
+ full_errors.map { |error| template.content_tag(:li, error) }.join.html_safe
56
57
  end
57
58
  end
58
59
 
@@ -78,4 +79,4 @@ module Formtastic
78
79
  end
79
80
  end
80
81
  end
81
- end
82
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Formtastic
2
3
  module Helpers
3
4
  # @private
@@ -21,8 +22,8 @@ module Formtastic
21
22
  # f.inputs "My little legend", :title, :body, :author # Explicit legend string => "My little legend"
22
23
  # f.inputs :my_little_legend, :title, :body, :author # Localized (118n) legend with I18n key => I18n.t(:my_little_legend, ...)
23
24
  # f.inputs :title, :body, :author # First argument is a column => (no legend)
24
- def field_set_and_list_wrapping(*args, &block) #:nodoc:
25
- contents = args.last.is_a?(::Hash) ? '' : args.pop.flatten
25
+ def field_set_and_list_wrapping(*args, &block) # @private
26
+ contents = args[-1].is_a?(::Hash) ? '' : args.pop.flatten
26
27
  html_options = args.extract_options!
27
28
 
28
29
  if block_given?
@@ -33,12 +34,15 @@ module Formtastic
33
34
  end
34
35
  end
35
36
 
37
+ # Work-around for empty contents block
38
+ contents ||= ""
39
+
36
40
  # Ruby 1.9: String#to_s behavior changed, need to make an explicit join.
37
41
  contents = contents.join if contents.respond_to?(:join)
38
42
 
39
43
  legend = field_set_legend(html_options)
40
44
  fieldset = template.content_tag(:fieldset,
41
- Formtastic::Util.html_safe(legend) << template.content_tag(:ol, Formtastic::Util.html_safe(contents)),
45
+ legend.html_safe << template.content_tag(:ol, contents.html_safe),
42
46
  html_options.except(:builder, :parent, :name)
43
47
  )
44
48
 
@@ -47,30 +51,31 @@ module Formtastic
47
51
 
48
52
  def field_set_legend(html_options)
49
53
  legend = (html_options[:name] || '').to_s
50
- legend %= parent_child_index(html_options[:parent]) if html_options[:parent]
51
- legend = template.content_tag(:legend, template.content_tag(:span, Formtastic::Util.html_safe(legend))) unless legend.blank?
54
+ # only applying if String includes '%i' avoids argument error when $DEBUG is true
55
+ legend %= parent_child_index(html_options[:parent]) if html_options[:parent] && legend.include?('%i')
56
+ legend = template.content_tag(:legend, template.content_tag(:span, legend.html_safe)) unless legend.blank?
52
57
  legend
53
58
  end
54
59
 
55
60
  # Gets the nested_child_index value from the parent builder. It returns a hash with each
56
61
  # association that the parent builds.
57
- def parent_child_index(parent) #:nodoc:
62
+ def parent_child_index(parent) # @private
58
63
  # Could be {"post[authors_attributes]"=>0} or { :authors => 0 }
59
64
  duck = parent[:builder].instance_variable_get('@nested_child_index')
60
-
65
+
61
66
  # Could be symbol for the association, or a model (or an array of either, I think? TODO)
62
67
  child = parent[:for]
63
68
  # Pull a sybol or model out of Array (TODO: check if there's an Array)
64
69
  child = child.first if child.respond_to?(:first)
65
70
  # If it's an object, get a symbol from the class name
66
71
  child = child.class.name.underscore.to_sym unless child.is_a?(Symbol)
67
-
72
+
68
73
  key = "#{parent[:builder].object_name}[#{child}_attributes]"
69
74
 
70
- # TODO: One of the tests produces a scenario where duck is "0" and the test looks for a "1"
75
+ # TODO: One of the tests produces a scenario where duck is "0" and the test looks for a "1"
71
76
  # in the legend, so if we have a number, return it with a +1 until we can verify this scenario.
72
- return duck + 1 if duck.is_a?(Fixnum)
73
-
77
+ return duck + 1 if duck.is_a?(Integer)
78
+
74
79
  # First try to extract key from duck Hash, then try child
75
80
  (duck[key] || duck[child]).to_i + 1
76
81
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Formtastic
2
3
  module Helpers
3
4
  # @private
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Formtastic
2
3
  module Helpers
3
4
 
@@ -155,7 +156,7 @@ module Formtastic
155
156
  options[:builder] ||= @@builder
156
157
  options[:html] ||= {}
157
158
  options[:html][:novalidate] = !@@builder.perform_browser_validations unless options[:html].key?(:novalidate)
158
- @@builder.custom_namespace = options.delete(:namespace).to_s
159
+ options[:custom_namespace] = options.delete(:namespace)
159
160
 
160
161
  singularizer = defined?(ActiveModel::Naming.singular) ? ActiveModel::Naming.method(:singular) : ActionController::RecordIdentifier.method(:singular_class_name)
161
162
 
@@ -163,7 +164,7 @@ module Formtastic
163
164
  class_names << @@default_form_class
164
165
  model_class_name = case record_or_name_or_array
165
166
  when String, Symbol then record_or_name_or_array.to_s # :post => "post"
166
- when Array then options[:as] || singularizer.call(record_or_name_or_array.last.class) # [@post, @comment] # => "comment"
167
+ when Array then options[:as] || singularizer.call(record_or_name_or_array[-1].class) # [@post, @comment] # => "comment"
167
168
  else options[:as] || singularizer.call(record_or_name_or_array.class) # @post => "post"
168
169
  end
169
170
  class_names << @@default_form_model_class_proc.call(model_class_name)
@@ -182,7 +183,7 @@ module Formtastic
182
183
  def semantic_fields_for(record_name, record_object = nil, options = {}, &block)
183
184
  options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
184
185
  options[:builder] ||= @@builder
185
- @@builder.custom_namespace = options.delete(:namespace).to_s # TODO needed?
186
+ options[:custom_namespace] = options.delete(:namespace)
186
187
 
187
188
  with_custom_field_error_proc do
188
189
  self.fields_for(record_name, record_object, options, &block)
@@ -1,4 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
+ # frozen_string_literal: true
2
3
  module Formtastic
3
4
  module Helpers
4
5
 
@@ -37,6 +38,7 @@ module Formtastic
37
38
  # @see Formtastic::Helpers::FormHelper#semantic_form_for
38
39
  module InputHelper
39
40
  include Formtastic::Helpers::Reflection
41
+ include Formtastic::Helpers::Enum
40
42
  include Formtastic::Helpers::FileColumnDetection
41
43
 
42
44
  # Returns a chunk of HTML markup for a given `method` on the form object, wrapped in
@@ -86,26 +88,26 @@ module Formtastic
86
88
  #
87
89
  # Available input styles:
88
90
  #
89
- # * `:boolean` (see {Inputs::BooleanInput})
90
- # * `:check_boxes` (see {Inputs::CheckBoxesInput})
91
- # * `:color` (see {Inputs::ColorInput})
92
- # * `:country` (see {Inputs::CountryInput})
93
- # * `:datetime_select` (see {Inputs::DatetimeSelectInput})
94
- # * `:date_select` (see {Inputs::DateSelectInput})
95
- # * `:email` (see {Inputs::EmailInput})
96
- # * `:file` (see {Inputs::FileInput})
97
- # * `:hidden` (see {Inputs::HiddenInput})
98
- # * `:number` (see {Inputs::NumberInput})
99
- # * `:password` (see {Inputs::PasswordInput})
100
- # * `:phone` (see {Inputs::PhoneInput})
101
- # * `:radio` (see {Inputs::RadioInput})
102
- # * `:search` (see {Inputs::SearchInput})
103
- # * `:select` (see {Inputs::SelectInput})
104
- # * `:string` (see {Inputs::StringInput})
105
- # * `:text` (see {Inputs::TextInput})
106
- # * `:time_zone` (see {Inputs::TimeZoneInput})
107
- # * `:time_select` (see {Inputs::TimeSelectInput})
108
- # * `:url` (see {Inputs::UrlInput})
91
+ # * `:boolean` (see {Inputs::BooleanInput})
92
+ # * `:check_boxes` (see {Inputs::CheckBoxesInput})
93
+ # * `:color` (see {Inputs::ColorInput})
94
+ # * `:country` (see {Inputs::CountryInput})
95
+ # * `:datetime_select` (see {Inputs::DatetimeSelectInput})
96
+ # * `:date_select` (see {Inputs::DateSelectInput})
97
+ # * `:email` (see {Inputs::EmailInput})
98
+ # * `:file` (see {Inputs::FileInput})
99
+ # * `:hidden` (see {Inputs::HiddenInput})
100
+ # * `:number` (see {Inputs::NumberInput})
101
+ # * `:password` (see {Inputs::PasswordInput})
102
+ # * `:phone` (see {Inputs::PhoneInput})
103
+ # * `:radio` (see {Inputs::RadioInput})
104
+ # * `:search` (see {Inputs::SearchInput})
105
+ # * `:select` (see {Inputs::SelectInput})
106
+ # * `:string` (see {Inputs::StringInput})
107
+ # * `:text` (see {Inputs::TextInput})
108
+ # * `:time_zone` (see {Inputs::TimeZoneInput})
109
+ # * `:time_select` (see {Inputs::TimeSelectInput})
110
+ # * `:url` (see {Inputs::UrlInput})
109
111
  #
110
112
  # Calling `:as => :string` (for example) will call `#to_html` on a new instance of
111
113
  # `Formtastic::Inputs::StringInput`. Before this, Formtastic will try to instantiate a top-level
@@ -131,6 +133,7 @@ module Formtastic
131
133
  #
132
134
  # @option options :input_html [Hash]
133
135
  # Override or add to the HTML attributes to be passed down to the `<input>` tag
136
+ # (If you use attr_readonly method in your model, formtastic will automatically set those attributes's input readonly)
134
137
  #
135
138
  # @option options :wrapper_html [Hash]
136
139
  # Override or add to the HTML attributes to be passed down to the wrapping `<li>` tag
@@ -138,12 +141,6 @@ module Formtastic
138
141
  # @option options :collection [Array<ActiveModel, String, Symbol>, Hash{String => String, Boolean}, OrderedHash{String => String, Boolean}]
139
142
  # Override collection of objects in the association (`:select`, `:radio` & `:check_boxes` inputs only)
140
143
  #
141
- # @option options :member_label [Symbol, Proc, Method]
142
- # Override the method called on each object in the `:collection` for use as the `<label>` content (`:check_boxes` & `:radio` inputs) or `<option>` content (`:select` inputs)
143
- #
144
- # @option options :member_value [Symbol, Proc, Method]
145
- # Override the method called on each object in the `:collection` for use as the `value` attribute in the `<input>` (`:check_boxes` & `:radio` inputs) or `<option>` (`:select` inputs)
146
- #
147
144
  # @option options :multiple [Boolean]
148
145
  # Specify if the `:select` input should allow multiple selections or not (defaults to `belongs_to` associations, and `true` for `has_many` and `has_and_belongs_to_many` associations)
149
146
  #
@@ -230,14 +227,15 @@ module Formtastic
230
227
  # first_name: "Joe"
231
228
  # last_name: "Smith"
232
229
  #
230
+ # @see #namespaced_input_class
233
231
  # @todo Many many more examples. Some of the detail probably needs to be pushed out to the relevant methods too.
234
232
  # @todo More i18n examples.
235
233
  def input(method, options = {})
236
- method = method.to_sym if method.is_a?(String)
234
+ method = method.to_sym
237
235
  options = options.dup # Allow options to be shared without being tainted by Formtastic
238
236
  options[:as] ||= default_input_type(method, options)
239
237
 
240
- klass = input_class(options[:as])
238
+ klass = namespaced_input_class(options[:as])
241
239
 
242
240
  klass.new(self, template, @object, @object_name, method, options).to_html
243
241
  end
@@ -254,14 +252,15 @@ module Formtastic
254
252
  #
255
253
  # If there is no column for the method (eg "virtual columns" with an attr_accessor), the
256
254
  # default is a :string, a similar behaviour to Rails' scaffolding.
257
- def default_input_type(method, options = {}) #:nodoc:
255
+ def default_input_type(method, options = {}) # @private
258
256
  if @object
259
257
  return :select if reflection_for(method)
260
258
 
261
259
  return :file if is_file?(method, options)
262
260
  end
263
261
 
264
- if column = column_for(method)
262
+ column = column_for(method)
263
+ if column && column.type
265
264
  # Special cases where the column type doesn't map to an input method.
266
265
  case column.type
267
266
  when :string
@@ -275,6 +274,7 @@ module Formtastic
275
274
  return :color if method.to_s =~ /color/
276
275
  when :integer
277
276
  return :select if reflection_for(method)
277
+ return :select if enum_for(method)
278
278
  return :number
279
279
  when :float, :decimal
280
280
  return :number
@@ -284,6 +284,10 @@ module Formtastic
284
284
  return :time_select
285
285
  when :date
286
286
  return :date_select
287
+ when :hstore, :json, :jsonb
288
+ return :text
289
+ when :citext, :inet
290
+ return :string
287
291
  end
288
292
 
289
293
  # Try look for hints in options hash. Quite common senario: Enum keys stored as string in the database.
@@ -298,16 +302,27 @@ module Formtastic
298
302
  end
299
303
 
300
304
  # Get a column object for a specified attribute method - if possible.
301
- def column_for(method) #:nodoc:
302
- @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
305
+ # @return [ActiveModel::Type::Value, #type] in case of rails 5 attributes api
306
+ # @return [ActiveRecord::ConnectionAdapters::Column] in case of rails 4
307
+ def column_for(method) # @private
308
+ case
309
+ when @object.class.respond_to?(:type_for_attribute)
310
+ @object.class.type_for_attribute(method.to_s)
311
+ when @object.class.respond_to?(:column_for_attribute)
312
+ @object.class.column_for_attribute(method)
313
+ when @object.respond_to?(:column_for_attribute)
314
+ @object.column_for_attribute(method)
315
+ else nil
316
+ end
303
317
  end
304
318
 
305
- # Takes the `:as` option and attempts to return the corresponding input class. In the case of
306
- # `:as => :string` it will first attempt to find a top level `StringInput` class (to allow the
307
- # application to subclass and modify to suit), falling back to `Formtastic::Inputs::StringInput`.
319
+ # Takes the `:as` option and attempts to return the corresponding input
320
+ # class. In the case of `:as => :awesome` it will first attempt to find a
321
+ # top level `AwesomeInput` class (to allow the application to subclass
322
+ # and modify to suit), falling back to `Formtastic::Inputs::AwesomeInput`.
308
323
  #
309
- # This also means that the application can define it's own custom inputs in the top level
310
- # namespace (eg `DatepickerInput`).
324
+ # Custom input namespaces to look into can be configured via the
325
+ # {Formtastic::FormBuilder.input_namespaces} configuration setting.
311
326
  #
312
327
  # @param [Symbol] as A symbol representing the type of input to render
313
328
  # @raise [Formtastic::UnknownInputError] An appropriate input class could not be found
@@ -319,50 +334,14 @@ module Formtastic
319
334
  #
320
335
  # @example When a top-level class is found
321
336
  # input_class(:string) #=> StringInput
322
- # input_class(:awesome) #=> AwesomeInput
323
- def input_class(as)
324
- @input_classes_cache ||= {}
325
- @input_classes_cache[as] ||= begin
326
- config = Rails.application.config
327
- use_const_defined = config.respond_to?(:eager_load) ? config.eager_load : config.cache_classes
328
- use_const_defined ? input_class_with_const_defined(as) : input_class_by_trying(as)
329
- end
330
- end
331
-
332
- # prevent exceptions in production environment for better performance
333
- def input_class_with_const_defined(as)
334
- input_class_name = custom_input_class_name(as)
335
-
336
- if ::Object.const_defined?(input_class_name)
337
- input_class_name.constantize
338
- elsif Formtastic::Inputs.const_defined?(input_class_name)
339
- standard_input_class_name(as).constantize
340
- else
341
- raise Formtastic::UnknownInputError, "Unable to find input class #{input_class_name}"
342
- end
337
+ # input_class(:awesome) #=> AwesomeInput
338
+ # @see NamespacedClassFinder#find
339
+ def namespaced_input_class(as)
340
+ @input_class_finder ||= input_class_finder.new(self)
341
+ @input_class_finder.find(as)
342
+ rescue Formtastic::InputClassFinder::NotFoundError
343
+ raise Formtastic::UnknownInputError, "Unable to find input #{$!.message}"
343
344
  end
344
-
345
- # use auto-loading in development environment
346
- def input_class_by_trying(as)
347
- begin
348
- custom_input_class_name(as).constantize
349
- rescue NameError
350
- standard_input_class_name(as).constantize
351
- end
352
- rescue NameError
353
- raise Formtastic::UnknownInputError, "Unable to find input class for #{as}"
354
- end
355
-
356
- # :as => :string # => StringInput
357
- def custom_input_class_name(as)
358
- "#{as.to_s.camelize}Input"
359
- end
360
-
361
- # :as => :string # => Formtastic::Inputs::StringInput
362
- def standard_input_class_name(as)
363
- "Formtastic::Inputs::#{as.to_s.camelize}Input"
364
- end
365
-
366
345
  end
367
346
  end
368
347
  end