simple_form 1.2.2 → 1.3.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 (36) hide show
  1. data/README.rdoc +80 -14
  2. data/lib/generators/simple_form/templates/_form.html.erb +1 -11
  3. data/lib/generators/simple_form/templates/simple_form.rb +31 -6
  4. data/lib/simple_form.rb +38 -2
  5. data/lib/simple_form/action_view_extensions/builder.rb +53 -20
  6. data/lib/simple_form/components.rb +6 -5
  7. data/lib/simple_form/components/errors.rb +4 -6
  8. data/lib/simple_form/components/hints.rb +2 -2
  9. data/lib/simple_form/components/labels.rb +3 -5
  10. data/lib/simple_form/components/placeholders.rb +22 -0
  11. data/lib/simple_form/components/wrapper.rb +7 -0
  12. data/lib/simple_form/error_notification.rb +4 -6
  13. data/lib/simple_form/form_builder.rb +61 -55
  14. data/lib/simple_form/has_errors.rb +14 -0
  15. data/lib/simple_form/inputs/base.rb +45 -17
  16. data/lib/simple_form/inputs/block_input.rb +3 -2
  17. data/lib/simple_form/inputs/collection_input.rb +42 -17
  18. data/lib/simple_form/inputs/date_time_input.rb +3 -1
  19. data/lib/simple_form/inputs/hidden_input.rb +11 -2
  20. data/lib/simple_form/inputs/mapping_input.rb +16 -3
  21. data/lib/simple_form/inputs/numeric_input.rb +45 -8
  22. data/lib/simple_form/inputs/string_input.rb +13 -9
  23. data/lib/simple_form/map_type.rb +4 -4
  24. data/lib/simple_form/version.rb +1 -1
  25. data/test/action_view_extensions/builder_test.rb +155 -51
  26. data/test/components/error_test.rb +10 -8
  27. data/test/components/hint_test.rb +4 -8
  28. data/test/components/label_test.rb +22 -9
  29. data/test/components/wrapper_test.rb +16 -7
  30. data/test/error_notification_test.rb +4 -3
  31. data/test/form_builder_test.rb +81 -34
  32. data/test/inputs_test.rb +242 -3
  33. data/test/support/misc_helpers.rb +6 -4
  34. data/test/support/models.rb +26 -3
  35. data/test/test_helper.rb +13 -5
  36. metadata +25 -8
@@ -1,9 +1,10 @@
1
1
  module SimpleForm
2
2
  module Components
3
- autoload :Errors, 'simple_form/components/errors'
4
- autoload :Hints, 'simple_form/components/hints'
5
- autoload :LabelInput, 'simple_form/components/label_input'
6
- autoload :Labels, 'simple_form/components/labels'
7
- autoload :Wrapper, 'simple_form/components/wrapper'
3
+ autoload :Errors, 'simple_form/components/errors'
4
+ autoload :Hints, 'simple_form/components/hints'
5
+ autoload :LabelInput, 'simple_form/components/label_input'
6
+ autoload :Labels, 'simple_form/components/labels'
7
+ autoload :Placeholders, 'simple_form/components/placeholders'
8
+ autoload :Wrapper, 'simple_form/components/wrapper'
8
9
  end
9
10
  end
@@ -1,6 +1,8 @@
1
1
  module SimpleForm
2
2
  module Components
3
3
  module Errors
4
+ include SimpleForm::HasErrors
5
+
4
6
  def error
5
7
  template.content_tag(error_tag, error_text, error_html_options) if has_errors?
6
8
  end
@@ -18,15 +20,11 @@ module SimpleForm
18
20
  end
19
21
 
20
22
  def error_html_options
21
- html_options_for(:error, [:error])
23
+ html_options_for(:error, [SimpleForm.error_class])
22
24
  end
23
25
 
24
26
  protected
25
27
 
26
- def has_errors?
27
- object && errors.present?
28
- end
29
-
30
28
  def errors
31
29
  @errors ||= (errors_on_attribute + errors_on_association).compact
32
30
  end
@@ -40,4 +38,4 @@ module SimpleForm
40
38
  end
41
39
  end
42
40
  end
43
- end
41
+ end
@@ -14,8 +14,8 @@ module SimpleForm
14
14
  end
15
15
 
16
16
  def hint_html_options
17
- html_options_for(:hint, [:hint])
17
+ html_options_for(:hint, [SimpleForm.hint_class])
18
18
  end
19
19
  end
