formtastic 2.1.0 → 4.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 (164) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +1 -0
  3. data/.github/workflows/test.yml +61 -0
  4. data/.gitignore +4 -2
  5. data/CHANGELOG.md +52 -0
  6. data/Gemfile +1 -1
  7. data/Gemfile.lock +105 -0
  8. data/MIT-LICENSE +1 -1
  9. data/{README.textile → README.md} +204 -219
  10. data/RELEASE_PROCESS +3 -1
  11. data/Rakefile +27 -29
  12. data/app/assets/stylesheets/formtastic.css +3 -2
  13. data/bin/appraisal +8 -0
  14. data/formtastic.gemspec +11 -14
  15. data/gemfiles/rails_5.2/Gemfile +5 -0
  16. data/gemfiles/rails_6.0/Gemfile +5 -0
  17. data/gemfiles/rails_6.1/Gemfile +5 -0
  18. data/gemfiles/rails_edge/Gemfile +13 -0
  19. data/lib/formtastic/action_class_finder.rb +18 -0
  20. data/lib/formtastic/actions/button_action.rb +55 -60
  21. data/lib/formtastic/actions/input_action.rb +59 -57
  22. data/lib/formtastic/actions/link_action.rb +68 -67
  23. data/lib/formtastic/actions.rb +6 -3
  24. data/lib/formtastic/deprecation.rb +5 -0
  25. data/lib/formtastic/engine.rb +3 -1
  26. data/lib/formtastic/form_builder.rb +35 -16
  27. data/lib/formtastic/helpers/action_helper.rb +34 -28
  28. data/lib/formtastic/helpers/enum.rb +13 -0
  29. data/lib/formtastic/helpers/errors_helper.rb +2 -2
  30. data/lib/formtastic/helpers/fieldset_wrapper.rb +16 -12
  31. data/lib/formtastic/helpers/form_helper.rb +19 -16
  32. data/lib/formtastic/helpers/input_helper.rb +69 -97
  33. data/lib/formtastic/helpers/inputs_helper.rb +35 -25
  34. data/lib/formtastic/helpers/reflection.rb +4 -4
  35. data/lib/formtastic/helpers.rb +1 -2
  36. data/lib/formtastic/html_attributes.rb +12 -1
  37. data/lib/formtastic/i18n.rb +1 -1
  38. data/lib/formtastic/input_class_finder.rb +18 -0
  39. data/lib/formtastic/inputs/base/choices.rb +2 -2
  40. data/lib/formtastic/inputs/base/collections.rb +46 -14
  41. data/lib/formtastic/inputs/base/database.rb +7 -2
  42. data/lib/formtastic/inputs/base/datetime_pickerish.rb +85 -0
  43. data/lib/formtastic/inputs/base/errors.rb +7 -7
  44. data/lib/formtastic/inputs/base/hints.rb +2 -2
  45. data/lib/formtastic/inputs/base/html.rb +10 -9
  46. data/lib/formtastic/inputs/base/labelling.rb +5 -8
  47. data/lib/formtastic/inputs/base/naming.rb +4 -4
  48. data/lib/formtastic/inputs/base/numeric.rb +1 -1
  49. data/lib/formtastic/inputs/base/options.rb +3 -4
  50. data/lib/formtastic/inputs/base/stringish.rb +10 -2
  51. data/lib/formtastic/inputs/base/timeish.rb +34 -22
  52. data/lib/formtastic/inputs/base/validations.rb +41 -13
  53. data/lib/formtastic/inputs/base/wrapping.rb +29 -26
  54. data/lib/formtastic/inputs/base.rb +22 -15
  55. data/lib/formtastic/inputs/boolean_input.rb +26 -12
  56. data/lib/formtastic/inputs/check_boxes_input.rb +39 -31
  57. data/lib/formtastic/inputs/color_input.rb +41 -0
  58. data/lib/formtastic/inputs/country_input.rb +24 -5
  59. data/lib/formtastic/inputs/datalist_input.rb +41 -0
  60. data/lib/formtastic/inputs/date_picker_input.rb +93 -0
  61. data/lib/formtastic/inputs/{date_input.rb → date_select_input.rb} +1 -1
  62. data/lib/formtastic/inputs/datetime_picker_input.rb +103 -0
  63. data/lib/formtastic/inputs/{datetime_input.rb → datetime_select_input.rb} +1 -1
  64. data/lib/formtastic/inputs/file_input.rb +2 -2
  65. data/lib/formtastic/inputs/hidden_input.rb +2 -6
  66. data/lib/formtastic/inputs/radio_input.rb +28 -22
  67. data/lib/formtastic/inputs/select_input.rb +36 -39
  68. data/lib/formtastic/inputs/time_picker_input.rb +99 -0
  69. data/lib/formtastic/inputs/{time_input.rb → time_select_input.rb} +6 -2
  70. data/lib/formtastic/inputs/time_zone_input.rb +16 -6
  71. data/lib/formtastic/inputs.rb +32 -21
  72. data/lib/formtastic/localized_string.rb +1 -1
  73. data/lib/formtastic/localizer.rb +24 -24
  74. data/lib/formtastic/namespaced_class_finder.rb +99 -0
  75. data/lib/formtastic/version.rb +1 -1
  76. data/lib/formtastic.rb +20 -10
  77. data/lib/generators/formtastic/form/form_generator.rb +10 -4
  78. data/lib/generators/formtastic/input/input_generator.rb +46 -0
  79. data/lib/generators/formtastic/install/install_generator.rb +5 -19
  80. data/lib/generators/templates/_form.html.slim +2 -2
  81. data/lib/generators/templates/formtastic.rb +46 -25
  82. data/lib/generators/templates/input.rb +19 -0
  83. data/sample/basic_inputs.html +23 -3
  84. data/script/integration-template.rb +74 -0
  85. data/script/integration.sh +19 -0
  86. data/spec/action_class_finder_spec.rb +12 -0
  87. data/spec/actions/button_action_spec.rb +8 -8
  88. data/spec/actions/generic_action_spec.rb +92 -56
  89. data/spec/actions/input_action_spec.rb +7 -7
  90. data/spec/actions/link_action_spec.rb +10 -10
  91. data/spec/builder/custom_builder_spec.rb +36 -20
  92. data/spec/builder/error_proc_spec.rb +4 -4
  93. data/spec/builder/semantic_fields_for_spec.rb +28 -29
  94. data/spec/fast_spec_helper.rb +12 -0
  95. data/spec/generators/formtastic/form/form_generator_spec.rb +45 -32
  96. data/spec/generators/formtastic/input/input_generator_spec.rb +124 -0
  97. data/spec/generators/formtastic/install/install_generator_spec.rb +9 -9
  98. data/spec/helpers/action_helper_spec.rb +75 -103
  99. data/spec/helpers/actions_helper_spec.rb +17 -17
  100. data/spec/helpers/form_helper_spec.rb +84 -33
  101. data/spec/helpers/input_helper_spec.rb +333 -285
  102. data/spec/helpers/inputs_helper_spec.rb +167 -121
  103. data/spec/helpers/reflection_helper_spec.rb +3 -3
  104. data/spec/helpers/semantic_errors_helper_spec.rb +23 -23
  105. data/spec/i18n_spec.rb +26 -26
  106. data/spec/input_class_finder_spec.rb +10 -0
  107. data/spec/inputs/base/collections_spec.rb +76 -0
  108. data/spec/inputs/base/validations_spec.rb +480 -0
  109. data/spec/inputs/boolean_input_spec.rb +100 -65
  110. data/spec/inputs/check_boxes_input_spec.rb +200 -101
  111. data/spec/inputs/color_input_spec.rb +85 -0
  112. data/spec/inputs/country_input_spec.rb +20 -20
  113. data/spec/inputs/custom_input_spec.rb +3 -4
  114. data/spec/inputs/datalist_input_spec.rb +61 -0
  115. data/spec/inputs/date_picker_input_spec.rb +449 -0
  116. data/spec/inputs/date_select_input_spec.rb +249 -0
  117. data/spec/inputs/datetime_picker_input_spec.rb +490 -0
  118. data/spec/inputs/datetime_select_input_spec.rb +209 -0
  119. data/spec/inputs/email_input_spec.rb +5 -5
  120. data/spec/inputs/file_input_spec.rb +6 -6
  121. data/spec/inputs/hidden_input_spec.rb +22 -35
  122. data/spec/inputs/include_blank_spec.rb +11 -11
  123. data/spec/inputs/label_spec.rb +62 -25
  124. data/spec/inputs/number_input_spec.rb +112 -112
  125. data/spec/inputs/password_input_spec.rb +5 -5
  126. data/spec/inputs/phone_input_spec.rb +5 -5
  127. data/spec/inputs/placeholder_spec.rb +6 -6
  128. data/spec/inputs/radio_input_spec.rb +99 -55
  129. data/spec/inputs/range_input_spec.rb +66 -66
  130. data/spec/inputs/readonly_spec.rb +50 -0
  131. data/spec/inputs/search_input_spec.rb +5 -5
  132. data/spec/inputs/select_input_spec.rb +170 -170
  133. data/spec/inputs/string_input_spec.rb +68 -16
  134. data/spec/inputs/text_input_spec.rb +16 -16
  135. data/spec/inputs/time_picker_input_spec.rb +455 -0
  136. data/spec/inputs/time_select_input_spec.rb +261 -0
  137. data/spec/inputs/time_zone_input_spec.rb +54 -28
  138. data/spec/inputs/url_input_spec.rb +5 -5
  139. data/spec/inputs/with_options_spec.rb +7 -7
  140. data/spec/localizer_spec.rb +39 -17
  141. data/spec/namespaced_class_finder_spec.rb +79 -0
  142. data/spec/schema.rb +21 -0
  143. data/spec/spec_helper.rb +254 -221
  144. data/spec/support/custom_macros.rb +128 -95
  145. data/spec/support/shared_examples.rb +12 -0
  146. data/spec/support/specialized_class_finder_shared_example.rb +27 -0
  147. data/spec/support/test_environment.rb +26 -10
  148. metadata +177 -238
  149. data/.travis.yml +0 -8
  150. data/Appraisals +0 -11
  151. data/CHANGELOG +0 -371
  152. data/gemfiles/rails-3.0.gemfile +0 -7
  153. data/gemfiles/rails-3.1.gemfile +0 -7
  154. data/gemfiles/rails-3.2.gemfile +0 -7
  155. data/lib/formtastic/helpers/buttons_helper.rb +0 -310
  156. data/lib/formtastic/inputs/base/grouped_collections.rb +0 -77
  157. data/lib/formtastic/util.rb +0 -25
  158. data/lib/tasks/verify_rcov.rb +0 -44
  159. data/spec/helpers/buttons_helper_spec.rb +0 -166
  160. data/spec/helpers/commit_button_helper_spec.rb +0 -530
  161. data/spec/inputs/date_input_spec.rb +0 -227
  162. data/spec/inputs/datetime_input_spec.rb +0 -185
  163. data/spec/inputs/time_input_spec.rb +0 -267
  164. data/spec/support/deferred_garbage_collection.rb +0 -21
