simple_form 3.0.1 → 3.1.0.rc1

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -27
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +146 -71
  5. data/lib/generators/simple_form/install_generator.rb +2 -2
  6. data/lib/generators/simple_form/templates/README +3 -4
  7. data/lib/generators/simple_form/templates/config/initializers/simple_form.rb +19 -3
  8. data/lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb +83 -22
  9. data/lib/generators/simple_form/templates/config/initializers/simple_form_foundation.rb +1 -1
  10. data/lib/generators/simple_form/templates/config/locales/simple_form.en.yml +7 -2
  11. data/lib/simple_form/action_view_extensions/form_helper.rb +1 -1
  12. data/lib/simple_form/components/errors.rb +30 -2
  13. data/lib/simple_form/components/hints.rb +2 -2
  14. data/lib/simple_form/components/html5.rb +1 -1
  15. data/lib/simple_form/components/label_input.rb +20 -2
  16. data/lib/simple_form/components/labels.rb +9 -5
  17. data/lib/simple_form/components/maxlength.rb +1 -1
  18. data/lib/simple_form/components/min_max.rb +1 -1
  19. data/lib/simple_form/components/pattern.rb +1 -1
  20. data/lib/simple_form/components/placeholders.rb +2 -2
  21. data/lib/simple_form/components/readonly.rb +1 -1
  22. data/lib/simple_form/form_builder.rb +92 -57
  23. data/lib/simple_form/helpers.rb +5 -5
  24. data/lib/simple_form/inputs/base.rb +33 -11
  25. data/lib/simple_form/inputs/block_input.rb +1 -1
  26. data/lib/simple_form/inputs/boolean_input.rb +23 -13
  27. data/lib/simple_form/inputs/collection_input.rb +32 -9
  28. data/lib/simple_form/inputs/collection_radio_buttons_input.rb +6 -11
  29. data/lib/simple_form/inputs/collection_select_input.rb +4 -2
  30. data/lib/simple_form/inputs/date_time_input.rb +12 -2
  31. data/lib/simple_form/inputs/file_input.rb +4 -2
  32. data/lib/simple_form/inputs/grouped_collection_select_input.rb +15 -3
  33. data/lib/simple_form/inputs/hidden_input.rb +4 -2
  34. data/lib/simple_form/inputs/numeric_input.rb +5 -4
  35. data/lib/simple_form/inputs/password_input.rb +4 -2
  36. data/lib/simple_form/inputs/priority_input.rb +4 -2
  37. data/lib/simple_form/inputs/range_input.rb +1 -1
  38. data/lib/simple_form/inputs/string_input.rb +4 -2
  39. data/lib/simple_form/inputs/text_input.rb +4 -2
  40. data/lib/simple_form/railtie.rb +7 -0
  41. data/lib/simple_form/tags.rb +7 -0
  42. data/lib/simple_form/version.rb +1 -1
  43. data/lib/simple_form/wrappers/builder.rb +5 -5
  44. data/lib/simple_form/wrappers/leaf.rb +28 -0
  45. data/lib/simple_form/wrappers/many.rb +5 -6
  46. data/lib/simple_form/wrappers/root.rb +1 -1
  47. data/lib/simple_form/wrappers/single.rb +5 -3
  48. data/lib/simple_form/wrappers.rb +1 -0
  49. data/lib/simple_form.rb +38 -6
  50. data/test/action_view_extensions/builder_test.rb +2 -2
  51. data/test/components/label_test.rb +1 -1
  52. data/test/form_builder/association_test.rb +17 -0
  53. data/test/form_builder/error_notification_test.rb +1 -1
  54. data/test/form_builder/error_test.rb +61 -0
  55. data/test/form_builder/input_field_test.rb +25 -1
  56. data/test/form_builder/label_test.rb +24 -1
  57. data/test/form_builder/wrapper_test.rb +67 -0
  58. data/test/generators/simple_form_generator_test.rb +2 -2
  59. data/test/inputs/boolean_input_test.rb +50 -2
  60. data/test/inputs/collection_check_boxes_input_test.rb +40 -11
  61. data/test/inputs/collection_radio_buttons_input_test.rb +76 -17
  62. data/test/inputs/collection_select_input_test.rb +108 -3
  63. data/test/inputs/datetime_input_test.rb +105 -38
  64. data/test/inputs/discovery_test.rb +12 -1
  65. data/test/inputs/grouped_collection_select_input_test.rb +36 -0
  66. data/test/inputs/string_input_test.rb +20 -0
  67. data/test/simple_form_test.rb +8 -0
  68. data/test/support/discovery_inputs.rb +12 -2
  69. data/test/support/misc_helpers.rb +49 -5
  70. data/test/support/models.rb +49 -24
  71. data/test/test_helper.rb +2 -0
  72. metadata +23 -34
