formtastic 2.1.0 → 4.0.0

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