simple_form 1.2.2 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.

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