@@ -47,10 +47,6 @@ module Formtastic
47
47
  include Formtastic::Helpers::FieldsetWrapper
48
48
  include Formtastic::LocalizedString
49
49
 
50
- # Which columns to skip when automatically rendering a form without any fields specified.
51
- SKIPPED_COLUMNS = [:created_at, :updated_at, :created_on, :updated_on, :lock_version, :version]
52
-
53
-
54
50
  # {#inputs} creates an input fieldset and ol tag wrapping for use around a set of inputs. It can be
55
51
  # called either with a block (in which you can do the usual Rails form stuff, HTML, ERB, etc),
56
52
  # or with a list of fields (accepting all default arguments and options). These two examples
@@ -136,7 +132,7 @@ module Formtastic
136
132
  # <% end %>
137
133
  # <% end %>
138
134
  #
139
- # {#inputs} also provides a DSL similar to `fields_for` / `semantic_fields_for` to reduce the
135
+ # {#inputs} also provides a DSL similar to `fields_for` / `semantic_fields_for` to reduce the
140
136
  # lines of code a little:
141
137
  #
142
138
  # <% semantic_form_for @user do |f| %>
@@ -162,7 +158,7 @@ module Formtastic
162
158
  # All options except `:name`, `:title` and `:for` will be passed down to the fieldset as HTML
