formtastic 2.0.0.rc3 → 2.0.0.rc4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.travis.yml +7 -0
  2. data/CHANGELOG +26 -0
  3. data/Gemfile +2 -0
  4. data/README.textile +0 -1
  5. data/RELEASE_PROCESS +0 -1
  6. data/Rakefile +1 -1
  7. data/app/assets/stylesheets/formtastic.css +3 -3
  8. data/app/assets/stylesheets/formtastic_ie6.css +7 -1
  9. data/app/assets/stylesheets/formtastic_ie7.css +7 -1
  10. data/formtastic.gemspec +0 -15
  11. data/lib/formtastic/form_builder.rb +2 -0
  12. data/lib/formtastic/helpers/errors_helper.rb +22 -0
  13. data/lib/formtastic/helpers/form_helper.rb +18 -16
  14. data/lib/formtastic/helpers/input_helper.rb +9 -7
  15. data/lib/formtastic/helpers/inputs_helper.rb +11 -3
  16. data/lib/formtastic/helpers/reflection.rb +5 -1
  17. data/lib/formtastic/inputs/base/collections.rb +7 -0
  18. data/lib/formtastic/inputs/base/html.rb +1 -1
  19. data/lib/formtastic/inputs/base/timeish.rb +7 -2
  20. data/lib/formtastic/inputs/base/validations.rb +39 -8
  21. data/lib/formtastic/inputs/check_boxes_input.rb +3 -3
  22. data/lib/formtastic/inputs/file_input.rb +4 -4
  23. data/lib/formtastic/inputs/number_input.rb +1 -1
  24. data/lib/formtastic/inputs/radio_input.rb +1 -1
  25. data/lib/formtastic/inputs/time_input.rb +25 -3
  26. data/lib/formtastic/version.rb +1 -1
  27. data/lib/generators/templates/formtastic.rb +10 -1
  28. data/spec/builder/errors_spec.rb +10 -0
  29. data/spec/builder/semantic_fields_for_spec.rb +77 -36
  30. data/spec/helpers/form_helper_spec.rb +32 -0
  31. data/spec/helpers/input_helper_spec.rb +196 -102
  32. data/spec/helpers/inputs_helper_spec.rb +85 -73
  33. data/spec/helpers/reflection_helper_spec.rb +32 -0
  34. data/spec/inputs/check_boxes_input_spec.rb +21 -6
  35. data/spec/inputs/date_input_spec.rb +20 -0
  36. data/spec/inputs/datetime_input_spec.rb +30 -11
  37. data/spec/inputs/label_spec.rb +8 -0
  38. data/spec/inputs/number_input_spec.rb +298 -2
  39. data/spec/inputs/radio_input_spec.rb +5 -6
  40. data/spec/inputs/string_input_spec.rb +22 -5
  41. data/spec/inputs/time_input_spec.rb +51 -7
  42. data/spec/spec_helper.rb +64 -12
  43. metadata +11 -9
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - ree
4
+ - 1.9.2
5
+ - 1.9.3
6
+ env:
7
+ - DEFER_GC=false
data/CHANGELOG CHANGED
@@ -1,3 +1,29 @@
1
+ 2.0.0.rc4
2
+
3
+ * Fixed that TimeInput was not rendering hidden y/m/d inputs by default.
4
+ * Fixed test suite under Rails 3.1.0.rc5
5
+ * Fixed false and blank fragment labels on date/time inputs producing unsafe HTML.
6
+ * Fixed that inputs were 'required' withput considering `:on => :create` validations
7
+ * Fixed that collections of strings in CheckBoxesInput were ot being correctly checked be checked if they match the model
8
+ * Fixed that the required attribute was added to the choices in a :radio or :check_boxes input, instead of just the parent input wrapper
9
+ * Fixed semantic_fields_for when used with a hash-like model
10
+ * Fixed that models without defined validations (even if validators_on exists) should not be considered required
11
+ * Fixed min/max attributes when the validation uses a Proc
12
+ * Fixed that inputs should not be considered required if :allow_blank => true is set on validates_inclusion_of
13
+ * Fixed that inputs should not be considered required if if either :allow_blank => true, :minimum is > 0, or :within's least value is > 0 is set on validates_length_of
14
+ * Fixed a typo in the config template
15
+ * Fixed semantic_fields_for to work with Rails 3 *and* 3.1's method sigs (I hope), many thanks to the simple_form guys for figuring this out
16
+ * Changed HTML5 `step` attribute to default to "any" instead of "1"
17
+ * Added CarrierWave support for to file input detection
18
+ * Added a configuration 'perform_browser_validations' to opt out of HTML5 browser validations.
19
+ * Added more support for mongo documents, including MongoMapper-specific reflection capability
20
+ * Added IE specific stylesheets & moved those styles out of main stylesheet, include them yourself if needed
21
+ * Added a new configuration to opt out of HTML5 required attribute
22
+
23
+ 2.0.0.rc3
24
+
25
+ * Fixed that .label class was incorrectly applied to <label> tags inside a .choice on radio and checkbox inputs (#599)
26
+
1
27
  2.0.0.rc2
2
28
 
3
29
  * Fixed install instructions in readme to reflect 2.0.0.rc1
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
1
  source :rubygems
2
2
 
3
+ gem 'rake', '< 0.9'
4
+
3
5
  gemspec
data/README.textile CHANGED
@@ -586,7 +586,6 @@ h2. Dependencies
586
586
 
587
587
  There are none, but...
588
588
 
589
- * if you have the "ValidationReflection":http://github.com/redinger/validation_reflection plugin is installed, you won't have to specify the @:required@ option (it checks the validations on the model instead).
590
589
  * if you want to use the @:country@ input, you'll need to install the "country-select plugin":https://github.com/chrislerum/country_select (or any other country_select plugin with the same API).
591
590
  * "rspec":http://github.com/dchelimsky/rspec/, "rspec_hpricot_matchers":http://rubyforge.org/projects/rspec-hpricot/ and "rcov":http://github.com/relevance/rcov gems (plus any of their own dependencies) are required for the test suite.
592
591
 
data/RELEASE_PROCESS CHANGED
@@ -1,5 +1,4 @@
1
1
  # edit version.rb
2
- # edit gemspec release date
3
2
  git ci formtastic.gemspec -m "new gemspec" # commit changes
4
3
  git tag X.X.X # tag the new version in the code base too
5
4
  gem build formtastic.gemspec # build the gem
data/Rakefile CHANGED
@@ -44,7 +44,7 @@ end
44
44
 
45
45
  RCov::VerifyTask.new(:verify_coverage) do |t|
46
46
  t.require_exact_threshold = false
47
- t.threshold = 95
47
+ t.threshold = (RUBY_VERSION == "1.8.7" ? 95 : 0)
48
48
  end
49
49
 
50
50
  desc "Run all examples and verify coverage"
@@ -83,7 +83,7 @@ This stylesheet forms part of the Formtastic Rails Plugin
83
83
  /* BUTTONS
84
84
  --------------------------------------------------------------------------------------------------*/
85
85
  .formtastic .buttons {
86
- overflow:hidden; zoom:1; /* clear containing floats */
86
+ overflow:hidden; /* clear containing floats */
87
87
  padding-left:25%;
88
88
  }
89
89
 
@@ -96,11 +96,11 @@ This stylesheet forms part of the Formtastic Rails Plugin
96
96
  /* INPUTS
97
97
  --------------------------------------------------------------------------------------------------*/
98
98
  .formtastic .inputs {
99
- overflow:hidden; zoom:1; /* clear containing floats */
99
+ overflow:hidden; /* clear containing floats */
100
100
  }
101
101
 
102
102
  .formtastic .input {
103
- overflow:hidden; zoom:1; /* clear containing floats */
103
+ overflow:hidden; /* clear containing floats */
104
104
  padding:0.5em 0; /* padding and negative margin juggling is for Firefox */
105
105
  margin-top:-0.5em;
106
106
  margin-bottom:1em;
@@ -24,4 +24,10 @@
24
24
  /* fragment (eg year, month, day) appear a few pixels too far to the left*/
25
25
  .formtastic .fragment {
26
26
  padding-left:3px;
27
- }
27
+ }
28
+
29
+ .formtastic .buttons,
30
+ .formtastic .inputs,
31
+ .formtastic .input {
32
+ zoom:1;
33
+ }
@@ -14,4 +14,10 @@
14
14
  size:15px;
15
15
  margin-left:0;
16
16
  margin-right:0;
17
- }
17
+ }
18
+
19
+ .formtastic .buttons,
20
+ .formtastic .inputs,
21
+ .formtastic .input {
22
+ zoom:1;
23
+ }
data/formtastic.gemspec CHANGED
@@ -17,21 +17,6 @@ Gem::Specification.new do |s|
17
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
18
  s.require_paths = ["lib"]
