simple_form 0.5 → 1.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.

Potentially problematic release.


This version of simple_form might be problematic. Click here for more details.

Files changed (48) hide show
  1. data/README.rdoc +339 -6
  2. data/generators/simple_form_install/USAGE +3 -0
  3. data/generators/simple_form_install/simple_form_install_generator.rb +19 -0
  4. data/generators/simple_form_install/templates/simple_form.rb +38 -0
  5. data/init.rb +1 -0
  6. data/lib/simple_form.rb +57 -1
  7. data/lib/simple_form/action_view_extensions/builder.rb +122 -0
  8. data/lib/simple_form/action_view_extensions/form_helper.rb +33 -0
  9. data/lib/simple_form/action_view_extensions/instance_tag.rb +37 -0
  10. data/lib/simple_form/components.rb +8 -0
  11. data/lib/simple_form/components/errors.rb +35 -0
  12. data/lib/simple_form/components/hints.rb +21 -0
  13. data/lib/simple_form/components/labels.rb +68 -0
  14. data/lib/simple_form/components/wrapper.rb +21 -0
  15. data/lib/simple_form/form_builder.rb +332 -0
  16. data/lib/simple_form/i18n_cache.rb +22 -0
  17. data/lib/simple_form/inputs.rb +12 -0
  18. data/lib/simple_form/inputs/base.rb +107 -0
  19. data/lib/simple_form/inputs/block_input.rb +13 -0
  20. data/lib/simple_form/inputs/collection_input.rb +58 -0
  21. data/lib/simple_form/inputs/date_time_input.rb +18 -0
  22. data/lib/simple_form/inputs/hidden_input.rb +11 -0
  23. data/lib/simple_form/inputs/mapping_input.rb +23 -0
  24. data/lib/simple_form/inputs/priority_input.rb +20 -0
  25. data/lib/simple_form/inputs/text_field_input.rb +16 -0
  26. data/lib/simple_form/locale/en.yml +14 -0
  27. data/lib/simple_form/map_type.rb +13 -0
  28. data/lib/simple_form/version.rb +3 -0
  29. data/test/action_view_extensions/builder_test.rb +172 -0
  30. data/test/action_view_extensions/form_helper_test.rb +50 -0
  31. data/test/components/error_test.rb +45 -0
  32. data/test/components/hint_test.rb +78 -0
  33. data/test/components/label_test.rb +170 -0
  34. data/test/form_builder_test.rb +550 -0
  35. data/test/inputs_test.rb +337 -0
  36. data/test/simple_form_test.rb +9 -0
  37. data/test/support/country_select/init.rb +1 -0
  38. data/test/support/country_select/install.rb +2 -0
  39. data/test/support/country_select/lib/country_select.rb +84 -0
  40. data/test/support/country_select/uninstall.rb +1 -0
  41. data/test/support/misc_helpers.rb +29 -0
  42. data/test/support/mock_controller.rb +11 -0
  43. data/test/support/mock_response.rb +14 -0
  44. data/test/support/models.rb +100 -0
  45. data/test/test_helper.rb +60 -0
  46. metadata +50 -10
  47. data/CHANGELOG +0 -27
  48. data/Rakefile +0 -17