163
159
  # attributes (id, class, style, etc).
164
160
  #
165
- # When nesting `inputs()` inside another `inputs()` block, the nested content will
161
+ # When nesting `inputs()` inside another `inputs()` block, the nested content will
166
162
  # automatically be wrapped in an `<li>` tag to preserve the HTML validity (a `<fieldset>`
167
163
  # cannot be a direct descendant of an `<ol>`.
168
164
  #
@@ -182,9 +178,13 @@ module Formtastic
182
178
  # <%= f.inputs %>
183
179
  # <% end %>
184
180
  #
181
+ # @example Quick form: Skip one or more fields
182
+ # <%= f.inputs :except => [:featured, :something_for_admin_only] %>
183
+ # <%= f.inputs :except => :featured %>
184
+ #
185
185
  # @example Short hand: Render inputs for a named set of attributes and simple associations on the model, with all default arguments and options
186
186
  # <% semantic_form_for @post do |form| %>
187
- # <%= f.inputs, :title, :body, :user, :categories %>
187
+ # <%= f.inputs :title, :body, :user, :categories %>
188
188
  # <% end %>
189
189
  #
190
190
  # @example Block: Render inputs for attributes and simple associations with full control over arguments and options
@@ -215,7 +215,7 @@ module Formtastic
215
215
  # <%= f.input :title ... %>
216
216
  # <%= f.input :body ... %>
217
217
  # <% end %>
218
- # <%= f.inputs do :name => 'Advanced options:' do %>
218
+ # <%= f.inputs :name => 'Advanced options:' do %>
219
219
  # <%= f.input :user ... %>