19
19
 
20
- s.post_install_message = %q{
21
- ========================================================================
22
- Thanks for installing Formtastic!
23
- ------------------------------------------------------------------------
24
- You can now (optionally) run the generator to copy a config initializer
25
- (and some stylesheets, in Rails 3.0.x) into your application:
26
- rails generate formtastic:install
27
-
28
- Find out more and get involved:
29
- http://github.com/justinfrench/formtastic
30
- http://rdoc.info/github/justinfrench/formtastic/master/frames
31
- http://groups.google.com.au/group/formtastic
32
- http://twitter.com/formtastic
33
- ========================================================================
34
- }
35
20
  s.rdoc_options = ["--charset=UTF-8"]
36
21
  s.extra_rdoc_files = ["README.textile"]
37
22
 
@@ -28,6 +28,8 @@ module Formtastic
28
28
  configure :default_inline_error_class, 'inline-errors'
29
29
  configure :default_error_list_class, 'errors'
30
30
  configure :default_hint_class, 'inline-hints'
31
+ configure :use_required_attribute, true
32
+ configure :perform_browser_validations, true
31
33
 
32
34
  attr_reader :template
33
35
 
@@ -89,6 +89,28 @@ module Formtastic
89
89
 
90
90
 
91
91
  protected
92
+
93
+ # @deprecated This should be removed with inline_errors_for in 2.1
94
+ def error_sentence(errors, options = {})
95
+ error_class = options[:error_class] || default_inline_error_class
96
+ template.content_tag(:p, Formtastic::Util.html_safe(errors.to_sentence.untaint), :class => error_class)
97
+ end
98
+
99
+ # @deprecated This should be removed with inline_errors_for in 2.1
100
+ def error_list(errors, options = {})
101
+ error_class = options[:error_class] || default_error_list_class
102
+ list_elements = []
103
+ errors.each do |error|
104
+ list_elements << template.content_tag(:li, Formtastic::Util.html_safe(error.untaint))
105
+ end
106
+ template.content_tag(:ul, Formtastic::Util.html_safe(list_elements.join("\n")), :class => error_class)
107
+ end
108
+
109
+ # @deprecated This should be removed with inline_errors_for in 2.1
110
+ def error_first(errors, options = {})
111
+ error_class = options[:error_class] || default_inline_error_class
112
+ template.content_tag(:p, Formtastic::Util.html_safe(errors.first.untaint), :class => error_class)
113
+ end
92
114
 