20
20
  end
21
- end
21
+ end
@@ -52,16 +52,14 @@ module SimpleForm
52
52
  attribute_required? ? self.class.translate_required_html.dup : ''
53
53
  end
54
54
 
55
- # First check human attribute name and then labels.
55
+ # First check labels translation and then human attribute name.
56
56
  def label_translation #:nodoc:
57
- default = if object.class.respond_to?(:human_attribute_name)
57
+ translate(:labels) || if object.class.respond_to?(:human_attribute_name)
58
58
  object.class.human_attribute_name(reflection_or_attribute_name.to_s)
59
59
  else
60
60
  attribute_name.to_s.humanize
61
61
  end
62
-
63
- translate(:labels, default)
64
62
  end
65
63
  end
66
64
  end
67
- end
65
+ end
@@ -0,0 +1,22 @@
1
+ module SimpleForm
2
+ module Components
3
+ module Placeholders
4
+ def placeholder
5
+ input_html_options[:placeholder] ||= placeholder_text if has_placeholder?
6
+ nil
7
+ end
8
+
9
+ def has_placeholder?
10
+ false
11
+ end
12
+
13
+ def placeholder_present?
14
+ options[:placeholder] != false && placeholder_text.present?
15
+ end
16
+
17
+ def placeholder_text
18
+ @placeholder ||= options[:placeholder] || translate(:placeholders)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -24,8 +24,15 @@ module SimpleForm
24
24
  def wrapper_html_options
25
25
  css_classes = input_html_classes.unshift(wrapper_class)
26
26
  css_classes << wrapper_error_class if has_errors?
27
+ css_classes << disabled_class if disabled?
27
28
  html_options_for(:wrapper, css_classes)
28
29
  end
30
+
31
+ private
32
+
33
+ def disabled_class
34
+ 'disabled'
35
+ end
29
36
  end
30
37
  end
31
38
  end
@@ -1,6 +1,7 @@
1
1
  module SimpleForm
2
2
  class ErrorNotification
3
3
  delegate :object, :object_name, :template, :to => :@builder
4
+ include SimpleForm::HasErrors
4
5
 
5
6
  def initialize(builder, options)
6
7
  @builder = builder
@@ -24,12 +25,9 @@ module SimpleForm
24
25
  SimpleForm.error_notification_tag
25
26
  end
26
27
 
27
- def has_errors?
28
- object && object.respond_to?(:errors) && object.errors.present?
29
- end
30
-
31
28
  def html_options
32
- @options[:class] = "error_notification #{@options[:class]}".strip
29
+ @options[:class] = "#{SimpleForm.error_notification_class} #{@options[:class]}".strip
30
+ @options[:id] = SimpleForm.error_notification_id if SimpleForm.error_notification_id
33
31
  @options
34
32
  end
35
33
 
@@ -41,4 +39,4 @@ module SimpleForm
41
39
  I18n.t(lookups.shift, :scope => :"simple_form.error_notification", :default => lookups)
42
40
  end
43
41
  end
44
- end
42
+ end
@@ -1,18 +1,17 @@
1
1
  module SimpleForm
2
2
  class FormBuilder < ActionView::Helpers::FormBuilder
3
- attr_reader :template, :object_name, :object, :attribute_name, :column,
4
- :reflection, :input_type, :options
3
+ attr_reader :template, :object_name, :object
5
4
 
6
5
  extend MapType
7
6
  include SimpleForm::Inputs
8
7
 
9
- map_type :password, :text, :file, :to => SimpleForm::Inputs::MappingInput
10
- map_type :string, :email, :url, :to => SimpleForm::Inputs::StringInput
11
- map_type :integer, :decimal, :float, :to => SimpleForm::Inputs::NumericInput
12
- map_type :select, :radio, :check_boxes, :to => SimpleForm::Inputs::CollectionInput
13
- map_type :date, :time, :datetime, :to => SimpleForm::Inputs::DateTimeInput
14
- map_type :country, :time_zone, :to => SimpleForm::Inputs::PriorityInput
15
- map_type :boolean, :to => SimpleForm::Inputs::BooleanInput
8
+ map_type :password, :text, :file, :to => SimpleForm::Inputs::MappingInput
9
+ map_type :string, :email, :search, :tel, :url, :to => SimpleForm::Inputs::StringInput
10
+ map_type :integer, :decimal, :float, :to => SimpleForm::Inputs::NumericInput
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
+ map_type :boolean, :to => SimpleForm::Inputs::BooleanInput
16
15
 