220
220
  # <%= f.input :categories ... %>
221
221
  # <% end %>
@@ -279,11 +279,12 @@ module Formtastic
279
279
  def inputs(*args, &block)
280
280
  wrap_it = @already_in_an_inputs_block ? true : false
281
281
  @already_in_an_inputs_block = true
282
-
282
+
283
283
  title = field_set_title_from_args(*args)
284
284
  html_options = args.extract_options!
285
285
  html_options[:class] ||= "inputs"
286
286
  html_options[:name] = title
287
+ skipped_args = Array.wrap html_options.delete(:except)
287
288
 
288
289
  out = begin
289
290
  if html_options[:for] # Nested form
@@ -292,27 +293,27 @@ module Formtastic
292
293
  field_set_and_list_wrapping(*(args << html_options), &block)
293
294
  else
294
295
  legend = args.shift if args.first.is_a?(::String)
295
- args = default_columns_for_object if @object && args.empty?
296
+ args = default_columns_for_object - skipped_args if @object && args.empty?
296
297
  contents = fieldset_contents_from_column_list(args)
297
298
  args.unshift(legend) if legend.present?
298
299
  field_set_and_list_wrapping(*((args << html_options) << contents))
299
300
  end
300
301
  end
301
-
302
+
302
303
  out = template.content_tag(:li, out, :class => "input") if wrap_it
303
304
  @already_in_an_inputs_block = wrap_it
304
305
  out
305
306
  end
306
307
 
307
308
  protected
308
-
309
+
309
310
  def default_columns_for_object
310
311
  cols = association_columns(:belongs_to)
311
312
  cols += content_columns
312
- cols -= SKIPPED_COLUMNS
313
+ cols -= skipped_columns
313
314
  cols.compact
314
315
  end
315
-
316
+
316
317
  def fieldset_contents_from_column_list(columns)
317
318
  columns.collect do |method|
318
319
  if @object
@@ -323,22 +324,22 @@ module Formtastic
323
324
  elsif @object.class.respond_to?(:associations)
324
325
  if (@object.class.associations[method.to_sym] && @object.class.associations[method.to_sym].options[:polymorphic] == true)
325
326
  raise PolymorphicInputWithoutCollectionError.new("Please provide a collection for :#{method} input (you'll need to use block form syntax). Inputs for polymorphic associations can only be used when an explicit :collection is provided.")
326
- end
327
- end
327
+ end
328
+ end
328
329
  end
329
330
  input(method.to_sym)
330
331
  end
331
332
  end
332
-
333
+
333
334
  # Collects association columns (relation columns) for the current form object class. Skips
334
335
  # polymorphic associations because we can't guess which class to use for an automatically
335
336
  # generated input.
336
- def association_columns(*by_associations) #:nodoc:
337
+ def association_columns(*by_associations) # @private
337
338
  if @object.present? && @object.class.respond_to?(:reflections)
338
339
  @object.class.reflections.collect do |name, association_reflection|
339
340
  if by_associations.present?
340
341
  if by_associations.include?(association_reflection.macro) && association_reflection.options[:polymorphic] != true
341
- name
342
+ name
342
343
  end
343
344
  else
344
345
  name
@@ -349,12 +350,21 @@ module Formtastic
349
350
  end
350
351
  end
351
352
 
353
+ # Collects all foreign key columns
354
+ def foreign_key_columns # @private
355
+ if @object.present? && @object.class.respond_to?(:reflect_on_all_associations)
356
+ @object.class.reflect_on_all_associations(:belongs_to).map{ |reflection| reflection.foreign_key.to_sym }
357
+ else
358
+ []
359
+ end
360
+ end
361
+
352
362
  # Collects content columns (non-relation columns) for the current form object class.
353
- def content_columns #:nodoc:
363
+ def content_columns # @private
354
364
  # TODO: NameError is raised by Inflector.constantize. Consider checking if it exists instead.
355
365
  begin klass = model_name.constantize; rescue NameError; return [] end
356
366
  return [] unless klass.respond_to?(:content_columns)
357
- klass.content_columns.collect { |c| c.name.to_sym }.compact
367
+ klass.content_columns.collect { |c| c.name.to_sym }.compact - foreign_key_columns
358
368
  end
359
369
 
360
370
  # Deals with :for option when it's supplied to inputs methods. Additional
@@ -362,7 +372,7 @@ module Formtastic
362
372
  # key.
363
373
  #
364
374
  # It should raise an error if a block with arity zero is given.
365
- def inputs_for_nested_attributes(*args, &block) #:nodoc:
375
+ def inputs_for_nested_attributes(*args, &block) # @private
366
376
  options = args.extract_options!