@@ -1,45 +1,106 @@
1
1
  # Use this setup block to configure all options available in SimpleForm.
2
2
  SimpleForm.setup do |config|
3
- config.wrappers :bootstrap, tag: 'div', class: 'control-group', error_class: 'error' do |b|
3
+ config.button_class = 'btn btn-default'
4
+ config.boolean_label_class = nil
5
+
6
+ config.wrappers :vertical_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
4
7
  b.use :html5
5
8
  b.use :placeholder
6
- b.use :label
7
- b.wrapper tag: 'div', class: 'controls' do |ba|
9
+ b.use :label, class: 'control-label'
10
+
11
+ b.wrapper tag: 'div' do |ba|
12
+ ba.use :input, class: 'form-control'
13
+ ba.use :error, wrap_with: { tag: 'span', class: 'help-block' }
14
+ ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
15
+ end
16
+ end
17
+
18
+ config.wrappers :vertical_file_input, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
19
+ b.use :html5
20
+ b.use :placeholder
21
+ b.use :label, class: 'control-label'
22
+
23
+ b.wrapper tag: 'div' do |ba|
8
24
  ba.use :input
9
- ba.use :error, wrap_with: { tag: 'span', class: 'help-inline' }
25
+ ba.use :error, wrap_with: { tag: 'span', class: 'help-block' }
10
26
  ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
11
27
  end
12
28
  end
13
29
 
14
- config.wrappers :prepend, tag: 'div', class: "control-group", error_class: 'error' do |b|
30
+ config.wrappers :vertical_boolean, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
15
31
  b.use :html5
16
32
  b.use :placeholder
17
- b.use :label
18
- b.wrapper tag: 'div', class: 'controls' do |input|
19
- input.wrapper tag: 'div', class: 'input-prepend' do |prepend|
20
- prepend.use :input
21
- end
22
- input.use :hint, wrap_with: { tag: 'span', class: 'help-block' }
23
- input.use :error, wrap_with: { tag: 'span', class: 'help-inline' }
33
+
34
+ b.wrapper tag: 'div', class: 'checkbox' do |ba|
35
+ ba.use :label_input
36
+ end
37
+
38
+ b.use :error, wrap_with: { tag: 'span', class: 'help-block' }
39
+ b.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
40
+ end
41
+
42
+ config.wrappers :vertical_radio_and_checkboxes, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
43
+ b.use :html5
44
+ b.use :placeholder
45
+ b.use :label_input
46
+ b.use :error, wrap_with: { tag: 'span', class: 'help-block' }
47
+ b.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
48
+ end
49
+
50
+ config.wrappers :horizontal_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
51
+ b.use :html5
52
+ b.use :placeholder
53
+ b.use :label, class: 'col-sm-3 control-label'
54
+
55
+ b.wrapper tag: 'div', class: 'col-sm-9' do |ba|
56
+ ba.use :input, class: 'form-control'
57
+ ba.use :error, wrap_with: { tag: 'span', class: 'help-block' }
58
+ ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
24
59
  end
25
60
  end
26
61
 
27
- config.wrappers :append, tag: 'div', class: "control-group", error_class: 'error' do |b|
62
+ config.wrappers :horizontal_file_input, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
28
63
  b.use :html5