93
115
  def error_keys(method, options)
94
116
  @methods_for_error ||= {}
@@ -140,24 +140,10 @@ module Formtastic
140
140
  #
141
141
  # @option *args [String] :namespace
142
142
  def semantic_form_for(record_or_name_or_array, *args, &proc)
143
- form_helper_wrapper(:form_for, record_or_name_or_array, *args, &proc)
144
- end
145
-
146
- # Wrapper around Rails' own `fields_for` helper to set the `:builder` option to
147
- # `Formtastic::FormBuilder`.
148
- #
149
- # @see #semantic_form_for
150
- def semantic_fields_for(record_or_name_or_array, *args, &proc)
151
- form_helper_wrapper(:fields_for, record_or_name_or_array, *args, &proc)
152
- end
153
-
154
- protected
155
-
156
- # @todo pretty sure some of this (like HTML classes and record naming are exlusive to `form_for`)
157
- def form_helper_wrapper(rails_helper_method_name, record_or_name_or_array, *args, &proc)
158
143
  options = args.extract_options!
159
144
  options[:builder] ||= @@builder
160
145
  options[:html] ||= {}
146
+ options[:html][:novalidate] = !builder.perform_browser_validations unless options[:html].key?(:novalidate)
161
147
  @@builder.custom_namespace = options[:namespace].to_s