367
377
  args << options.merge!(:parent => { :builder => self, :for => options[:for] })
368
378
 
@@ -372,10 +382,10 @@ module Formtastic
372
382
  lambda do |f|
373
383
  contents = f.inputs(*args) do
374
384
  if block.arity == 1 # for backwards compatibility with REE & Ruby 1.8.x
375
- block.call(f)
385
+ yield(f)
376
386
  else
377
387
  index = parent_child_index(options[:parent]) if options[:parent]
378
- block.call(f, index)
388
+ yield(f, index)
379
389
  end
380
390
  end
381
391
  template.concat(contents)
@@ -391,7 +401,7 @@ module Formtastic
391
401
  fields_for(*fields_for_args, &fields_for_block)
392
402
  end
393
403
 
394
- def field_set_title_from_args(*args) #:nodoc:
404
+ def field_set_title_from_args(*args) # @private
395
405
  options = args.extract_options!
396
406
  options[:name] ||= options.delete(:title)
397
407
  title = options[:name]
@@ -4,20 +4,20 @@ module Formtastic
4
4
  module Reflection
5
5
  # If an association method is passed in (f.input :author) try to find the
6
6
  # reflection object.
7
- def reflection_for(method) #:nodoc:
7
+ def reflection_for(method) # @private
8
8
  if @object.class.respond_to?(:reflect_on_association)
9
- @object.class.reflect_on_association(method)
9
+ @object.class.reflect_on_association(method)
10
10
  elsif @object.class.respond_to?(:associations) # MongoMapper uses the 'associations(method)' instead
11
11
  @object.class.associations[method]
12
12
  end
13
13
  end
14
14
 
15
- def association_macro_for_method(method) #:nodoc:
15
+ def association_macro_for_method(method) # @private
16
16
  reflection = reflection_for(method)
17
17
  reflection.macro if reflection
18
18
  end
19
19
 
20
- def association_primary_key_for_method(method) #:nodoc:
20
+ def association_primary_key_for_method(method) # @private
21
21
  reflection = reflection_for(method)
22
22
  if reflection
23
23
  case association_macro_for_method(method)
@@ -1,5 +1,4 @@
1
1
  module Formtastic
2
- # @private
3
2
  module Helpers
4
3
  autoload :ButtonsHelper, 'formtastic/helpers/buttons_helper'
5
4
  autoload :ActionHelper, 'formtastic/helpers/action_helper'
@@ -10,8 +9,8 @@ module Formtastic
10
9
  autoload :FormHelper, 'formtastic/helpers/form_helper'
11
10
  autoload :InputHelper, 'formtastic/helpers/input_helper'
12
11
  autoload :InputsHelper, 'formtastic/helpers/inputs_helper'
13
- autoload :LabelHelper, 'formtastic/helpers/label_helper'
14
12
  autoload :Reflection, 'formtastic/helpers/reflection'
13
+ autoload :Enum, 'formtastic/helpers/enum'
15
14
  end
16
15
  end
17
16
 
@@ -1,6 +1,17 @@
1
1
  module Formtastic
2
2
  # @private
3
3
  module HtmlAttributes
4
+ # Returns a namespace passed by option or inherited from parent builders / class configuration
5
+ def dom_id_namespace
6
+ namespace = options[:custom_namespace]
7
+ parent = options[:parent_builder]
8
+
9
+ case
10
+ when namespace then namespace
11
+ when parent && parent != self then parent.dom_id_namespace
12
+ else custom_namespace
13
+ end
14
+ end
4
15
 
5
16
  protected
6
17
 
@@ -18,4 +29,4 @@ module Formtastic
18
29
  end
19
30
 
20
31
  end
21
- end
32
+ end
@@ -23,7 +23,7 @@ module Formtastic
23
23
  options = args.extract_options!
24
24
  options.reverse_merge!(:default => DEFAULT_VALUES[key])
25
25
  options[:scope] = [DEFAULT_SCOPE, options[:scope]].flatten.compact
26
- ::I18n.translate(key, *(args << options))
26
+ ::I18n.translate(key, *args, **options)
27
27
  end
28
28
  alias :t :translate
29
29
 
@@ -0,0 +1,18 @@
1
+ module Formtastic
2
+
3
+ # Uses the {Formtastic::NamespacedClassFinder} to look up input class names.
4
+ #
5
+ # See {Formtastic::FormBuilder#namespaced_input_class} for details.
6
+ #
7
+ class InputClassFinder < NamespacedClassFinder
8
+
9
+ # @param builder [FormBuilder]
10
+ def initialize(builder)
11
+ super builder.input_namespaces
12
+ end
13
+
14
+ def class_name(as)
15
+ "#{super}Input"
16
+ end
17
+ end
18
+ end
@@ -64,7 +64,7 @@ module Formtastic
64
64
  end
