formtastic 3.0.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
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