29
64
  b.use :placeholder
30
- b.use :label
31
- b.wrapper tag: 'div', class: 'controls' do |input|
32
- input.wrapper tag: 'div', class: 'input-append' do |append|
33
- append.use :input
65
+ b.use :label, class: 'col-sm-3 control-label'
66
+
67
+ b.wrapper tag: 'div', class: 'col-sm-9' do |ba|
68
+ ba.use :input
69
+ ba.use :error, wrap_with: { tag: 'span', class: 'help-block' }
70
+ ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
71
+ end
72
+ end
73
+
74
+ config.wrappers :horizontal_boolean, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
75
+ b.use :html5
76
+ b.use :placeholder
77
+
78
+ b.wrapper tag: 'div', class: 'col-sm-offset-3 col-sm-9' do |wr|
79
+ wr.wrapper tag: 'div', class: 'checkbox' do |ba|
80
+ ba.use :label_input, class: 'col-sm-9'
34
81
  end
35
- input.use :hint, wrap_with: { tag: 'span', class: 'help-block' }
36
- input.use :error, wrap_with: { tag: 'span', class: 'help-inline' }
82
+
83
+ wr.use :error, wrap_with: { tag: 'span', class: 'help-block' }
84
+ wr.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
85
+ end
86
+ end
87
+
88
+ config.wrappers :horizontal_radio_and_checkboxes, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
89
+ b.use :html5
90
+ b.use :placeholder
91
+
92
+ b.use :label, class: 'col-sm-3 control-label'
93
+
94
+ b.wrapper tag: 'div', class: 'col-sm-9' do |ba|
95
+ ba.use :input
96
+ ba.use :error, wrap_with: { tag: 'span', class: 'help-block' }
97
+ ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
37
98
  end
38
99
  end
39
100
 
40
- # Wrappers for forms and inputs using the Twitter Bootstrap toolkit.
41
- # Check the Bootstrap docs (http://twitter.github.com/bootstrap)
101
+ # Wrappers for forms and inputs using the Bootstrap toolkit.
102
+ # Check the Bootstrap docs (http://getbootstrap.com)
42
103
  # to learn about the different styles for forms and inputs,
43
104
  # buttons and other elements.
44
- config.default_wrapper = :bootstrap
105
+ config.default_wrapper = :vertical_form
45
106
  end
@@ -8,7 +8,7 @@ SimpleForm.setup do |config|
8
8
  b.optional :min_max
9
9
  b.optional :readonly
10
10
  b.use :label_input
11
- b.use :error, wrap_with: { tag: :small }
11
+ b.use :error, wrap_with: { tag: :small, class: :error }
12
12
 
13
13
  # Uncomment the following line to enable hints. The line is commented out by default since Foundation
14
14
  # does't provide styles for hints. You will need to provide your own CSS styles for hints.
@@ -10,7 +10,7 @@ en:
10
10
  # html: '<abbr title="required">*</abbr>'
11
11
  error_notification:
12
12
  default_message: "Please review the problems below:"
13
- # Labels and hints examples
13
+ # Examples
14
14
  # labels:
15
15
  # defaults:
16
16
  # password: 'Password'
@@ -23,4 +23,9 @@ en:
23
23
  # defaults:
24
24
  # username: 'User name to sign in.'
25
25
  # password: 'No special characters, please.'
26
-
26
+ # include_blanks:
27
+ # defaults:
28
+ # age: 'Rather not say'
29
+ # prompts:
30
+ # defaults:
31
+ # age: 'Select your age'
@@ -10,7 +10,7 @@ module SimpleForm
10
10
  #
11
11
  module FormHelper
12
12
 
13
- def simple_form_for(record, options={}, &block)
13
+ def simple_form_for(record, options = {}, &block)
14
14
  options[:builder] ||= SimpleForm::FormBuilder
15
15
  options[:html] ||= {}
16
16
  unless options[:html].key?(:novalidate)
@@ -1,10 +1,14 @@
1
1
  module SimpleForm