65
65
 
66
66
  def custom_choice_html_options(choice)
67
- (choice.is_a?(Array) && choice.size > 2) ? choice.last : {}
67
+ (choice.is_a?(Array) && choice.size > 2) ? choice[-1] : {}
68
68
  end
69
69
 
70
70
  def choice_html_safe_value(choice)
@@ -73,7 +73,7 @@ module Formtastic
73
73
 
74
74
  def choice_input_dom_id(choice)
75
75
  [
76
- builder.custom_namespace,
76
+ builder.dom_id_namespace,
77
77
  sanitized_object_name,
78
78
  builder.options[:index],
79
79
  association_primary_key || method,
@@ -12,7 +12,7 @@ module Formtastic
12
12
  end
13
13
 
14
14
  def value_method
15
- @value_method ||= (value_method_from_options || label_and_value_method.last)
15
+ @value_method ||= (value_method_from_options || label_and_value_method[-1])
16
16
  end
17
17
 
18
18
  def value_method_from_options
@@ -24,7 +24,7 @@ module Formtastic
24
24
  end
25
25
 
26
26
  def label_and_value_method_from_collection(_collection)
27
- sample = _collection.first || _collection.last
27
+ sample = _collection.first || _collection[-1]
28
28
 
29
29
  case sample
30
30
  when Array
@@ -43,16 +43,16 @@ module Formtastic
43
43
  end
44
44
 
45
45
  def raw_collection
46
- @raw_collection ||= (collection_from_options || collection_from_association || collection_for_boolean)
46
+ @raw_collection ||= (collection_from_options || collection_from_enum || collection_from_association || collection_for_boolean)
47
47
  end
48
48
 
49
49
  def collection
50
50
  # Return if we have a plain string
51
- return raw_collection if raw_collection.instance_of?(String) || raw_collection.instance_of?(ActiveSupport::SafeBuffer)
51
+ return raw_collection if raw_collection.is_a?(String)
52
52
 
53
- # Return if we have an Array of strings, fixnums or arrays
53
+ # Return if we have an Array of strings, integers or arrays
54
54
  return raw_collection if (raw_collection.instance_of?(Array) || raw_collection.instance_of?(Range)) &&
55
- [Array, Fixnum, String].include?(raw_collection.first.class) &&
55
+ ([Array, String].include?(raw_collection.first.class) || raw_collection.first.is_a?(Integer)) &&
56
56
  !(options.include?(:member_label) || options.include?(:member_value))
57
57
 
58
58
  raw_collection.map { |o| [send_or_call(label_method, o), send_or_call(value_method, o)] }
@@ -78,21 +78,53 @@ module Formtastic
78
78
  ) if reflection.options[:polymorphic] == true
79
79
  end
80
80
 
81
- find_options_from_options = options[:find_options] || {}
82
- conditions_from_options = find_options_from_options[:conditions] || {}
83
81
  conditions_from_reflection = (reflection.respond_to?(:options) && reflection.options[:conditions]) || {}
84
82
  conditions_from_reflection = conditions_from_reflection.call if conditions_from_reflection.is_a?(Proc)
85
83
 
86
84
  scope_conditions = conditions_from_reflection.empty? ? nil : {:conditions => conditions_from_reflection}
87
- if conditions_from_options.any?
88
- reflection.klass.scoped(scope_conditions).where(conditions_from_options)
89
- else
90
- find_options_from_options.merge!(:include => group_by) if self.respond_to?(:group_by) && group_by
91
- reflection.klass.scoped(scope_conditions).where(find_options_from_options)
85
+ where_conditions = (scope_conditions && scope_conditions[:conditions]) || {}
86
+
87
+ reflection.klass.where(where_conditions)
88
+ end
89
+ end
90
+
91
+ # Assuming the following model:
92
+ #
93
+ # class Post < ActiveRecord::Base
94
+ # enum :status => [ :active, :archived ]
95
+ # end
96
+ #
97
+ # We would end up with a collection like this:
98
+ #
99
+ # [["Active", "active"], ["Archived", "archived"]
100
+ #
101
+ # The first element in each array uses String#humanize, but I18n
102
+ # translations are available too. Set them with the following structure.
103
+ #
104
+ # en:
105
+ # activerecord:
106
+ # attributes:
107
+ # post:
108
+ # statuses:
109
+ # active: Custom Active Label Here
110
+ # archived: Custom Archived Label Here
111
+ def collection_from_enum
112
+ if collection_from_enum?
113
+ method_name = method.to_s
114
+
115
+ enum_options_hash = object.defined_enums[method_name]
116
+ enum_options_hash.map do |name, value|
117
+ key = "activerecord.attributes.#{object_name}.#{method_name.pluralize}.#{name}"
118
+ label = ::I18n.translate(key, :default => name.humanize)
119
+ [label, name]
92
120
  end