@@ -0,0 +1,122 @@
1
+ module SimpleForm
2
+ module ActionViewExtensions
3
+ # A collection of methods required by simple_form but added to rails default form.
4
+ # This means that you can use such methods outside simple_form context.
5
+ module Builder
6
+
7
+ # Create a collection of radio inputs for the attribute. Basically this
8
+ # helper will create a radio input associated with a label for each
9
+ # text/value option in the collection, using value_method and text_method
10
+ # to convert these text/value. Based on collection_select.
11
+ #
12
+ # == Examples
13
+ #
14
+ # form_for @user do |f|
15
+ # f.collection_radio :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
16
+ # end
17
+ #
18
+ # <input id="user_options_true" name="user[options]" type="radio" value="true" />
19
+ # <label class="collection_radio" for="user_options_true">Yes</label>
20
+ # <input id="user_options_false" name="user[options]" type="radio" value="false" />
21
+ # <label class="collection_radio" for="user_options_false">No</label>
22
+ #
23
+ # == Options
24
+ #
25
+ # Collection radio accepts some extra options:
26
+ #
27
+ # * checked => the value that should be checked initially.
28
+ #
29
+ # * disabled => the value or values that should be disabled. Accepts a single
30
+ # item or an array of items.
31
+ #
32
+ def collection_radio(attribute, collection, value_method, text_method, options={}, html_options={})
33
+ collection.inject('') do |result, item|
34
+ value = item.send value_method
35
+ text = item.send text_method
36
+
37
+ default_html_options = default_html_options_for_collection(item, value, options, html_options)
38
+
39
+ result << radio_button(attribute, value, default_html_options) <<
40
+ label("#{attribute}_#{value}", text, :class => "collection_radio")
41
+ end
42
+ end
43
+
44
+ # Creates a collection of check boxes for each item in the collection, associated
45
+ # with a clickable label. Use value_method and text_method to convert items in
46
+ # the collection for use as text/value in check boxes.
47
+ #
48
+ # == Examples
49
+ #
50
+ # form_for @user do |f|
51
+ # f.collection_check_box :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
52
+ # end
53
+ #
54
+ # <input name="user[options][]" type="hidden" value="" />
55
+ # <input id="user_options_true" name="user[options][]" type="checkbox" value="true" />
56
+ # <label class="collection_check_box" for="user_options_true">Yes</label>
57
+ # <input name="user[options][]" type="hidden" value="" />
58
+ # <input id="user_options_false" name="user[options][]" type="checkbox" value="false" />
59
+ # <label class="collection_check_box" for="user_options_false">No</label>
60
+ #
61
+ # == Options
62
+ #
63
+ # Collection check box accepts some extra options:
64
+ #
65
+ # * checked => the value or values that should be checked initially. Accepts
66
+ # a single item or an array of items.
67
+ #
68
+ # * disabled => the value or values that should be disabled. Accepts a single
69
+ # item or an array of items.
70
+ #
71
+ def collection_check_boxes(attribute, collection, value_method, text_method, options={}, html_options={})
72
+ collection.inject('') do |result, item|
73
+ value = item.send value_method
74
+ text = item.send text_method
75
+
76
+ default_html_options = default_html_options_for_collection(item, value, options, html_options)
77
+ default_html_options[:multiple] = true
78
+
79
+ result << check_box(attribute, default_html_options, value, '') <<
80
+ label("#{attribute}_#{value}", text, :class => "collection_check_boxes")
81
+ end
82
+ end
83
+
84
+ # Wrapper for using simple form inside a default rails form.
85
+ # Example:
86
+ #
87
+ # form_for @user do |f|
88
+ # f.simple_fields_for :posts do |posts_form|
89
+ # # Here you have all simple_form methods available
90
+ # posts_form.input :title
91
+ # end
92
+ # end
93
+ def simple_fields_for(*args, &block)
94
+ options = args.extract_options!
95
+ options[:builder] = SimpleForm::FormBuilder
96
+ fields_for(*(args << options), &block)
97
+ end
98
+
99
+ private
100
+
101
+ # Generate default options for collection helpers, such as :checked and
102
+ # :disabled.
103
+ def default_html_options_for_collection(item, value, options, html_options) #:nodoc:
104
+ returning(html_options.dup) do |default_html_options|
105
+ [:checked, :disabled].each do |option|
106
+ next unless options[option]
107
+
108
+ accept = if options[option].is_a?(Proc)
109
+ options[option].call(item)
110
+ else
111
+ Array(options[option]).include?(value)
112
+ end
113
+
114
+ default_html_options[option] = true if accept
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ ActionView::Helpers::FormBuilder.send :include, SimpleForm::ActionViewExtensions::Builder
@@ -0,0 +1,33 @@
1
+ module SimpleForm
2
+ module ActionViewExtensions
3
+ # This modules create simple form wrappers around default form_for,
4
+ # fields_for and remote_form_for.
5
+ #
6
+ # Example:
7
+ #
8
+ # simple_form_for @user do |f|
9
+ # f.input :name, :hint => 'My hint'
10
+ # end
11
+ #
12
+ module FormHelper
13
+ [:form_for, :fields_for, :remote_form_for].each do |helper|
14
+ class_eval <<-METHOD, __FILE__, __LINE__
15
+ def simple_#{helper}(record_or_name_or_array, *args, &block)
16
+ options = args.extract_options!
17
+ options[:builder] = SimpleForm::FormBuilder
18
+ css_class = case record_or_name_or_array
19
+ when String, Symbol then record_or_name_or_array.to_s
20
+ when Array then dom_class(record_or_name_or_array.last)
21
+ else dom_class(record_or_name_or_array)
22
+ end
23
+ options[:html] ||= {}
24
+ options[:html][:class] = "simple_form \#{css_class} \#{options[:html][:class]}".strip
25
+ #{helper}(record_or_name_or_array, *(args << options), &block)
26
+ end
27
+ METHOD
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ ActionView::Base.send :include, SimpleForm::ActionViewExtensions::FormHelper
@@ -0,0 +1,37 @@
1
+ module SimpleForm
2
+ module ActionViewExtensions
3
+ module InstanceTag #:nodoc:
4
+ # Overwrite to_check_box_tag to make it available to work with :multiple => true
5
+ def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
6
+ options = options.stringify_keys
7
+ options["type"] = "checkbox"
8
+ options["value"] = checked_value
9
+
10
+ if options.has_key?("checked")
11
+ cv = options.delete "checked"
12
+ checked = cv == true || cv == "checked"
13
+ else
14
+ checked = self.class.check_box_checked?(value(object), checked_value)
15
+ end
16
+ options["checked"] = "checked" if checked
17
+
18
+ # The only part added to deal with multiple check box is this conditional.
19
+ if options["multiple"]
20
+ add_default_name_and_id_for_value(checked_value, options)
21
+ options.delete("multiple")
22
+ else
23
+ add_default_name_and_id(options)
24
+ end
25
+
26
+ hidden = tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
27
+ checkbox = tag("input", options)
28
+
29
+ result = hidden + checkbox
30
+ result.respond_to?(:html_safe!) ? result.html_safe! : result
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ ActionView::Helpers::InstanceTag.send :remove_method, :to_check_box_tag
37
+ ActionView::Helpers::InstanceTag.send :include, SimpleForm::ActionViewExtensions::InstanceTag
@@ -0,0 +1,8 @@
1
+ module SimpleForm
2
+ module Components
3
+ autoload :Errors, 'simple_form/components/errors'
4
+ autoload :Hints, 'simple_form/components/hints'
5
+ autoload :Labels, 'simple_form/components/labels'
6
+ autoload :Wrapper, 'simple_form/components/wrapper'
7
+ end
8
+ end
@@ -0,0 +1,35 @@
1
+ module SimpleForm
2
+ module Components
3
+ module Errors
4
+ def error
5
+ template.content_tag(error_tag, error_text, error_html_options) if object && errors.present?
6
+ end
7
+
8
+ def error_tag
9
+ options[:error_tag] || SimpleForm.error_tag
10
+ end
11
+
12
+ def error_text
13
+ errors.to_sentence
14
+ end
15
+
16
+ def error_html_options
17
+ html_options_for(:error, :error)
18
+ end
19
+
20
+ protected
21
+
22
+ def errors
23
+ @errors ||= (errors_on_attribute + errors_on_association).compact
24
+ end
25
+
26
+ def errors_on_attribute
27
+ Array(object.errors[attribute_name])
28
+ end
29
+
30
+ def errors_on_association
31
+ reflection ? Array(object.errors[reflection.name]) : []
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ module SimpleForm
2
+ module Components
3
+ module Hints
4
+ def hint
5
+ template.content_tag(hint_tag, hint_text, hint_html_options) unless hint_text.blank?
6
+ end
7
+
8
+ def hint_tag
9
+ options[:hint_tag] || SimpleForm.hint_tag
10
+ end
11
+
12
+ def hint_text
13
+ @hint_text ||= options[:hint] || translate(:hints)
14
+ end
15
+
16
+ def hint_html_options
17
+ html_options_for(:hint, :hint)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,68 @@
1
+ module SimpleForm
2
+ module Components
3
+ module Labels
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods #:nodoc:
9
+ def translate_required_html
10
+ i18n_cache :translate_required_html do
11
+ I18n.t(:"simple_form.required.html", :default =>
12
+ %[<abbr title="#{translate_required_text}">#{translate_required_mark}</abbr>]
13
+ )
14
+ end
15
+ end
16
+
17
+ def translate_required_text
18
+ I18n.t(:"simple_form.required.text", :default => 'required')
19
+ end
20
+
21
+ def translate_required_mark
22
+ I18n.t(:"simple_form.required.mark", :default => '*')
23
+ end
24
+ end
25
+
26
+ def label
27
+ @builder.label(label_target, label_text, label_html_options)
28
+ end
29
+
30
+ def label_text
31
+ SimpleForm.label_text.call(raw_label_text, required_label_text)
32
+ end
33
+
34
+ def label_target
35
+ attribute_name
36
+ end
37
+
38
+ def label_html_options
39
+ label_options = html_options_for(:label, input_type, required_class)
40
+ label_options[:for] = options[:input_html][:id] if options.key?(:input_html)
41
+ label_options
42
+ end
43
+
44
+ protected
45
+
46
+ def raw_label_text #:nodoc:
47
+ options[:label] || label_translation
48
+ end
49
+
50
+ # Default required text when attribute is required.
51
+ def required_label_text #:nodoc:
52
+ attribute_required? ? self.class.translate_required_html.dup : ''
53
+ end
54
+
55
+ # First check human attribute name and then labels.
56
+ # TODO Remove me in Rails > 2.3.5
57
+ def label_translation #:nodoc:
58
+ default = if object.class.respond_to?(:human_attribute_name)
59
+ object.class.human_attribute_name(reflection_or_attribute_name.to_s)
60
+ else
61
+ attribute_name.to_s.humanize
62
+ end
63
+
64
+ translate(:labels, default)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,21 @@
1
+ module SimpleForm
2
+ module Components
3
+ module Wrapper
4
+ def wrap(content)
5
+ if wrapper_tag && options[:wrapper] != false
6
+ template.content_tag(wrapper_tag, content, wrapper_html_options)
7
+ else
8
+ content
9
+ end
10
+ end
11
+
12
+ def wrapper_tag
13
+ options[:wrapper_tag] || SimpleForm.wrapper_tag
14
+ end
15
+
16
+ def wrapper_html_options
17
+ html_options_for(:wrapper, input_type, required_class)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,332 @@
1
+ module SimpleForm
2
+ class FormBuilder < ActionView::Helpers::FormBuilder
3
+ attr_reader :template, :object_name, :object, :attribute_name, :column,
4
+ :reflection, :input_type, :options
5
+
6
+ extend MapType
7
+ include SimpleForm::Inputs
8
+
9
+ map_type :boolean, :password, :text, :file, :to => SimpleForm::Inputs::MappingInput
10
+ map_type :string, :integer, :decimal, :float, :to => SimpleForm::Inputs::TextFieldInput
11
+ map_type :select, :radio, :check_boxes, :to => SimpleForm::Inputs::CollectionInput
12
+ map_type :date, :time, :datetime, :to => SimpleForm::Inputs::DateTimeInput
13
+ map_type :country, :time_zone, :to => SimpleForm::Inputs::PriorityInput
14
+
15
+ # Basic input helper, combines all components in the stack to generate
16
+ # input html based on options the user define and some guesses through
17
+ # database column information. By default a call to input will generate
18
+ # label + input + hint (when defined) + errors (when exists), and all can
19
+ # be configured inside a wrapper html.
20
+ #
21
+ # == Examples
22
+ #
23
+ # # Imagine @user has error "can't be blank" on name
24
+ # simple_form_for @user do |f|
25
+ # f.input :name, :hint => 'My hint'
26
+ # end
27
+ #
28
+ # This is the output html (only the input portion, not the form):
29
+ #
30
+ # <label class="string required" for="user_name">
31
+ # <abbr title="required">*</abbr> Super User Name!
32
+ # </label>
33
+ # <input class="string required" id="user_name" maxlength="100"
34
+ # name="user[name]" size="100" type="text" value="Carlos" />
35
+ # <span class="hint">My hint</span>
36
+ # <span class="error">can't be blank</span>
37
+ #
38
+ # Each database type will render a default input, based on some mappings and
39
+ # heuristic to determine which is the best option.
40
+ #
41
+ # You have some options for the input to enable/disable some functions:
42
+ #
43
+ # :as => allows you to define the input type you want, for instance you
44
+ # can use it to generate a text field for a date column.
45
+ #
46
+ # :required => defines whether this attribute is required or not. True
47
+ # by default.
48
+ #
49
+ # The fact SimpleForm is built in components allow the interface to be unified.
50
+ # So, for instance, if you need to disable :hint for a given input, you can pass
51
+ # :hint => false. The same works for :error, :label and :wrapper.
52
+ #
53
+ # Besides the html for any component can be changed. So, if you want to change
54
+ # the label html you just need to give a hash to :label_html. To configure the
55
+ # input html, supply :input_html instead and so on.
56
+ #
57
+ # == Options
58
+ #
59
+ # Some inputs, as datetime, time and select allow you to give extra options, like
60
+ # prompt and/or include blank. Such options are given in plainly:
61
+ #
62
+ # f.input :created_at, :include_blank => true
63
+ #
64
+ # == Collection
65
+ #
66
+ # When playing with collections (:radio and :select inputs), you have three extra
67
+ # options:
68
+ #
69
+ # :collection => use to determine the collection to generate the radio or select
70
+ #
71
+ # :label_method => the method to apply on the array collection to get the label
72
+ #
73
+ # :value_method => the method to apply on the array collection to get the value
74
+ #
75
+ # == Priority
76
+ #
77
+ # Some inputs, as :time_zone and :country accepts a :priority option. If none is
78
+ # given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectivelly.
79
+ #
80
+ def input(attribute_name, options={}, &block)
81
+ define_simple_form_attributes(attribute_name, options)
82
+
83
+ if block_given?
84
+ SimpleForm::Inputs::BlockInput.new(self, block).render
85
+ else
86
+ klass = self.class.mappings[input_type] ||
87
+ self.class.const_get(:"#{input_type.to_s.camelize}Input")
88
+ klass.new(self).render
89
+ end
90
+ end
91
+ alias :attribute :input
92
+
93
+ # Helper for dealing with association selects/radios, generating the
94
+ # collection automatically. It's just a wrapper to input, so all options
95
+ # supported in input are also supported by association. Some extra options
96
+ # can also be given:
97
+ #
98
+ # == Options
99
+ #
100
+ # * :conditions - Given as conditions when retrieving the collection
101
+ #
102
+ # * :include - Given as include when retrieving the collection
103
+ #
104
+ # * :joins - Given as joins when retrieving the collection
105
+ #
106
+ # * :order - Given as order when retrieving the collection
107
+ #
108
+ # * :scope - Given as scopes when retrieving the collection
109
+ #
110
+ # == Examples
111
+ #
112
+ # simple_form_for @user do |f|
113
+ # f.association :company # Company.all
114
+ # end
115
+ #
116
+ # f.association :company, :order => 'name'
117
+ # # Company.all(:order => 'name')
118
+ #
119
+ # f.association :company, :conditions => { :active => true }
120
+ # # Company.all(:conditions => { :active => true })
121
+ #
122
+ # f.association :company, :collection => Company.all(:order => 'name')
123
+ # # Same as using :order option, but overriding collection
124
+ #
125
+ # f.association :company, :scope => [ :public, :not_broken ]
126
+ # # Same as doing Company.public.not_broken.all
127
+ #
128
+ # == Block
129
+ #
130
+ # When a block is given, association simple behaves as a proxy to
131
+ # simple_fields_for:
132
+ #
133
+ # f.association :company do |c|
134
+ # c.input :name
135
+ # c.input :type
136
+ # end
137
+ #
138
+ # From the options above, only :collection can also be supplied.
139
+ #
140
+ def association(association, options={}, &block)
141
+ return simple_fields_for(*[association,
142
+ options.delete(:collection), options].compact, &block) if block_given?
143
+
144
+ raise ArgumentError, "Association cannot be used in forms not associated with an object" unless @object
145
+
146
+ options[:as] ||= :select
147
+ @reflection = find_association_reflection(association)
148
+ raise "Association #{association.inspect} not found" unless @reflection
149
+
150
+ case @reflection.macro
151
+ when :belongs_to
152
+ attribute = @reflection.options[:foreign_key] || :"#{@reflection.name}_id"
153
+ when :has_one
154
+ raise ":has_one association are not supported by f.association"
155
+ else
156
+ attribute = :"#{@reflection.name.to_s.singularize}_ids"
157
+
158
+ if options[:as] == :select
159
+ html_options = options[:input_html] ||= {}
160
+ html_options[:size] ||= 5
161
+ html_options[:multiple] = true unless html_options.key?(:multiple)
162
+ end
163
+ end
164
+
165
+ options[:collection] ||= begin
166
+ finders = options.slice(:conditions, :order, :include, :joins)
167
+ finders[:conditions] = @reflection.klass.merge_conditions(finders[:conditions],
168
+ @reflection.options[:conditions])
169
+ klass = Array(options[:scope]).inject(@reflection.klass) do |klass, scope|
170
+ klass.send(scope)
171
+ end
172
+ klass.all(finders)
173
+ end
174
+
175
+ returning(input(attribute, options)) { @reflection = nil }
176
+ end
177
+
178
+ # Creates a button:
179
+ #
180
+ # form_for @user do |f|
181
+ # f.button :submit
182
+ # end
183
+ #
184
+ # If the record is a new_record?, it will create a button with label "Create User",
185
+ # otherwise it will create with label "Update User". You can overwrite the label
186
+ # giving a second parameter or giving :label.
187
+ #
188
+ # f.button :submit, "Create a new user"
189
+ # f.button :submit, :label => "Create a new user"
190
+ #
191
+ # button is actually just a wrapper that adds a default text, that said, f.button
192
+ # above is just calling:
193
+ #
194
+ # submit_tag "Create a new user"
195
+ #
196
+ # All options given to button are given straight to submit_tag. That said, you can
197
+ # use :confirm normally:
198
+ #
199
+ # f.button :submit, :confirm => "Are you sure?"
200
+ #
201
+ # And if you want to use image_submit_tag, just give it as an option:
202
+ #
203
+ # f.button :image_submit, "/images/foo/bar.png"
204
+ #
205
+ # This comes with a bonus that any method added to your ApplicationController can
206
+ # be used by SimpleForm, as long as it ends with _tag. So is quite easy to customize
207
+ # your buttons.
208
+ #
209
+ def button(type, *args)
210
+ options = args.extract_options!
211
+ value = args.first || options.delete(:label)
212
+ key = @object ? (@object.new_record? ? :create : :update) : :submit
213
+
214
+ value ||= begin
215
+ model = if @object.class.respond_to?(:human_name)
216
+ @object.class.human_name
217
+ else
218
+ @object_name.to_s.humanize
219
+ end
220
+
221
+ I18n.t(:"simple_form.buttons.#{key}", :model => model, :default => "#{key.to_s.humanize} #{model}")
222
+ end
223
+
224
+ options[:class] = "#{key} #{options[:class]}".strip
225
+ @template.send(:"#{type}_tag", value, options)
226
+ end
227
+
228
+ # Creates an error tag based on the given attribute, only when the attribute
229
+ # contains errors. All the given options are sent as :error_html.
230
+ #
231
+ # == Examples
232
+ #
233
+ # f.error :name
234
+ # f.error :name, :id => "cool_error"
235
+ #
236
+ def error(attribute_name, options={})
237
+ define_simple_form_attributes(attribute_name, :error_html => options)
238
+ SimpleForm::Inputs::Base.new(self).error
239
+ end
240
+
241
+ # Creates a hint tag for the given attribute. Accepts a symbol indicating
242
+ # an attribute for I18n lookup or a string. All the given options are sent
243
+ # as :hint_html.
244
+ #
245
+ # == Examples
246
+ #
247
+ # f.hint :name # Do I18n lookup
248
+ # f.hint :name, :id => "cool_hint"
249
+ # f.hint "Don't forget to accept this"
250
+ #
251
+ def hint(attribute_name, options={})
252
+ attribute_name, options[:hint] = nil, attribute_name if attribute_name.is_a?(String)
253
+ define_simple_form_attributes(attribute_name, :hint => options.delete(:hint), :hint_html => options)
254
+ SimpleForm::Inputs::Base.new(self).hint
255
+ end
256
+
257
+ # Creates a default label tag for the given attribute. You can give a label
258
+ # through the :label option or using i18n. All the given options are sent
259
+ # as :label_html.
260
+ #
261
+ # == Examples
262
+ #
263
+ # f.label :name # Do I18n lookup
264
+ # f.label :name, "Name" # Same behavior as Rails, do not add required tag
265
+ # f.label :name, :label => "Name" # Same as above, but adds required tag
266
+ #
267
+ # f.label :name, :required => false
268
+ # f.label :name, :id => "cool_label"
269
+ #
270
+ def label(attribute_name, *args)
271
+ return super if args.first.is_a?(String)
272
+ options = args.extract_options!
273
+ define_simple_form_attributes(attribute_name, :label => options.delete(:label),
274
+ :label_html => options, :required => options.delete(:required))
275
+ SimpleForm::Inputs::Base.new(self).label
276
+ end
277
+
278
+ private
279
+
280
+ # Setup default simple form attributes.
281
+ def define_simple_form_attributes(attribute_name, options) #:nodoc:
282
+ @options = options
283
+
284
+ if @attribute_name = attribute_name
285
+ @column = find_attribute_column
286
+ @input_type = default_input_type
287
+ end
288
+ end
289
+
290
+ # Attempt to guess the better input type given the defined options. By
291
+ # default alwayls fallback to the user :as option, or to a :select when a
292
+ # collection is given.
293
+ def default_input_type #:nodoc:
294
+ return @options[:as].to_sym if @options[:as]
295
+ return :select if @options[:collection]
296
+
297
+ input_type = @column.try(:type)
298
+
299
+ case input_type
300
+ when :timestamp
301
+ :datetime
302
+ when :string, nil
303
+ match = case @attribute_name.to_s
304
+ when /password/ then :password
305
+ when /time_zone/ then :time_zone
306
+ when /country/ then :country
307
+ end
308
+
309
+ match || input_type || file_method? || :string
310
+ else
311
+ input_type
312
+ end
313
+ end
314
+
315
+ # Checks if attribute is a file_method.
316
+ def file_method? #:nodoc:
317
+ file = @object.send(@attribute_name) if @object.respond_to?(@attribute_name)
318
+ :file if file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
319
+ end
320
+
321
+ # Finds the database column for the given attribute
322
+ def find_attribute_column #:nodoc:
323
+ @object.column_for_attribute(@attribute_name) if @object.respond_to?(:column_for_attribute)
324
+ end
325
+
326
+ # Find reflection related to association
327
+ def find_association_reflection(association) #:nodoc:
328
+ @object.class.reflect_on_association(association) if @object.class.respond_to?(:reflect_on_association)
329
+ end
330
+
331
+ end
332
+ end