2
2
  module Components
3
3
  module Errors
4
- def error
4
+ def error(wrapper_options = nil)
5
5
  error_text if has_errors?
6
6
  end
7
7
 
8
+ def full_error(wrapper_options = nil)
9
+ full_error_text if options[:error] != false && has_errors?
10
+ end
11
+
8
12
  def has_errors?
9
13
  object && object.respond_to?(:errors) && errors.present?
10
14
  end
@@ -12,7 +16,15 @@ module SimpleForm
12
16
  protected
13
17
 
14
18
  def error_text
15
- "#{html_escape(options[:error_prefix])} #{errors.send(error_method)}".lstrip.html_safe
19
+ text = has_custom_error? ? options[:error] : errors.send(error_method)
20
+
21
+ "#{html_escape(options[:error_prefix])} #{text}".lstrip.html_safe
22
+ end
23
+
24
+ def full_error_text
25
+ text = has_custom_error? ? options[:error] : full_errors.send(error_method)
26
+
27
+ text.html_safe
16
28
  end
17
29
 
18
30
  def error_method
@@ -23,13 +35,29 @@ module SimpleForm
23
35
  @errors ||= (errors_on_attribute + errors_on_association).compact
24
36
  end
25
37
 
38
+ def full_errors
39
+ @full_errors ||= (full_errors_on_attribute + full_errors_on_association).compact
40
+ end
41
+
26
42
  def errors_on_attribute
27
43
  object.errors[attribute_name]
28
44
  end
29
45
 
46
+ def full_errors_on_attribute
47
+ object.errors.full_messages_for(attribute_name)
48
+ end
49
+
30
50
  def errors_on_association
31
51
  reflection ? object.errors[reflection.name] : []
32
52
  end
53
+
54
+ def full_errors_on_association
55
+ reflection ? object.full_messages_for(reflection.name) : []
56
+ end
57
+
58
+ def has_custom_error?
59
+ options[:error].is_a?(String)
60
+ end
33
61
  end
34
62
  end
35
63
  end
@@ -2,14 +2,14 @@ module SimpleForm
2
2
  module Components
3
3
  # Needs to be enabled in order to do automatic lookups.
4
4
  module Hints
5
- def hint
5
+ def hint(wrapper_options = nil)
6
6
  @hint ||= begin
7
7
  hint = options[:hint]
8
8
 
9
9
  if hint.is_a?(String)
10
10
  html_escape(hint)
11
11
  else
12
- content = translate(:hints)
12
+ content = translate_from_namespace(:hints)
13
13
  content.html_safe if content
14
14
  end
15
15
  end
@@ -5,7 +5,7 @@ module SimpleForm
5
5
  @html5 = false
6
6
  end
7
7
 
8
- def html5
8
+ def html5(wrapper_options = nil)
9
9
  @html5 = true
10
10
  if has_required?
11
11
  input_html_options[:required] = true
@@ -7,8 +7,26 @@ module SimpleForm
7
7
  include SimpleForm::Components::Labels
8
8
  end
9
9
 
10
- def label_input
11
- options[:label] == false ? input : (label + input)
10
+ def label_input(wrapper_options = nil)
11
+ if options[:label] == false
12
+ deprecated_component(:input, wrapper_options)
13
+ else
14
+ deprecated_component(:label, wrapper_options) + deprecated_component(:input, wrapper_options)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def deprecated_component(namespace, wrapper_options)
21
+ method = method(namespace)
22
+
23
+ if method.arity == 0
24
+ ActiveSupport::Deprecation.warn(SimpleForm::CUSTOM_INPUT_DEPRECATION_WARN % { name: namespace })
25
+
26
+ method.call
27
+ else
28
+ method.call(wrapper_options)
29
+ end
12
30
  end
13
31
  end
14
32
  end
@@ -21,16 +21,19 @@ module SimpleForm
21
21
  end
22
22
  end
23
23
 
24
- def label
24
+ def label(wrapper_options = nil)
25
+ label_options = merge_wrapper_options(label_html_options, wrapper_options)
26
+
25
27
  if generate_label_for_attribute?