162
148
 
163
149
  singularizer = defined?(ActiveModel::Naming.singular) ? ActiveModel::Naming.method(:singular) : ActionController::RecordIdentifier.method(:singular_class_name)
@@ -172,10 +158,26 @@ module Formtastic
172
158
  options[:html][:class] = class_names.join(" ")
173
159
 
174
160
  with_custom_field_error_proc do
175
- self.send(rails_helper_method_name, record_or_name_or_array, *(args << options), &proc)
161
+ self.form_for(record_or_name_or_array, *(args << options), &proc)
176
162
  end
177
163
  end
178
164
 
165
+ # Wrapper around Rails' own `fields_for` helper to set the `:builder` option to
166
+ # `Formtastic::FormBuilder`.
167
+ #
168
+ # @see #semantic_form_for
169
+ def semantic_fields_for(record_name, record_object = nil, options = {}, &block)
170
+ options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
171
+ options[:builder] ||= @@builder
172
+ @@builder.custom_namespace = options[:namespace].to_s # TODO needed?
173
+
174
+ with_custom_field_error_proc do
175
+ self.fields_for(record_name, record_object, options, &block)
176
+ end
177
+ end
178
+
179
+ protected
180
+
179
181
  # Override the default ActiveRecordHelper behaviour of wrapping the input.
180
182
  # This gets taken care of semantically by adding an error class to the LI tag
181
183
  # containing the input.
@@ -267,6 +267,9 @@ module Formtastic
267
267
 
268
268
  protected
269
269
 
270
+ # First try if we can detect special things like :file. With CarrierWave the method does have
271
+ # an underlying column so we don't want :string to get selected.
272
+ #
270
273
  # For methods that have a database column, take a best guess as to what the input method
271
274
  # should be. In most cases, it will just return the column type (eg :string), but for special
272
275
  # cases it will simplify (like the case of :integer, :float & :decimal to :number), or do
@@ -275,6 +278,12 @@ module Formtastic
275
278
  # If there is no column for the method (eg "virtual columns" with an attr_accessor), the
276
279
  # default is a :string, a similar behaviour to Rails' scaffolding.
277
280
  def default_input_type(method, options = {}) #:nodoc:
281
+ if @object
282
+ return :select if reflection_for(method)
283
+
284
+ return :file if is_file?(method, options)
285
+ end
286
+
278
287
  if column = column_for(method)
279
288
  # Special cases where the column type doesn't map to an input method.
280
289
  case column.type
@@ -300,12 +309,6 @@ module Formtastic
300
309
  # Try 3: Assume the input name will be the same as the column type (e.g. string_input).
301
310
  return column.type
302
311
  else
303
- if @object
304
- return :select if reflection_for(method)
305
-
306
- return :file if is_file?(method, options)
307
- end
308
-
309
312
  return :select if options.key?(:collection)
310
313
  return :password if method.to_s =~ /password/
311
314
  return :string
@@ -363,4 +366,3 @@ module Formtastic
363
366
  end
364
367
  end
365
368
  end
366
-
@@ -314,9 +314,17 @@ module Formtastic
314
314
  end
315
315
 
316
316
  def fieldset_contents_from_column_list(columns)