93
121
  end
94
122
  end
95
123
 
124
+ def collection_from_enum?
125
+ object.respond_to?(:defined_enums) && object.defined_enums.has_key?(method.to_s)
126
+ end
127
+
96
128
  def collection_for_boolean
97
129
  true_text = options[:true] || Formtastic::I18n.t(:yes)
98
130
  false_text = options[:false] || Formtastic::I18n.t(:no)
@@ -113,7 +145,7 @@ module Formtastic
113
145
  # Avoids an issue where `send_or_call` can be a String and duck can be something simple like
114
146
  # `:first`, which obviously String responds to.
115
147
  def send_or_call_or_object(duck, object)
116
- return object if object.is_a?(String) # TODO what about other classes etc?
148
+ return object if object.is_a?(String) || object.is_a?(Integer) || object.is_a?(Symbol) # TODO what about other classes etc?
117
149
  send_or_call(duck, object)
118
150
  end
119
151
 
@@ -4,7 +4,12 @@ module Formtastic
4
4
  module Database
5
5
 
6
6
  def column
7
- object.column_for_attribute(method) if object.respond_to?(:column_for_attribute)
7
+ if object.respond_to?(:column_for_attribute)
8
+ # Remove deprecation wrapper & review after Rails 5.0 ships
9
+ ActiveSupport::Deprecation.silence do
10
+ object.column_for_attribute(method)
11
+ end
12
+ end
8
13
  end
9
14
 
10
15
  def column?
@@ -14,4 +19,4 @@ module Formtastic
14
19
  end
15
20
  end
16
21
  end
17
- end
22
+ end
@@ -0,0 +1,85 @@
1
+ module Formtastic
2
+ module Inputs
3
+ module Base
4
+ module DatetimePickerish
5
+ include Base::Placeholder
6
+
7
+ def html_input_type
8
+ raise NotImplementedError
9
+ end
10
+
11
+ def default_size
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def value
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def input_html_options
20
+ super.merge(extra_input_html_options)
21
+ end
22
+
23
+ def extra_input_html_options
24
+ {
25
+ :type => html_input_type,
26
+ :size => size,
27
+ :maxlength => maxlength,
28
+ :step => step,
29
+ :value => value
30
+ }
31
+ end
32
+
33
+ def size
34
+ return options[:size] if options.key?(:size)
35
+ return options[:input_html][:size] if options[:input_html] && options[:input_html].key?(:size)
36
+ default_size
37
+ end
38
+
39
+ def step
40
+ return step_from_macro(options[:input_html][:step]) if options[:input_html] && options[:input_html][:step] && options[:input_html][:step].is_a?(Symbol)
41
+ return options[:input_html][:step] if options[:input_html] && options[:input_html].key?(:step)
42
+ default_step
43
+ end
44
+
45
+ def maxlength
46
+ return options[:maxlength] if options.key?(:maxlength)
47
+ return options[:input_html][:maxlength] if options[:input_html] && options[:input_html].key?(:maxlength)
48
+ default_size
49
+ end
50
+
51
+ def default_maxlength
52
+ default_size
53
+ end
54
+
55
+ def default_step
56
+ 1
57
+ end
58
+
59
+ protected
60
+
61
+ def step_from_macro(sym)
62
+ case sym
63
+
64
+ # date
65
+ when :day then "1"
66
+ when :seven_days, :week then "7"
67
+ when :two_weeks, :fortnight then "14"
68
+ when :four_weeks then "28"
69
+ when :thirty_days then "30"
70
+
71
+ # time
72
+ when :second then "1"
73
+ when :minute then "60"
74
+ when :fifteen_minutes, :quarter_hour then "900"
75
+ when :thirty_minutes, :half_hour then "1800"
76
+ when :sixty_minutes, :hour then "3600"
77
+
78
+ else sym
79
+ end
80
+ end
81
+
82
+ end
83
+ end
84
+ end
85
+ end
@@ -8,22 +8,22 @@ module Formtastic
8
8
  end
9
9
 
10
10
  def error_sentence_html
11
- error_class = options[:error_class] || builder.default_inline_error_class
12
- template.content_tag(:p, Formtastic::Util.html_safe(errors.to_sentence.html_safe), :class => error_class)
11
+ error_class = builder.default_inline_error_class
12
+ template.content_tag(:p, errors.to_sentence, :class => error_class)
13
13
  end
14
14
 
15
15
  def error_list_html