26
- @builder.label(label_target, label_text, label_html_options)
28
+ @builder.label(label_target, label_text, label_options)
27
29
  else
28
- template.label_tag(nil, label_text, label_html_options)
30
+ template.label_tag(nil, label_text, label_options)
29
31
  end
30
32
  end
31
33
 
32
34
  def label_text
33
- SimpleForm.label_text.call(html_escape(raw_label_text), required_label_text).strip.html_safe
35
+ label_text = options[:label_text] || SimpleForm.label_text
36
+ label_text.call(html_escape(raw_label_text), required_label_text, options[:label].present?).strip.html_safe
34
37
  end
35
38
 
36
39
  def label_target
@@ -46,6 +49,7 @@ module SimpleForm
46
49
  if options.key?(:input_html) && options[:input_html].key?(:id)
47
50
  label_options[:for] = options[:input_html][:id]
48
51
  end
52
+
49
53
  label_options
50
54
  end
51
55
 
@@ -62,7 +66,7 @@ module SimpleForm
62
66
 
63
67
  # First check labels translation and then human attribute name.
64
68
  def label_translation #:nodoc:
65
- if SimpleForm.translate_labels && (translated_label = translate(:labels))
69
+ if SimpleForm.translate_labels && (translated_label = translate_from_namespace(:labels))
66
70
  translated_label
67
71
  elsif object.class.respond_to?(:human_attribute_name)
68
72
  object.class.human_attribute_name(reflection_or_attribute_name.to_s)
@@ -2,7 +2,7 @@ module SimpleForm
2
2
  module Components
3
3
  # Needs to be enabled in order to do automatic lookups.
4
4
  module Maxlength
5
- def maxlength
5
+ def maxlength(wrapper_options = nil)
6
6
  input_html_options[:maxlength] ||= maximum_length_from_validation || limit
7
7
  nil
8
8
  end
@@ -1,7 +1,7 @@
1
1
  module SimpleForm
2
2
  module Components
3
3
  module MinMax
4
- def min_max
4
+ def min_max(wrapper_options = nil)
5
5
  if numeric_validator = find_numericality_validator
6
6
  validator_options = numeric_validator.options
7
7
  input_html_options[:min] ||= minimum_value(validator_options)
@@ -2,7 +2,7 @@ module SimpleForm
2
2
  module Components
3
3
  # Needs to be enabled in order to do automatic lookups.
4
4
  module Pattern
5
- def pattern
5
+ def pattern(wrapper_options = nil)
6
6
  input_html_options[:pattern] ||= pattern_source
7
7
  nil
8
8
  end
@@ -2,14 +2,14 @@ module SimpleForm
2
2
  module Components
3
3
  # Needs to be enabled in order to do automatic lookups.
4
4
  module Placeholders
5
- def placeholder
5
+ def placeholder(wrapper_options = nil)
6
6
  input_html_options[:placeholder] ||= placeholder_text
7
7
  nil
8
8
  end
9
9
 
10
10
  def placeholder_text
11
11
  placeholder = options[:placeholder]
12
- placeholder.is_a?(String) ? placeholder : translate(:placeholders)
12
+ placeholder.is_a?(String) ? placeholder : translate_from_namespace(:placeholders)
13
13
  end
14
14
  end
15
15
  end
@@ -2,7 +2,7 @@ module SimpleForm
2
2
  module Components
3
3
  # Needs to be enabled in order to do automatic lookups.
4
4
  module Readonly
5
- def readonly
5
+ def readonly(wrapper_options = nil)
6
6
  if readonly_attribute? && !has_readonly?
7
7
  input_html_options[:readonly] ||= true
8
8
  input_html_classes << :readonly
@@ -8,8 +8,8 @@ module SimpleForm
8
8
 
9
9
  # When action is create or update, we still should use new and edit
10
10
  ACTIONS = {
11
- create: :new,
12
- update: :edit
11
+ 'create' => 'new',
12
+ 'update' => 'edit'
13
13
  }