17
16
  # Basic input helper, combines all components in the stack to generate
18
17
  # input html based on options the user define and some guesses through
@@ -80,14 +79,14 @@ module SimpleForm
80
79
  # given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectivelly.
81
80
  #
82
81
  def input(attribute_name, options={}, &block)
83
- define_simple_form_attributes(attribute_name, options)
82
+ column = find_attribute_column(attribute_name)
83
+ input_type = default_input_type(attribute_name, column, options)
84
84
 
85
85
  if block_given?
86
- SimpleForm::Inputs::BlockInput.new(self, block).render
86
+ SimpleForm::Inputs::BlockInput.new(self, attribute_name, column, input_type, options, &block).render
87
87
  else
88
- klass = self.class.mappings[input_type] ||
89
- self.class.const_get(:"#{input_type.to_s.camelize}Input")
90
- klass.new(self).render
88
+ klass = self.class.mappings[input_type] || self.class.const_get("#{input_type.to_s.camelize}Input")
89
+ klass.new(self, attribute_name, column, input_type, options).render
91
90
  end
92
91
  end
93
92
  alias :attribute :input
@@ -124,30 +123,28 @@ module SimpleForm
124
123
 
125
124
  raise ArgumentError, "Association cannot be used in forms not associated with an object" unless @object
126
125
 
126
+ reflection = find_association_reflection(association)
127
+ raise "Association #{association.inspect} not found" unless reflection
128
+
127
129
  options[:as] ||= :select
128
- @reflection = find_association_reflection(association)
129
- raise "Association #{association.inspect} not found" unless @reflection
130
+ options[:collection] ||= reflection.klass.all(reflection.options.slice(:conditions, :order))
130
131
 
131
- case @reflection.macro
132
+ attribute = case reflection.macro
132
133
  when :belongs_to
133
- attribute = @reflection.options[:foreign_key] || :"#{@reflection.name}_id"
134
+ reflection.options[:foreign_key] || :"#{reflection.name}_id"
134
135
  when :has_one
135
136
  raise ":has_one association are not supported by f.association"
136
137
  else
137
- attribute = :"#{@reflection.name.to_s.singularize}_ids"
138
-
139
138
  if options[:as] == :select
140
139
  html_options = options[:input_html] ||= {}
141
140
  html_options[:size] ||= 5
142
141
  html_options[:multiple] = true unless html_options.key?(:multiple)
143
142
  end
144
- end
145
143
 
146
- options[:collection] ||= @reflection.klass.all(
147
- :conditions => @reflection.options[:conditions], :order => @reflection.options[:order]
148
- )
144
+ :"#{reflection.name.to_s.singularize}_ids"
145
+ end
149
146
 
150
- input(attribute, options).tap { @reflection = nil }
147
+ input(attribute, options.merge(:reflection => reflection))
151
148
  end
152
149
 
153
150
  # Creates a button:
@@ -159,8 +156,11 @@ module SimpleForm
159
156
  # It just acts as a proxy to method name given.
160
157
  #
161
158
  def button(type, *args, &block)
162
- if respond_to?(:"#{type}_button")
163
- send(:"#{type}_button", *args, &block)
159
+ options = args.extract_options!
160
+ options[:class] = "button #{options[:class]}".strip
161
+ args << options
162
+ if respond_to?("#{type}_button")
163
+ send("#{type}_button", *args, &block)
164
164
  else
165
165
  send(type, *args, &block)
166
166
  end
@@ -175,8 +175,10 @@ module SimpleForm
175
175
  # f.error :name, :id => "cool_error"
176
176
  #
177
177
  def error(attribute_name, options={})
178
- define_simple_form_attributes(attribute_name, :error_html => options)
179
- SimpleForm::Inputs::Base.new(self).error
178
+ options[:error_html] = options
179
+ column = find_attribute_column(attribute_name)
180
+ input_type = default_input_type(attribute_name, column, options)
181
+ SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).error
180
182
  end
181
183
 
182
184
  # Creates a hint tag for the given attribute. Accepts a symbol indicating
@@ -190,9 +192,15 @@ module SimpleForm
190
192
  # f.hint "Don't forget to accept this"
191
193
  #
192
194
  def hint(attribute_name, options={})