317
- columns.collect do |method|
318
- if @object && (@object.class.reflect_on_association(method.to_sym) && @object.class.reflect_on_association(method.to_sym).options[:polymorphic] == true)
319
- raise PolymorphicInputWithoutCollectionError.new("Please provide a collection for :#{method} input (you'll need to use block form syntax). Inputs for polymorphic associations can only be used when an explicit :collection is provided.")
317
+ columns.collect do |method|
318
+ if @object
319
+ if @object.class.respond_to?(:reflect_on_association)
320
+ if (@object.class.reflect_on_association(method.to_sym) && @object.class.reflect_on_association(method.to_sym).options[:polymorphic] == true)
321
+ raise PolymorphicInputWithoutCollectionError.new("Please provide a collection for :#{method} input (you'll need to use block form syntax). Inputs for polymorphic associations can only be used when an explicit :collection is provided.")
322
+ end
323
+ elsif @object.class.respond_to?(:associations)
324
+ if (@object.class.associations(method.to_sym) && @object.class.associations(method.to_sym).options[:polymorphic] == true)
325
+ raise PolymorphicInputWithoutCollectionError.new("Please provide a collection for :#{method} input (you'll need to use block form syntax). Inputs for polymorphic associations can only be used when an explicit :collection is provided.")
326
+ end
327
+ end
320
328
  end
321
329
  input(method.to_sym)
322
330
  end
@@ -5,7 +5,11 @@ module Formtastic
5
5
  # If an association method is passed in (f.input :author) try to find the
6
6
  # reflection object.
7
7
  def reflection_for(method) #:nodoc:
8
- @object.class.reflect_on_association(method) if @object.class.respond_to?(:reflect_on_association)
8
+ if @object.class.respond_to?(:reflect_on_association)
9
+ @object.class.reflect_on_association(method)
10
+ elsif @object.class.respond_to?(:associations) # MongoMapper uses the 'associations(method)' instead
11
+ @object.class.associations(method)
12
+ end
9
13
  end
10
14
 
11
15
  def association_macro_for_method(method) #:nodoc:
@@ -87,6 +87,13 @@ module Formtastic
87
87
  object.send(duck)
88
88
  end
89
89
  end
90
+
91
+ # Avoids an issue where `send_or_call` can be a String and duck can be something simple like
92
+ # `:first`, which obviously String responds to.
93
+ def send_or_call_or_object(duck, object)
94
+ return object if object.is_a?(String) # TODO what about other classes etc?
95
+ send_or_call(duck, object)
96
+ end
90
97
 
91
98
  end
92
99
  end
@@ -21,7 +21,7 @@ module Formtastic
21
21
  def input_html_options
22
22
  {
23
23
  :id => dom_id,
24
- :required => required?,
24
+ :required => required_attribute?,
25
25
  :autofocus => autofocus?
26
26
  }.merge(options[:input_html] || {})
27
27
  end
@@ -90,6 +90,7 @@ module Formtastic
90
90
  def to_html
91
91
  input_wrapping do
92
92
  fragments_wrapping do
93
+ hidden_fragments <<
93
94
  fragments_label <<
94
95
  template.content_tag(:ol,
95
96
  fragments.map do |fragment|
@@ -147,7 +148,7 @@ module Formtastic
147
148
 
148
149
  def fragment_label_html(fragment)
149
150
  text = fragment_label(fragment)
150
- text.blank? ? "" : template.content_tag(:label, text, :for => fragment_id(fragment))
151
+ text.blank? ? "".html_safe : template.content_tag(:label, text, :for => fragment_id(fragment))
151
152
  end
152
153
 
153
154
  def value
@@ -196,7 +197,7 @@ module Formtastic
196
197
  :class => "label"
197
198
  )
198
199
  else
199
- ""
200
+ "".html_safe
200
201
  end
201
202
  end
202
203
 
@@ -206,6 +207,10 @@ module Formtastic
206
207
  )
207
208
  end