14
14
 
15
15
  ATTRIBUTE_COMPONENTS = [:html5, :min_max, :maxlength, :placeholder, :pattern, :readonly]
@@ -106,18 +106,13 @@ module SimpleForm
106
106
  # Some inputs, as :time_zone and :country accepts a :priority option. If none is
107
107
  # given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectively.
108
108
  #
109
- def input(attribute_name, options={}, &block)
109
+ def input(attribute_name, options = {}, &block)
110
110
  options = @defaults.deep_dup.deep_merge(options) if @defaults
111
- input = find_input(attribute_name, options, &block)
112
111
 
113
- chosen =
114
- if name = options[:wrapper] || find_wrapper_mapping(input.input_type)
115
- name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
116
- else
117
- wrapper
118
- end
112
+ input = find_input(attribute_name, options, &block)
113
+ wrapper = find_wrapper(input.input_type, options)
119
114
 
120
- chosen.render input
115
+ wrapper.render input
121
116
  end
122
117
  alias :attribute :input
123
118
 
@@ -135,12 +130,17 @@ module SimpleForm
135
130
  # <input class="string required" id="user_name" maxlength="100"
136
131
  # name="user[name]" type="text" value="Carlos" />
137
132
  #
138
- def input_field(attribute_name, options={})
133
+ def input_field(attribute_name, options = {})
139
134
  options = options.dup
140
- options[:input_html] = options.except(:as, :collection, :label_method, :value_method, *ATTRIBUTE_COMPONENTS)
135
+ options[:input_html] = options.except(:as, :boolean_style, :collection, :label_method, :value_method, *ATTRIBUTE_COMPONENTS)
141
136
  options = @defaults.deep_dup.deep_merge(options) if @defaults
142
137
 
143
- SimpleForm::Wrappers::Root.new(ATTRIBUTE_COMPONENTS + [:input], wrapper: false).render find_input(attribute_name, options)
138
+ input = find_input(attribute_name, options)
139
+ wrapper = find_wrapper(input.input_type, options)
140
+ components = (wrapper.components.map(&:namespace) & ATTRIBUTE_COMPONENTS) + [:input]
141
+ components = components.map { |component| SimpleForm::Wrappers::Leaf.new(component) }
142
+
143
+ SimpleForm::Wrappers::Root.new(components, wrapper.options.merge(wrapper: false)).render input
144
144
  end
145
145
 
146
146
  # Helper for dealing with association selects/radios, generating the
@@ -171,7 +171,7 @@ module SimpleForm
171
171
  #
172
172
  # Please note that the association helper is currently only tested with Active Record. Depending on the ORM you are using your mileage may vary.
173
173
  #
174
- def association(association, options={}, &block)
174
+ def association(association, options = {}, &block)
175
175
  options = options.dup
176
176
 