193
- attribute_name, options[:hint] = nil, attribute_name if attribute_name.is_a?(String)
194
- define_simple_form_attributes(attribute_name, :hint => options.delete(:hint), :hint_html => options)
195
- SimpleForm::Inputs::Base.new(self).hint
195
+ options[:hint_html] = options
196
+ if attribute_name.is_a?(String)
197
+ options[:hint] = attribute_name
198
+ attribute_name, column, input_type = nil, nil, nil
199
+ else
200
+ column = find_attribute_column(attribute_name)
201
+ input_type = default_input_type(attribute_name, column, options)
202
+ end
203
+ SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).hint
196
204
  end
197
205
 
198
206
  # Creates a default label tag for the given attribute. You can give a label
@@ -211,9 +219,12 @@ module SimpleForm
211
219
  def label(attribute_name, *args)
212
220
  return super if args.first.is_a?(String)
213
221
  options = args.extract_options!
214
- define_simple_form_attributes(attribute_name, :label => options.delete(:label),
215
- :label_html => options, :required => options.delete(:required))
216
- SimpleForm::Inputs::Base.new(self).label
222
+ options[:label] = options.delete(:label)
223
+ options[:label_html] = options
224
+ options[:required] = options.delete(:required)
225
+ column = find_attribute_column(attribute_name)
226
+ input_type = default_input_type(attribute_name, column, options)
227
+ SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).label
217
228
  end
218
229
 
219
230
  # Creates an error notification message that only appears when the form object
@@ -233,52 +244,47 @@ module SimpleForm
233
244
 
234
245
  private
235
246
 
236
- # Setup default simple form attributes.
237
- def define_simple_form_attributes(attribute_name, options) #:nodoc:
238
- @options = options
239
-
240
- if @attribute_name = attribute_name
241
- @column = find_attribute_column
242
- @input_type = default_input_type
243
- end
244
- end
245
-
246
247
  # Attempt to guess the better input type given the defined options. By
247
248
  # default alwayls fallback to the user :as option, or to a :select when a
248
249
  # collection is given.
249
- def default_input_type #:nodoc:
250
- return @options[:as].to_sym if @options[:as]
251
- return :select if @options[:collection]
250
+ def default_input_type(attribute_name, column, options) #:nodoc:
251
+ return options[:as].to_sym if options[:as]
252
+ return :select if options[:collection]
252
253
 
253
- input_type = @column.try(:type)
254
+ input_type = column.try(:type)
254
255
 
255
256
  case input_type
256
257
  when :timestamp
257
258
  :datetime
258
259
  when :string, nil
259
- match = case @attribute_name.to_s
260
+ match = case attribute_name.to_s
260
261
  when /password/ then :password
261
262
  when /time_zone/ then :time_zone
262
263
  when /country/ then :country
263
264
  when /email/ then :email
265
+ when /phone/ then :tel
264
266
  when /url/ then :url
267
+ else
268
+ SimpleForm.input_mappings.find { |match, type|
269
+ attribute_name.to_s =~ match
270
+ }.try(:last) if SimpleForm.input_mappings
265
271
  end
266
272
 
267
- match || input_type || file_method? || :string
273
+ match || input_type || file_method?(attribute_name) || :string
268
274
  else
269
275
  input_type
270
276
  end
271
277
  end
272
278
 
273
279
  # Checks if attribute is a file_method.
274
- def file_method? #:nodoc:
275
- file = @object.send(@attribute_name) if @object.respond_to?(@attribute_name)
280
+ def file_method?(attribute_name) #:nodoc:
281
+ file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
276
282
  :file if file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
277
283
  end
278
284
 
279
285
  # Finds the database column for the given attribute
280
- def find_attribute_column #:nodoc:
281
- @object.column_for_attribute(@attribute_name) if @object.respond_to?(:column_for_attribute)
286
+ def find_attribute_column(attribute_name) #:nodoc:
287
+ @object.column_for_attribute(attribute_name) if @object.respond_to?(:column_for_attribute)
282
288
  end
283
289
 
284
290
  # Find reflection related to association
@@ -0,0 +1,14 @@
1
+ module SimpleForm
2
+ module HasErrors
3
+
4
+ protected
5
+
6
+ def errors
7
+ object.errors
8
+ end
9
+
10
+ def has_errors?
11
+ object && object.respond_to?(:errors) && errors.present?
12
+ end
13
+ end
14
+ end
@@ -12,13 +12,25 @@ module SimpleForm
12
12
  include SimpleForm::Components::Errors
13
13
  include SimpleForm::Components::Hints