208
209
 
210
+ def hidden_fragments
211
+ "".html_safe
212
+ end
213
+
209
214
  end
210
215
  end
211
216
  end
@@ -27,7 +27,7 @@ module Formtastic
27
27
  validator_relevant?(validator)
28
28
  end
29
29
  else
30
- []
30
+ nil
31
31
  end
32
32
  end
33
33
 
@@ -70,8 +70,15 @@ module Formtastic
70
70
  # We can't determine an appropriate value for :greater_than with a float/decimal column
71
71
  raise IndeterminableMinimumAttributeError if validation.options[:greater_than] && column? && [:float, :decimal].include?(column.type)
72
72
 
73
- return validation.options[:greater_than_or_equal_to] if validation.options[:greater_than_or_equal_to]
74
- return (validation.options[:greater_than] + 1) if validation.options[:greater_than]
73
+ if validation.options[:greater_than_or_equal_to]
74
+ return (validation.options[:greater_than_or_equal_to].call(object)) if validation.options[:greater_than_or_equal_to].kind_of?(Proc)
75
+ return (validation.options[:greater_than_or_equal_to])
76
+ end
77
+
78
+ if validation.options[:greater_than]
79
+ return (validation.options[:greater_than].call(object) + 1) if validation.options[:greater_than].kind_of?(Proc)
80
+ return (validation.options[:greater_than] + 1)
81
+ end
75
82
  end
76
83
  end
77
84
 
@@ -84,8 +91,15 @@ module Formtastic
84
91
  # We can't determine an appropriate value for :greater_than with a float/decimal column
85
92
  raise IndeterminableMaximumAttributeError if validation.options[:less_than] && column? && [:float, :decimal].include?(column.type)
86
93
 
87
- return validation.options[:less_than_or_equal_to] if validation.options[:less_than_or_equal_to]
88
- return (validation.options[:less_than] - 1) if validation.options[:less_than]
94
+ if validation.options[:less_than_or_equal_to]
95
+ return (validation.options[:less_than_or_equal_to].call(object)) if validation.options[:less_than_or_equal_to].kind_of?(Proc)
96
+ return (validation.options[:less_than_or_equal_to])
97
+ end
98
+
99
+ if validation.options[:less_than]
100
+ return ((validation.options[:less_than].call(object)) - 1) if validation.options[:less_than].kind_of?(Proc)
101
+ return (validation.options[:less_than] - 1)
102
+ end
89
103
  end
90
104
  end
91
105
 
@@ -112,7 +126,7 @@ module Formtastic
112
126
  end
113
127
 
114
128
  def validations?
115
- !validations.empty?
129
+ validations != nil
116
130
  end
117
131
 
118
132
  def required?
@@ -121,14 +135,31 @@ module Formtastic
121
135
  return false if not_required_through_negated_validation?
122
136
  if validations?
123
137
  validations.select { |validator|
124
- [:presence, :inclusion, :length].include?(validator.kind) &&
125
- validator.options[:allow_blank] != true
138
+ if validator.options.key?(:on)
139
+ return false if (validator.options[:on] != :save) && ((object.new_record? && validator.options[:on] != :create) || (!object.new_record? && validator.options[:on] != :update))
140
+ end
141
+ case validator.kind
142
+ when :presence
143
+ true
144
+ when :inclusion
145
+ validator.options[:allow_blank] != true
146
+ when :length
147
+ validator.options[:allow_blank] != true &&
148
+ validator.options[:minimum].to_i > 0 ||
149
+ validator.options[:within].try(:first).to_i > 0
150
+ else
151
+ false
152
+ end
126
153
  }.any?
127
154
  else
128
155
  return responds_to_global_required? && !!builder.all_fields_required_by_default
129
156
  end
130
157
  end
131
158
 
159
+ def required_attribute?
160
+ required? && builder.use_required_attribute
161
+ end
162
+
132
163
  def not_required_through_negated_validation?
133
164
  @not_required_through_negated_validation
134
165
  end