177
177
  return simple_fields_for(*[association,
@@ -183,31 +183,9 @@ module SimpleForm
183
183
  raise "Association #{association.inspect} not found" unless reflection
184
184
 
185
185
  options[:as] ||= :select
186
- options[:collection] ||= options.fetch(:collection) {
187
- conditions = reflection.options[:conditions]
188
- conditions = conditions.call if conditions.respond_to?(:call)
189
- reflection.klass.where(conditions).order(reflection.options[:order])
190
- }
191
-
192
- attribute = case reflection.macro
193
- when :belongs_to
194
- (reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
195
- when :has_one
196
- raise ArgumentError, ":has_one associations are not supported by f.association"
197
- else
198
- if options[:as] == :select
199
- html_options = options[:input_html] ||= {}
200
- html_options[:multiple] = true unless html_options.key?(:multiple)
201
- end
202
-
203
- # Force the association to be preloaded for performance.
204
- if options[:preload] != false && object.respond_to?(association)
205
- target = object.send(association)
206
- target.to_a if target.respond_to?(:to_a)
207
- end
208
-
209
- :"#{reflection.name.to_s.singularize}_ids"
210
- end
186
+ options[:collection] ||= fetch_association_collection(reflection, options)
187
+
188
+ attribute = build_association_attribute(reflection, association, options)
211
189
 
212
190
  input(attribute, options.merge(reflection: reflection))
213
191
  end
@@ -242,7 +220,7 @@ module SimpleForm
242
220
  # f.error :name
243
221
  # f.error :name, id: "cool_error"
244
222
  #
245
- def error(attribute_name, options={})
223
+ def error(attribute_name, options = {})
246
224
  options = options.dup
247
225
 
248
226
  options[:error_html] = options.except(:error_tag, :error_prefix, :error_method)
@@ -259,7 +237,7 @@ module SimpleForm
259
237
  #
260
238
  # f.full_error :token #=> <span class="error">Token is invalid</span>
261
239
  #
262
- def full_error(attribute_name, options={})
240
+ def full_error(attribute_name, options = {})
263
241
  options = options.dup
264
242
 
265
243
  options[:error_prefix] ||= if object.class.respond_to?(:human_attribute_name)
@@ -281,7 +259,7 @@ module SimpleForm
281
259
  # f.hint :name, id: "cool_hint"
282
260
  # f.hint "Don't forget to accept this"
283
261
  #
284
- def hint(attribute_name, options={})
262
+ def hint(attribute_name, options = {})
285
263
  options = options.dup
286
264
 
287
265
  options[:hint_html] = options.except(:hint_tag, :hint)
@@ -332,7 +310,7 @@ module SimpleForm
332
310
  # f.error_notification message: 'Something went wrong'
333
311
  # f.error_notification id: 'user_error_message', class: 'form_error'
334
312
  #
335
- def error_notification(options={})
313
+ def error_notification(options = {})
336
314
  SimpleForm::ErrorNotification.new(self, options).render
337
315
  end
338
316
 
@@ -464,15 +442,56 @@ module SimpleForm
464
442
  @lookup_action ||= begin
465
443
  action = template.controller && template.controller.action_name
466
444
  return unless action
467
- action = action.to_sym
445
+ action = action.to_s
468
446
  ACTIONS[action] || action
469
447
  end
470
448
  end
471
449
 
472
450
  private
473
451
 
452
+ def fetch_association_collection(reflection, options)
453
+ options.fetch(:collection) do
454
+ relation = reflection.klass.all
455
+
456
+ if reflection.respond_to?(:scope) && reflection.scope
457
+ relation = reflection.klass.instance_exec(&reflection.scope)
458
+ else
459
+ order = reflection.options[:order]
460
+ conditions = reflection.options[:conditions]
461
+ conditions = object.instance_exec(&conditions) if conditions.respond_to?(:call)
462
+
463
+ relation = relation.where(conditions)
464
+ relation = relation.order(order) if relation.respond_to?(:order)
465
+ end
466
+
467
+ relation
468
+ end
469
+ end
470
+
471
+ def build_association_attribute(reflection, association, options)
472
+ case reflection.macro
473
+ when :belongs_to
474
+ (reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
475
+ when :has_one
476
+ raise ArgumentError, ":has_one associations are not supported by f.association"
477
+ else
478
+ if options[:as] == :select
479
+ html_options = options[:input_html] ||= {}
480
+ html_options[:multiple] = true unless html_options.key?(:multiple)
481
+ end
482
+
483
+ # Force the association to be preloaded for performance.
484
+ if options[:preload] != false && object.respond_to?(association)
485
+ target = object.send(association)
486
+ target.to_a if target.respond_to?(:to_a)
487
+ end
488
+
489
+ :"#{reflection.name.to_s.singularize}_ids"
490
+ end
491
+ end
492
+
474
493
  # Find an input based on the attribute name.
475
- def find_input(attribute_name, options={}, &block) #:nodoc:
494
+ def find_input(attribute_name, options = {}, &block)
476
495
  column = find_attribute_column(attribute_name)
477
496
  input_type = default_input_type(attribute_name, column, options)
478
497
 
@@ -486,7 +505,7 @@ module SimpleForm
486
505
  # Attempt to guess the better input type given the defined options. By
487
506
  # default alwayls fallback to the user :as option, or to a :select when a
488
507
  # collection is given.
489
- def default_input_type(attribute_name, column, options) #:nodoc:
508
+ def default_input_type(attribute_name, column, options)
490
509
  return options[:as].to_sym if options[:as]
491
510
  return :select if options[:collection]
492
511
  custom_type = find_custom_type(attribute_name.to_s) and return custom_type
@@ -511,24 +530,24 @@ module SimpleForm
511
530
  end
512
531
  end
513
532
 
514
- def find_custom_type(attribute_name) #:nodoc:
533
+ def find_custom_type(attribute_name)
515
534
  SimpleForm.input_mappings.find { |match, type|
516
535
  attribute_name =~ match
517
536
  }.try(:last) if SimpleForm.input_mappings
518
537
  end
519
538
 
520
- def file_method?(attribute_name) #:nodoc:
539
+ def file_method?(attribute_name)
521
540
  file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
522
541
  file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
523
542
  end
524
543
 
525
- def find_attribute_column(attribute_name) #:nodoc:
544
+ def find_attribute_column(attribute_name)
526
545
  if @object.respond_to?(:column_for_attribute)
527
546
  @object.column_for_attribute(attribute_name)
528
547
  end
529
548
  end
530
549
 
531
- def find_association_reflection(association) #:nodoc:
550
+ def find_association_reflection(association)
532
551
  if @object.class.respond_to?(:reflect_on_association)
533
552
  @object.class.reflect_on_association(association)
534
553
  end
@@ -541,7 +560,7 @@ module SimpleForm
541
560
  # b) Or use the found mapping
542
561
  # 2) If not, fallbacks to #{input_type}Input
543
562
  # 3) If not, fallbacks to SimpleForm::Inputs::#{input_type}Input
544
- def find_mapping(input_type) #:nodoc:
563
+ def find_mapping(input_type)
545
564
  discovery_cache[input_type] ||=
546
565
  if mapping = self.class.mappings[input_type]
547
566
  mapping_override(mapping) || mapping
@@ -552,13 +571,29 @@ module SimpleForm
552
571
  end
553
572
  end
554
573
 
555
- def find_wrapper_mapping(input_type) #:nodoc:
556
- SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
574
+ # Attempts to find a wrapper mapping. It follows the following rules:
575
+ #
576
+ # 1) It tries to find a wrapper for the current form
577
+ # 2) If not, it tries to find a config
578
+ def find_wrapper_mapping(input_type)
579
+ if options[:wrapper_mappings] && options[:wrapper_mappings][input_type]
580
+ options[:wrapper_mappings][input_type]
581
+ else
582
+ SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
583
+ end
584
+ end
585
+
586
+ def find_wrapper(input_type, options)
587
+ if name = options[:wrapper] || find_wrapper_mapping(input_type)
588
+ name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
589
+ else
590
+ wrapper
591
+ end
557
592
  end
558
593
 
559
594
  # If cache_discovery is enabled, use the class level cache that persists
560
595
  # between requests, otherwise use the instance one.
561
- def discovery_cache #:nodoc:
596
+ def discovery_cache
562
597
  if SimpleForm.cache_discovery
563
598
  self.class.discovery_cache
564
599
  else
@@ -566,14 +601,14 @@ module SimpleForm
566
601
  end
567
602
  end
568
603
 
569
- def mapping_override(klass) #:nodoc:
604
+ def mapping_override(klass)
570
605
  name = klass.name
571
606
  if name =~ /^SimpleForm::Inputs/
572
607
  attempt_mapping name.split("::").last, Object
573
608
  end
574
609
  end
575
610
 
576
- def attempt_mapping(mapping, at) #:nodoc:
611
+ def attempt_mapping(mapping, at)
577
612
  return if SimpleForm.inputs_discovery == false && at == Object
578
613
 
579
614
  begin