14
14
  include SimpleForm::Components::LabelInput
15
+ include SimpleForm::Components::Placeholders
15
16
  include SimpleForm::Components::Wrapper
16
17
 
17
- delegate :template, :object, :object_name, :attribute_name, :column,
18
- :reflection, :input_type, :options, :to => :@builder
19
-
20
- def initialize(builder)
21
- @builder = builder
18
+ attr_reader :attribute_name, :column, :input_type, :reflection,
19
+ :options, :input_html_options
20
+
21
+ delegate :template, :object, :object_name, :to => :@builder
22
+
23
+ def initialize(builder, attribute_name, column, input_type, options = {})
24
+ @builder = builder
25
+ @attribute_name = attribute_name
26
+ @column = column
27
+ @input_type = input_type
28
+ @reflection = options.delete(:reflection)
29
+ @options = options
30
+ @input_html_options = html_options_for(:input, input_html_classes).tap do |o|
31
+ o[:required] = true if attribute_required?
32
+ o[:disabled] = true if disabled?
33
+ end
22
34
  end
23
35
 
24
36
  def input
@@ -29,10 +41,6 @@ module SimpleForm
29
41
  options
30
42
  end
31
43
 
32
- def input_html_options
33
- html_options_for(:input, input_html_classes)
34
- end
35
-
36
44
  def input_html_classes
37
45
  [input_type, required_class]
38
46
  end
@@ -41,7 +49,8 @@ module SimpleForm
41
49
  content = "".html_safe
42
50
  components_list.each do |component|
43
51
  next if options[component] == false
44
- content.safe_concat send(component).to_s
52
+ rendered = send(component)
53
+ content.safe_concat rendered.to_s if rendered
45
54
  end
46
55
  wrap(content)
47
56
  end
@@ -49,19 +58,31 @@ module SimpleForm
49
58
  protected
50
59
 
51
60
  def components_list
52
- SimpleForm.components
61
+ options[:components] || SimpleForm.components
53
62
  end
54
63
 
55
64
  def attribute_required?
56
- if options.key?(:required)
65
+ if !options[:required].nil?
57
66
  options[:required]
58
- elsif object.class.respond_to?(:validators_on)
59
- object.class.validators_on(attribute_name).any? { |v| v.kind == :presence }
67
+ elsif has_validators?
68
+ (attribute_validators + reflection_validators).any? { |v| v.kind == :presence }
60
69
  else
61
70
  attribute_required_by_default?
62
71
  end
63
72
  end
64
73
 
74
+ def has_validators?
75
+ object.class.respond_to?(:validators_on)
76
+ end
77
+
78
+ def attribute_validators
79
+ object.class.validators_on(attribute_name)
80
+ end
81
+
82
+ def reflection_validators
83
+ reflection ? object.class.validators_on(reflection.name) : []
84
+ end
85
+
65
86
  def attribute_required_by_default?
66
87
  SimpleForm.required_by_default
67
88
  end
@@ -82,6 +103,10 @@ module SimpleForm
82
103
  html_options
83
104
  end
84
105
 
106
+ def disabled?
107
+ options[:disabled]
108
+ end
109
+
85
110
  # Lookup translations for the given namespace using I18n, based on object name,
86
111
  # actual action and attribute name. Lookup priority as follows:
87
112
  #
@@ -107,19 +132,22 @@ module SimpleForm
107
132
  #
108
133
  # Take a look at our locale example file.
109
134
  def translate(namespace, default='')
135
+ return nil unless SimpleForm.translate
110
136
  lookups = []
111
137
  lookups << :"#{object_name}.#{lookup_action}.#{reflection_or_attribute_name}"
112
138
  lookups << :"#{object_name}.#{reflection_or_attribute_name}"
113
139
  lookups << :"#{reflection_or_attribute_name}"
114
140
  lookups << default
115
- I18n.t(lookups.shift, :scope => :"simple_form.#{namespace}", :default => lookups)
141
+ I18n.t(lookups.shift, :scope => :"simple_form.#{namespace}", :default => lookups).presence
116
142
  end
117
143
 
118
144
  # The action to be used in lookup.
119
145
  def lookup_action
120
- action = template.controller.action_name.to_sym
146
+ action = template.controller.action_name
147
+ return unless action
148
+ action = action.to_sym
121
149
  ACTIONS[action] || action
122
150
  end
123
151
  end
124
152
  end
125
- end
153
+ end