16
- error_class = options[:error_class] || builder.default_error_list_class
16
+ error_class = builder.default_error_list_class
17
17
  list_elements = []
18
18
  errors.each do |error|
19
- list_elements << template.content_tag(:li, Formtastic::Util.html_safe(error.html_safe))
19
+ list_elements << template.content_tag(:li, error.html_safe)
20
20
  end
21
- template.content_tag(:ul, Formtastic::Util.html_safe(list_elements.join("\n")), :class => error_class)
21
+ template.content_tag(:ul, list_elements.join("\n").html_safe, :class => error_class)
22
22
  end
23
23
 
24
24
  def error_first_html
25
- error_class = options[:error_class] || builder.default_inline_error_class
26
- template.content_tag(:p, Formtastic::Util.html_safe(errors.first.untaint), :class => error_class)
25
+ error_class = builder.default_inline_error_class
26
+ template.content_tag(:p, errors.first.untaint.html_safe, :class => error_class)
27
27
  end
28
28
 
29
29
  def error_none_html
@@ -7,8 +7,8 @@ module Formtastic
7
7
  if hint?
8
8
  template.content_tag(
9
9
  :p,
10
- Formtastic::Util.html_safe(hint_text),
11
- :class => (options[:hint_class] || builder.default_hint_class)
10
+ hint_text.html_safe,
11
+ :class => builder.default_hint_class
12
12
  )
13
13
  end
14
14
  end
@@ -2,7 +2,7 @@ module Formtastic
2
2
  module Inputs
3
3
  module Base
4
4
  module Html
5
-
5
+
6
6
  # Defines how the instance of an input should be rendered to a HTML string.
7
7
  #
8
8
  # @abstract Implement this method in your input class to describe how the input should render itself.
@@ -17,24 +17,25 @@ module Formtastic
17
17
  def to_html
18
18
  raise NotImplementedError
19
19
  end
20
-
20
+
21
21
  def input_html_options
22
- {
22
+ {
23
23
  :id => dom_id,
24
24
  :required => required_attribute?,
25
- :autofocus => autofocus?
25
+ :autofocus => autofocus?,
26
+ :readonly => readonly?
26
27
  }.merge(options[:input_html] || {})
27
28
  end
28
-
29
+
29
30
  def dom_id
30
31
  [
31
- builder.custom_namespace,
32
- sanitized_object_name,
33
- dom_index,
32
+ builder.dom_id_namespace,
33
+ sanitized_object_name,
34
+ dom_index,
34
35
  association_primary_key || sanitized_method_name
35
36
  ].reject { |x| x.blank? }.join('_')
36
37
  end
37
-
38
+
38
39
  def dom_index
39
40
  if builder.options.has_key?(:index)
40
41
  builder.options[:index]
@@ -10,17 +10,14 @@ module Formtastic
10
10
  end
11
11
 
12
12
  def label_html_options
13
- # opts = options_for_label(options) # TODO
14
- opts = {}
15
- opts[:for] ||= input_html_options[:id]
16
- opts[:class] = [opts[:class]]
17
- opts[:class] << 'label'
18
-
19
- opts
13
+ {
14
+ :for => input_html_options[:id],
15
+ :class => ['label'],
16
+ }
20
17
  end
21
18
 
22
19
  def label_text
23
- ((localized_label || humanized_method_name) << requirement_text).html_safe
20
+ ((localized_label || humanized_method_name) + requirement_text).html_safe
24
21
  end
25
22
 
26
23
  # TODO: why does this need to be memoized in order to make the inputs_spec tests pass?
@@ -4,9 +4,9 @@ module Formtastic
4
4
  module Naming
5
5
 
6
6
  def as
7
- self.class.name.split("::").last.underscore.gsub(/_input$/, '')
7
+ self.class.name.split("::")[-1].underscore.gsub(/_input$/, '')
8
8
  end
9
-
9
+
10
10
  def sanitized_object_name
11
11
  object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
12
12
  end
@@ -18,7 +18,7 @@ module Formtastic
18
18
  def attributized_method_name
19
19
  method.to_s.gsub(/_id$/, '').to_sym
20
20
  end
21
-
21
+
22
22
  def humanized_method_name
23
23
  if builder.label_str_method != :humanize
24
24
  # Special case where label_str_method should trump the human_attribute_name
@@ -39,4 +39,4 @@ module Formtastic
39
39
  end
40
40
  end
41
41
  end
42
- end
42
+ end
@@ -5,7 +5,7 @@ module Formtastic
5
5
  def input_html_options
6
6
  defaults = super
7
7
 
8
- # override rails default size - not valid on numeric inputs
8
+ # override rails default size - does not apply to numeric inputs
9
9
  #@todo document/spec
10
10
  defaults[:size] = nil
11
11