formtastic-rails3 0.9.7 → 0.9.10.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.
Files changed (50) hide show
  1. data/README.textile +23 -7
  2. data/Rakefile +27 -4
  3. data/generators/formtastic/templates/formtastic.css +9 -7
  4. data/generators/formtastic/templates/formtastic.rb +1 -1
  5. data/generators/formtastic/templates/formtastic_changes.css +4 -0
  6. data/lib/formtastic.rb +190 -142
  7. data/lib/formtastic/i18n.rb +8 -5
  8. data/lib/formtastic/railtie.rb +12 -0
  9. data/lib/formtastic/util.rb +35 -0
  10. data/lib/generators/formtastic/form/form_generator.rb +4 -3
  11. data/lib/generators/formtastic/install/install_generator.rb +2 -1
  12. data/rails/init.rb +5 -0
  13. data/spec/buttons_spec.rb +25 -8
  14. data/spec/commit_button_spec.rb +88 -64
  15. data/spec/custom_builder_spec.rb +1 -1
  16. data/spec/custom_macros.rb +67 -26
  17. data/spec/error_proc_spec.rb +1 -1
  18. data/spec/errors_spec.rb +21 -1
  19. data/spec/form_helper_spec.rb +47 -15
  20. data/spec/i18n_spec.rb +40 -19
  21. data/spec/include_blank_spec.rb +9 -5
  22. data/spec/input_spec.rb +224 -76
  23. data/spec/inputs/boolean_input_spec.rb +22 -11
  24. data/spec/inputs/check_boxes_input_spec.rb +103 -11
  25. data/spec/inputs/country_input_spec.rb +46 -8
  26. data/spec/inputs/date_input_spec.rb +80 -55
  27. data/spec/inputs/datetime_input_spec.rb +134 -83
  28. data/spec/inputs/file_input_spec.rb +4 -3
  29. data/spec/inputs/hidden_input_spec.rb +17 -3
  30. data/spec/inputs/numeric_input_spec.rb +3 -3
  31. data/spec/inputs/password_input_spec.rb +3 -3
  32. data/spec/inputs/radio_input_spec.rb +28 -11
  33. data/spec/inputs/select_input_spec.rb +122 -46
  34. data/spec/inputs/string_input_spec.rb +3 -3
  35. data/spec/inputs/text_input_spec.rb +4 -3
  36. data/spec/inputs/time_input_spec.rb +109 -53
  37. data/spec/inputs/time_zone_input_spec.rb +15 -7
  38. data/spec/inputs_spec.rb +85 -39
  39. data/spec/label_spec.rb +1 -1
  40. data/spec/layout_helper_spec.rb +5 -16
  41. data/spec/semantic_errors_spec.rb +7 -7
  42. data/spec/semantic_fields_for_spec.rb +5 -4
  43. data/spec/spec_helper.rb +102 -36
  44. metadata +11 -14
  45. data/lib/generators/formtastic/form/templates/_form.html.erb +0 -5
  46. data/lib/generators/formtastic/form/templates/_form.html.haml +0 -4
  47. data/lib/generators/formtastic/install/templates/formtastic.css +0 -144
  48. data/lib/generators/formtastic/install/templates/formtastic.rb +0 -58
  49. data/lib/generators/formtastic/install/templates/formtastic_changes.css +0 -10
  50. data/spec/inputs/currency_input_spec.rb +0 -80
@@ -173,7 +173,7 @@ If you want to customize the label text, or render some hint text below the fiel
173
173
  <% end %>
174
174
  </pre>
175
175
 
176
- Nested forms (Rails 2.3) are also supported. You can do it in the Rails way:
176
+ Nested forms (Rails 2.3) are also supported (don't forget your models need to be setup correctly with accepts_nested_attributes_for – search the Rails docs). You can do it in the Rails way:
177
177
 
178
178
  <pre>
179
179
  <% semantic_form_for @post do |form| %>
@@ -206,7 +206,7 @@ When working in has many association, you can even supply @"%i"@ in your fieldse
206
206
  </pre>
207
207
 
208
208
 
209
- Customize HTML attributes for any input using the @:input_html@ option. Typically his is used to disable the input, change the size of a text field, change the rows in a textarea, or even to add a special class to an input to attach special behavior like "autogrow":http://plugins.jquery.com/project/autogrow textareas:
209
+ Customize HTML attributes for any input using the @:input_html@ option. Typically this is used to disable the input, change the size of a text field, change the rows in a textarea, or even to add a special class to an input to attach special behavior like "autogrow":http://plugins.jquery.com/project/autogrow textareas:
210
210
 
211
211
  <pre>
212
212
  <% semantic_form_for @post do |form| %>
@@ -239,6 +239,23 @@ Customize the HTML attributes for the @<li>@ wrapper around every input with the
239
239
  <% end %>
240
240
  </pre>
241
241
 
242
+ Many inputs provide a collection of options to choose from (like @:select@, @:radio@, @:check_boxes@, @:boolean@). In many cases, Formtastic can find choices through the model associations, but if you want to use your own set of choices, the @:collection@ option is what you want. You can pass in an Array of objects, an array of Strings, a Hash... Throw almost anything at it! Examples:
243
+
244
+ <pre>
245
+ f.input :authors, :as => :check_boxes, :collection => User.find(:all, :order => "last_name ASC")
246
+ f.input :authors, :as => :check_boxes, :collection => current_user.company.users.active
247
+ f.input :authors, :as => :check_boxes, :collection => [@justin, @kate]
248
+ f.input :authors, :as => :check_boxes, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
249
+ f.input :author, :as => :select, :collection => Author.find(:all)
250
+ f.input :author, :as => :select, :collection => { @justin.name => @justin.id, @kate.name => @kate.id }
251
+ f.input :author, :as => :select, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
252
+ f.input :author, :as => :radio, :collection => User.find(:all)
253
+ f.input :author, :as => :radio, :collection => [@justin, @kate]
254
+ f.input :author, :as => :radio, :collection => { @justin.name => @justin.id, @kate.name => @kate.id }
255
+ f.input :author, :as => :radio, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
256
+ f.input :admin, :as => :radio, :collection => ["Yes!", "No"]
257
+ </pre>
258
+
242
259
 
243
260
  h2. The Available Inputs
244
261
 
@@ -258,10 +275,10 @@ The Formtastic input types:
258
275
  * @:numeric@ - a text field (just like string). Default for column types: @:integer@, @:float@, and @:decimal@.
259
276
  * @:file@ - a file field. Default for file-attachment attributes matching: "paperclip":http://github.com/thoughtbot/paperclip or "attachment_fu":http://github.com/technoweenie/attachment_fu.
260
277
  * @:country@ - a select menu of country names. Default for column types: :string with name @"country"@ - requires a *country_select* plugin to be installed.
261
- * @:currency@ - a select menu of currencies. Default for column types: :string with name @"currency"@ - requires a *currency_select* plugin to be installed.
262
278
  * @:hidden@ - a hidden field. Creates a hidden field (added for compatibility).
263
279
 
264
- The documentation is pretty good for each of these (what it does, what the output is, what the options are, etc.) so go check it out.
280
+ The comments in the code are pretty good for each of these (what it does, what the output is, what the options are, etc.) so go check it out.
281
+
265
282
 
266
283
  h2. Delegation for label lookups
267
284
 
@@ -320,7 +337,7 @@ Formtastic supports localized *labels*, *hints*, *legends*, *actions* using the
320
337
  title: "Choose a good title for you post."
321
338
  body: "Write something inspiring here."
322
339
  actions:
323
- create: "Create my {{model}}"
340
+ create: "Create my %{model}"
324
341
  update: "Save changes"
325
342
  dummie: "Launch!"
326
343
  </pre>
@@ -337,7 +354,7 @@ Formtastic supports localized *labels*, *hints*, *legends*, *actions* using the
337
354
  <%= form.input :section %> # => :label => I18n.t('activerecord.attributes.user.section') or 'Section'
338
355
  <% end %>
339
356
  <% form.buttons do %>
340
- <%= form.commit_button %> # => "Create my {{model}}"
357
+ <%= form.commit_button %> # => "Create my %{model}"
341
358
  <% end %>
342
359
  <% end %>
343
360
  </pre>
@@ -514,7 +531,6 @@ There are none, but...
514
531
 
515
532
  * 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).
516
533
  * if you want to use the @:country@ input, you'll need to install the "iso-3166-country-select plugin":http://github.com/rails/iso-3166-country-select (or any other country_select plugin with the same API).
517
- * if you want to use the @:currency@ input, you'll need to install the "currency_select plugin":http://github.com/gavinlaking/currency_select (or any other currency_select plugin with the same API).
518
534
  * "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.
519
535
 
520
536
 
data/Rakefile CHANGED
@@ -1,13 +1,17 @@
1
1
  # coding: utf-8
2
+ require 'rubygems'
2
3
  require 'rake'
3
4
  require 'rake/rdoctask'
4
5
 
5
6
  begin
7
+ gem 'rspec', '>= 1.2.6'
8
+ gem 'rspec-rails', '>= 1.2.6'
9
+ require 'spec'
6
10
  require 'spec/rake/spectask'
7
11
  rescue LoadError
8
12
  begin
9
- gem 'rspec-rails', '>= 1.0.0'
10
- require 'spec/rake/spectask'
13
+ require 'rspec/core/rake_task.rb'
14
+ require 'rspec/core/version'
11
15
  rescue LoadError
12
16
  puts "[formtastic:] RSpec - or one of it's dependencies - is not available. Install it with: sudo gem install rspec-rails"
13
17
  end
@@ -55,8 +59,8 @@ begin
55
59
 
56
60
  # Runtime dependencies: When installing Formtastic these will be checked if they are installed.
57
61
  # Will be offered to install these if they are not already installed.
58
- s.add_dependency 'activesupport', '>= 2.3.0'
59
- s.add_dependency 'actionpack', '>= 2.3.0'
62
+ s.add_dependency 'activesupport', '>= 3.0.0beta3'
63
+ s.add_dependency 'actionpack', '>= 3.0.0beta3'
60
64
 
61
65
  # Development dependencies. Not installed by default.
62
66
  # Install with: sudo gem install formtastic --development
@@ -101,3 +105,22 @@ if defined?(Spec)
101
105
  t.rcov_opts = ['--exclude', 'spec,Library']
102
106
  end
103
107
  end
108
+
109
+ if defined?(Rspec)
110
+ desc 'Test the formtastic plugin.'
111
+ Rspec::Core::RakeTask.new('spec') do |t|
112
+ t.pattern = FileList['spec/**/*_spec.rb']
113
+ end
114
+
115
+ desc 'Test the formtastic plugin with specdoc formatting and colors'
116
+ Rspec::Core::RakeTask.new('specdoc') do |t|
117
+ t.pattern = FileList['spec/**/*_spec.rb']
118
+ end
119
+
120
+ desc "Run all examples with RCov"
121
+ Rspec::Core::RakeTask.new('examples_with_rcov') do |t|
122
+ t.pattern = FileList['spec/**/*_spec.rb']
123
+ t.rcov = true
124
+ t.rcov_opts = ['--exclude', 'spec,Library']
125
+ end
126
+ end
@@ -19,7 +19,7 @@ form.formtastic ol, form.formtastic ul { list-style:none; }
19
19
  form.formtastic abbr, form.formtastic acronym { border:0; font-variant:normal; }
20
20
  form.formtastic input, form.formtastic textarea, form.formtastic select { font-family:inherit; font-size:inherit; font-weight:inherit; }
21
21
  form.formtastic input, form.formtastic textarea, form.formtastic select { font-size:100%; }
22
- form.formtastic legend { color:#000; }
22
+ form.formtastic legend { white-space:normal; color:#000; }
23
23
 
24
24
 
25
25
  /* SEMANTIC ERRORS
@@ -68,7 +68,7 @@ form.formtastic fieldset > ol > li > li label input { line-height:100%; vertical
68
68
  /* NESTED FIELDSETS AND LEGENDS (radio, check boxes and date/time inputs use nested fieldsets)
69
69
  --------------------------------------------------------------------------------------------------*/
70
70
  form.formtastic fieldset > ol > li fieldset { position:relative; }
71
- form.formtastic fieldset > ol > li fieldset legend { position:absolute; width:25%; padding-top:0.1em; }
71
+ form.formtastic fieldset > ol > li fieldset legend { position:absolute; width:95%; padding-top:0.1em; left: 0px; }
72
72
  form.formtastic fieldset > ol > li fieldset legend span { position:absolute; }
73
73
  form.formtastic fieldset > ol > li fieldset legend.label label { position:absolute; }
74
74
  form.formtastic fieldset > ol > li fieldset ol { float:left; width:74%; margin:0; padding:0 0 0 25%; }
@@ -89,9 +89,9 @@ form.formtastic fieldset > ol > li ul.errors li { padding:0; border:none; displa
89
89
 
90
90
  /* STRING & NUMERIC OVERRIDES
91
91
  --------------------------------------------------------------------------------------------------*/
92
- form.formtastic fieldset > ol > li.string input { width:74%; }
93
- form.formtastic fieldset > ol > li.password input { width:74%; }
94
- form.formtastic fieldset > ol > li.numeric input { width:74%; }
92
+ form.formtastic fieldset > ol > li.string input { max-width:74%; }
93
+ form.formtastic fieldset > ol > li.password input { max-width: 13em; }
94
+ form.formtastic fieldset > ol > li.numeric input { max-width:74%; }
95
95
 
96
96
 
97
97
  /* TEXTAREA OVERRIDES
@@ -100,9 +100,11 @@ form.formtastic fieldset > ol > li.text textarea { width:74%; }
100
100
 
101
101
 
102
102
  /* HIDDEN OVERRIDES
103
+ The dual declarations are required because of our clearfix display hack on the LIs, which is more
104
+ specific than the more general rule below. TODO: Revist the clearing hack and this rule.
103
105
  --------------------------------------------------------------------------------------------------*/
104
- form.formtastic fieldset > ol > li.hidden { display:none; }
105
-
106
+ form.formtastic fieldset ol li.hidden,
107
+ html[xmlns] form.formtastic fieldset ol li.hidden { display:none; }
106
108
 
107
109
  /* BOOLEAN OVERRIDES
108
110
  --------------------------------------------------------------------------------------------------*/
@@ -28,7 +28,7 @@
28
28
  # Formtastic::SemanticFormBuilder.inline_errors = :sentence
29
29
 
30
30
  # Set the method to call on label text to transform or format it for human-friendly
31
- # reading when formtastic is user without object. Defaults to :humanize.
31
+ # reading when formtastic is used without object. Defaults to :humanize.
32
32
  # Formtastic::SemanticFormBuilder.label_str_method = :humanize
33
33
 
34
34
  # Set the array of methods to try calling on parent objects in :select and :radio inputs
@@ -7,4 +7,8 @@ For example, to make the inline hint paragraphs a little darker in color than th
7
7
 
8
8
  form.formtastic fieldset > ol > li p.inline-hints { color:#333; }
9
9
 
10
+ HINT:
11
+ The following style may be *conditionally* included for improved support on older versions of IE(<8)
12
+ form.formtastic fieldset ol li fieldset legend { margin-left: -6px;}
13
+
10
14
  --------------------------------------------------------------------------------------------------*/
@@ -1,5 +1,7 @@
1
1
  # coding: utf-8
2
2
  require File.join(File.dirname(__FILE__), *%w[formtastic i18n])
3
+ require File.join(File.dirname(__FILE__), *%w[formtastic util])
4
+ require File.join(File.dirname(__FILE__), *%w[formtastic railtie]) if defined?(::Rails::Railtie)
3
5
 
4
6
  module Formtastic #:nodoc:
5
7
 
@@ -9,7 +11,7 @@ module Formtastic #:nodoc:
9
11
  @@default_text_area_height = 20
10
12
  @@all_fields_required_by_default = true
11
13
  @@include_blank_for_select_by_default = true
12
- @@required_string = proc { %{<abbr title="#{::Formtastic::I18n.t(:required)}">*</abbr>} }
14
+ @@required_string = proc { ::Formtastic::Util.html_safe(%{<abbr title="#{::Formtastic::I18n.t(:required)}">*</abbr>}) }
13
15
  @@optional_string = ''
14
16
  @@inline_errors = :sentence
15
17
  @@label_str_method = :humanize
@@ -17,13 +19,12 @@ module Formtastic #:nodoc:
17
19
  @@inline_order = [ :input, :hints, :errors ]
18
20
  @@file_methods = [ :file?, :public_filename, :filename ]
19
21
  @@priority_countries = ["Australia", "Canada", "United Kingdom", "United States"]
20
- @@priority_currencies = ["US Dollar", "Euro"]
21
22
  @@i18n_lookups_by_default = false
22
- @@default_commit_button_accesskey = nil
23
+ @@default_commit_button_accesskey = nil
23
24
 
24
25
  cattr_accessor :default_text_field_size, :default_text_area_height, :all_fields_required_by_default, :include_blank_for_select_by_default,
25
26
  :required_string, :optional_string, :inline_errors, :label_str_method, :collection_label_methods,
26
- :inline_order, :file_methods, :priority_countries, :priority_currencies, :i18n_lookups_by_default, :default_commit_button_accesskey
27
+ :inline_order, :file_methods, :priority_countries, :i18n_lookups_by_default, :default_commit_button_accesskey
27
28
 
28
29
  RESERVED_COLUMNS = [:created_at, :updated_at, :created_on, :updated_on, :lock_version, :version]
29
30
 
@@ -77,6 +78,13 @@ module Formtastic #:nodoc:
77
78
  # <% end %>
78
79
  #
79
80
  def input(method, options = {})
81
+ if options.key?(:selected) || options.key?(:checked) || options.key?(:default)
82
+ ::ActiveSupport::Deprecation.warn(
83
+ "The :selected, :checked (and :default) options are deprecated in Formtastic and will be removed from 1.0. " <<
84
+ "Please set default values in your models (using an after_initialize callback) or in your controller set-up. " <<
85
+ "See http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html for more information.", caller)
86
+ end
87
+
80
88
  options[:required] = method_required?(method) unless options.key?(:required)
81
89
  options[:as] ||= default_input_type(method, options)
82
90
 
@@ -93,13 +101,13 @@ module Formtastic #:nodoc:
93
101
  end
94
102
 
95
103
  input_parts = @@inline_order.dup
96
- input_parts.delete(:errors) if options[:as] == :hidden
104
+ input_parts = input_parts - [:errors, :hints] if options[:as] == :hidden
97
105
 
98
106
  list_item_content = input_parts.map do |type|
99
107
  send(:"inline_#{type}_for", method, options)
100
108
  end.compact.join("\n")
101
109
 
102
- return template.content_tag(:li, list_item_content, wrapper_html)
110
+ return template.content_tag(:li, Formtastic::Util.html_safe(list_item_content), wrapper_html)
103
111
  end
104
112
 
105
113
  # Creates an input fieldset and ol tag wrapping for use around a set of inputs. It can be
@@ -253,7 +261,7 @@ module Formtastic #:nodoc:
253
261
  html_options = args.extract_options!
254
262
  html_options[:class] ||= "inputs"
255
263
  html_options[:name] = title
256
-
264
+
257
265
  if html_options[:for] # Nested form
258
266
  inputs_for_nested_attributes(*(args << html_options), &block)
259
267
  elsif block_given?
@@ -268,7 +276,7 @@ module Formtastic #:nodoc:
268
276
  legend = args.shift if args.first.is_a?(::String)
269
277
  contents = args.collect { |method| input(method.to_sym) }
270
278
  args.unshift(legend) if legend.present?
271
-
279
+
272
280
  field_set_and_list_wrapping(*((args << html_options) << contents))
273
281
  end
274
282
  end
@@ -319,10 +327,14 @@ module Formtastic #:nodoc:
319
327
  # ActiveRecord::Base.human_name falls back to ActiveRecord::Base.name.humanize ("Userpost")
320
328
  # if there's no i18n, which is pretty crappy. In this circumstance we want to detect this
321
329
  # fall back (human_name == name.humanize) and do our own thing name.underscore.humanize ("User Post")
322
- object_human_name = @object.class.human_name # default is UserPost => "Userpost", but i18n may do better ("User post")
323
- crappy_human_name = @object.class.name.humanize # UserPost => "Userpost"
324
- decent_human_name = @object.class.name.underscore.humanize # UserPost => "User post"
325
- object_name = (object_human_name == crappy_human_name) ? decent_human_name : object_human_name
330
+ if @object.class.model_name.respond_to?(:human)
331
+ object_name = @object.class.model_name.human
332
+ else
333
+ object_human_name = @object.class.human_name # default is UserPost => "Userpost", but i18n may do better ("User post")
334
+ crappy_human_name = @object.class.name.humanize # UserPost => "Userpost"
335
+ decent_human_name = @object.class.name.underscore.humanize # UserPost => "User post"
336
+ object_name = (object_human_name == crappy_human_name) ? decent_human_name : object_human_name
337
+ end
326
338
  else
327
339
  key = :submit
328
340
  object_name = @object_name.to_s.send(@@label_str_method)
@@ -335,8 +347,8 @@ module Formtastic #:nodoc:
335
347
  button_html.merge!(:class => [button_html[:class], key].compact.join(' '))
336
348
  element_class = ['commit', options.delete(:class)].compact.join(' ') # TODO: Add class reflecting on form action.
337
349
  accesskey = (options.delete(:accesskey) || @@default_commit_button_accesskey) unless button_html.has_key?(:accesskey)
338
- button_html = button_html.merge(:accesskey => accesskey) if accesskey
339
- template.content_tag(:li, self.submit(text, button_html), :class => element_class)
350
+ button_html = button_html.merge(:accesskey => accesskey) if accesskey
351
+ template.content_tag(:li, Formtastic::Util.html_safe(self.submit(text, button_html)), :class => element_class)
340
352
  end
341
353
 
342
354
  # A thin wrapper around #fields_for to set :builder => Formtastic::SemanticFormBuilder
@@ -393,11 +405,15 @@ module Formtastic #:nodoc:
393
405
  text = options_or_text
394
406
  options ||= {}
395
407
  end
408
+
396
409
  text = localized_string(method, text, :label) || humanized_attribute_name(method)
397
410
  text += required_or_optional_string(options.delete(:required))
411
+ text = Formtastic::Util.html_safe(text)
398
412
 
399
413
  # special case for boolean (checkbox) labels, which have a nested input
400
- text = (options.delete(:label_prefix_for_nested_input) || "") + text
414
+ if options.key?(:label_prefix_for_nested_input)
415
+ text = options.delete(:label_prefix_for_nested_input) + text
416
+ end
401
417
 
402
418
  input_name = options.delete(:input_name) || method
403
419
  super(input_name, text, options)
@@ -416,8 +432,10 @@ module Formtastic #:nodoc:
416
432
  #
417
433
  def inline_errors_for(method, options = nil) #:nodoc:
418
434
  if render_inline_errors?
419
- errors = @object.errors[method.to_sym]
420
- send(:"error_#{@@inline_errors}", [*errors]) if errors.present?
435
+ errors = [@object.errors[method.to_sym]]
436
+ errors << [@object.errors[association_primary_key(method)]] if association_macro_for_method(method) == :belongs_to
437
+ errors = errors.flatten.compact.uniq
438
+ send(:"error_#{@@inline_errors}", [*errors]) if errors.any?
421
439
  else
422
440
  nil
423
441
  end
@@ -440,13 +458,13 @@ module Formtastic #:nodoc:
440
458
  errors = Array(@object.errors[method.to_sym]).to_sentence
441
459
  errors.present? ? array << [attribute, errors].join(" ") : array ||= []
442
460
  end
443
- full_errors << @object.errors.on_base
461
+ full_errors << @object.errors[:base]
444
462
  full_errors.flatten!
445
463
  full_errors.compact!
446
464
  return nil if full_errors.blank?
447
465
  html_options[:class] ||= "errors"
448
466
  template.content_tag(:ul, html_options) do
449
- full_errors.map { |error| template.content_tag(:li, error) }.join
467
+ Formtastic::Util.html_safe(full_errors.map { |error| template.content_tag(:li, Formtastic::Util.html_safe(error)) }.join)
450
468
  end
451
469
  end
452
470
 
@@ -477,6 +495,18 @@ module Formtastic #:nodoc:
477
495
  []
478
496
  end
479
497
  end
498
+
499
+ # Returns nil, or a symbol like :belongs_to or :has_many
500
+ def association_macro_for_method(method) #:nodoc:
501
+ reflection = self.reflection_for(method)
502
+ reflection.macro if reflection
503
+ end
504
+
505
+ def association_primary_key(method)
506
+ reflection = self.reflection_for(method)
507
+ reflection.options[:foreign_key] if reflection && !reflection.options[:foreign_key].blank?
508
+ :"#{method}_id"
509
+ end
480
510
 
481
511
  # Prepare options to be sent to label
482
512
  #
@@ -498,9 +528,9 @@ module Formtastic #:nodoc:
498
528
  raise ArgumentError, 'You gave :for option with a block to inputs method, ' <<
499
529
  'but the block does not accept any argument.' if block.arity <= 0
500
530
 
501
- proc { |f| f.inputs(*args){ block.call(f) } }
531
+ proc { |f| return f.inputs(*args){ block.call(f) } }
502
532
  else
503
- proc { |f| f.inputs(*args) }
533
+ proc { |f| return f.inputs(*args) }
504
534
  end
505
535
 
506
536
  fields_for_args = [options.delete(:for), options.delete(:for_options) || {}].flatten
@@ -519,23 +549,31 @@ module Formtastic #:nodoc:
519
549
  # * if the :required option was provided in the options hash, the true/false value will be
520
550
  # returned immediately, allowing the view to override any guesswork that follows:
521
551
  #
522
- # * if the :required option isn't provided in the options hash, true is returned if the
523
- # presence macro has been used in the class for this attribute, or false otherwise.
552
+ # * if the :required option isn't provided in the options hash, and the ValidationReflection
553
+ # plugin is installed (http://github.com/redinger/validation_reflection), or the object is
554
+ # an ActiveModel, true is returned
555
+ # if the validates_presence_of macro has been used in the class for this attribute, or false
556
+ # otherwise.
524
557
  #
525
- # * if the :required option isn't provided, and the plugin isn't available, the value of the
558
+ # * if the :required option isn't provided, and validates_presence_of can't be determined, the
526
559
  # configuration option @@all_fields_required_by_default is used.
527
560
  #
528
561
  def method_required?(attribute) #:nodoc:
529
- if @object && @object.class.respond_to?(:validators_on)
562
+ if @object && @object.class.respond_to?(:reflect_on_validations_for)
530
563
  attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym
531
564
 
532
- @object.class.validators_on(attribute_sym).any? do |validation|
533
- validation.kind == :presence &&
534
- validation.attributes.include?(attribute_sym) &&
565
+ @object.class.reflect_on_validations_for(attribute_sym).any? do |validation|
566
+ validation.macro == :validates_presence_of &&
567
+ validation.name == attribute_sym &&
535
568
  (validation.options.present? ? options_require_validation?(validation.options) : true)
536
569
  end
537
570
  else
538
- @@all_fields_required_by_default
571
+ if @object && @object.class.respond_to?(:validators_on)
572
+ attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym
573
+ !@object.class.validators_on(attribute_sym).find{|validator| (validator.kind == :presence) && (validator.options.present? ? options_require_validation?(validator.options) : true)}.nil?
574
+ else
575
+ @@all_fields_required_by_default
576
+ end
539
577
  end
540
578
  end
541
579
 
@@ -639,9 +677,9 @@ module Formtastic #:nodoc:
639
677
  # </select>
640
678
  #
641
679
  #
642
- # You can customize the options available in the select by passing in a collection (an Array or
643
- # Hash) through the :collection option. If not provided, the choices are found by inferring the
644
- # parent's class name from the method name and simply calling find(:all) on it
680
+ # You can customize the options available in the select by passing in a collection (an Array or
681
+ # Hash) through the :collection option. If not provided, the choices are found by inferring the
682
+ # parent's class name from the method name and simply calling find(:all) on it
645
683
  # (VehicleOwner.find(:all) in the example above).
646
684
  #
647
685
  # Examples:
@@ -673,9 +711,9 @@ module Formtastic #:nodoc:
673
711
  # f.input :author, :value_method => Proc.new { |a| "author_#{a.login}" }
674
712
  #
675
713
  # You can pre-select a specific option value by passing in the :selected option.
676
- #
714
+ #
677
715
  # Examples:
678
- #
716
+ #
679
717
  # f.input :author, :selected => current_user.id
680
718
  # f.input :author, :value_method => :login, :selected => current_user.login
681
719
  # f.input :authors, :value_method => :login, :selected => Author.most_popular.collect(&:id)
@@ -691,14 +729,14 @@ module Formtastic #:nodoc:
691
729
  # a prompt with the :prompt option, or disable the blank option with :include_blank => false.
692
730
  #
693
731
  #
694
- # You can group the options in optgroup elements by passing the :group_by option
732
+ # You can group the options in optgroup elements by passing the :group_by option
695
733
  # (Note: only tested for belongs_to relations)
696
- #
734
+ #
697
735
  # Examples:
698
736
  #
699
737
  # f.input :author, :group_by => :continent
700
- #
701
- # All the other options should work as expected. If you want to call a custom method on the
738
+ #
739
+ # All the other options should work as expected. If you want to call a custom method on the
702
740
  # group item. You can include the option:group_label_method
703
741
  # Examples:
704
742
  #
@@ -720,7 +758,7 @@ module Formtastic #:nodoc:
720
758
  input_name = generate_association_input_name(method)
721
759
 
722
760
  select_html = if options[:group_by]
723
- # The grouped_options_select is a bit counter intuitive and not optimised (mostly due to ActiveRecord).
761
+ # The grouped_options_select is a bit counter intuitive and not optimised (mostly due to ActiveRecord).
724
762
  # The formtastic user however shouldn't notice this too much.
725
763
  raw_collection = find_raw_collection_for_column(method, options.reverse_merge(:find_options => { :include => options[:group_by] }))
726
764
  label, value = detect_label_and_value_method!(raw_collection)
@@ -732,7 +770,7 @@ module Formtastic #:nodoc:
732
770
  # Here comes the monster with 8 arguments
733
771
  self.grouped_collection_select(input_name, group_collection,
734
772
  group_association, group_label_method,
735
- value, label,
773
+ value, label,
736
774
  strip_formtastic_options(options), html_options)
737
775
  else
738
776
  collection = find_collection_for_column(method, options)
@@ -754,9 +792,9 @@ module Formtastic #:nodoc:
754
792
  # You can pre-select a specific option value by passing in the :selected option.
755
793
  # Note: Right now only works if the form object attribute value is not set (nil),
756
794
  # because of how the core helper is implemented.
757
- #
795
+ #
758
796
  # Examples:
759
- #
797
+ #
760
798
  # f.input :my_favorite_time_zone, :as => :time_zone, :selected => 'Singapore'
761
799
  #
762
800
  def time_zone_input(method, options)
@@ -790,7 +828,7 @@ module Formtastic #:nodoc:
790
828
  # </ol>
791
829
  # </fieldset>
792
830
  #
793
- # You can customize the choices available in the radio button set by passing in a collection (an Array or
831
+ # You can customize the choices available in the radio button set by passing in a collection (an Array or
794
832
  # Hash) through the :collection option. If not provided, the choices are found by reflecting on the association
795
833
  # (Author.find(:all) in the example above).
796
834
  #
@@ -820,7 +858,7 @@ module Formtastic #:nodoc:
820
858
  # f.input :author, :as => :radio, :value_method => :full_name
821
859
  # f.input :author, :as => :radio, :value_method => :login
822
860
  # f.input :author, :as => :radio, :value_method => Proc.new { |a| "author_#{a.login}" }
823
- #
861
+ #
824
862
  # You can force a particular radio button in the collection to be checked with the :selected option.
825
863
  #
826
864
  # Examples:
@@ -828,7 +866,7 @@ module Formtastic #:nodoc:
828
866
  # f.input :subscribe_to_newsletter, :as => :radio, :selected => true
829
867
  # f.input :subscribe_to_newsletter, :as => :radio, :collection => ["Yeah!", "Nope!"], :selected => "Nope!"
830
868
  #
831
- # Finally, you can set :value_as_class => true if you want the li wrapper around each radio
869
+ # Finally, you can set :value_as_class => true if you want the li wrapper around each radio
832
870
  # button / label combination to contain a class with the value of the radio button (useful for
833
871
  # applying specific CSS or Javascript to a particular radio button).
834
872
  #
@@ -851,12 +889,12 @@ module Formtastic #:nodoc:
851
889
  html_options[:checked] = selected_value == value if selected_option_is_present
852
890
 
853
891
  li_content = template.content_tag(:label,
854
- "#{self.radio_button(input_name, value, html_options)} #{label}",
892
+ Formtastic::Util.html_safe("#{self.radio_button(input_name, value, html_options)} #{label}"),
855
893
  :for => input_id
856
894
  )
857
895
 
858
896
  li_options = value_as_class ? { :class => [method.to_s.singularize, value.to_s.downcase].join('_') } : {}
859
- template.content_tag(:li, li_content, li_options)
897
+ template.content_tag(:li, Formtastic::Util.html_safe(li_content), li_options)
860
898
  end
861
899
 
862
900
  field_set_and_list_wrapping_for_method(method, options, list_item_content)
@@ -870,14 +908,14 @@ module Formtastic #:nodoc:
870
908
  # See date_or_datetime_input for a more detailed output example.
871
909
  #
872
910
  # You can pre-select a specific option value by passing in the :selected option.
873
- #
911
+ #
874
912
  # Examples:
875
- #
913
+ #
876
914
  # f.input :created_at, :as => :date, :selected => 1.day.ago
877
915
  # f.input :created_at, :as => :date, :selected => nil # override any defaults: select none
878
916
  # f.input :created_at, :as => :date, :labels => { :year => "Year", :month => "Month", :day => "Day" }
879
917
  #
880
- # Some of Rails' options for select_date are supported, but not everything yet, see
918
+ # Some of Rails' options for select_date are supported, but not everything yet, see
881
919
  # documentation of date_or_datetime_input() for more information.
882
920
  def date_input(method, options)
883
921
  options = set_include_blank(options)
@@ -891,15 +929,15 @@ module Formtastic #:nodoc:
891
929
  # text as value. See date_or_datetime_input for a more detailed output example.
892
930
  #
893
931
  # You can pre-select a specific option value by passing in the :selected option.
894
- #
932
+ #
895
933
  # Examples:
896
- #
934
+ #
897
935
  # f.input :created_at, :as => :datetime, :selected => 1.day.ago
898
936
  # f.input :created_at, :as => :datetime, :selected => nil # override any defaults: select none
899
937
  # f.input :created_at, :as => :date, :labels => { :year => "Year", :month => "Month", :day => "Day",
900
938
  # :hour => "Hour", :minute => "Minute" }
901
939
  #
902
- # Some of Rails' options for select_date are supported, but not everything yet, see
940
+ # Some of Rails' options for select_date are supported, but not everything yet, see
903
941
  # documentation of date_or_datetime_input() for more information.
904
942
  def datetime_input(method, options)
905
943
  options = set_include_blank(options)
@@ -913,22 +951,22 @@ module Formtastic #:nodoc:
913
951
  # See date_or_datetime_input for a more detailed output example.
914
952
  #
915
953
  # You can pre-select a specific option value by passing in the :selected option.
916
- #
954
+ #
917
955
  # Examples:
918
- #
956
+ #
919
957
  # f.input :created_at, :as => :time, :selected => 1.hour.ago
920
958
  # f.input :created_at, :as => :time, :selected => nil # override any defaults: select none
921
959
  # f.input :created_at, :as => :date, :labels => { :hour => "Hour", :minute => "Minute" }
922
960
  #
923
- # Some of Rails' options for select_time are supported, but not everything yet, see
961
+ # Some of Rails' options for select_time are supported, but not everything yet, see
924
962
  # documentation of date_or_datetime_input() for more information.
925
963
  def time_input(method, options)
926
964
  options = set_include_blank(options)
927
965
  date_or_datetime_input(method, options.merge(:discard_year => true, :discard_month => true, :discard_day => true))
928
966
  end
929
-
930
- # Helper method used by :as => (:date|:datetime|:time). Generates a fieldset containing a
931
- # legend (for what would normally be considered the label), and an ordered list of list items
967
+
968
+ # Helper method used by :as => (:date|:datetime|:time). Generates a fieldset containing a
969
+ # legend (for what would normally be considered the label), and an ordered list of list items
932
970
  # for year, month, day, hour, etc, each containing a label and a select. Example:
933
971
  #
934
972
  # <fieldset>
@@ -974,11 +1012,6 @@ module Formtastic #:nodoc:
974
1012
  # * @:include_blank => true@
975
1013
  # * @:labels => {}@
976
1014
  def date_or_datetime_input(method, options)
977
- if options.key?(:selected)
978
- ::ActiveSupport::Deprecation.warn(":selected is deprecated (and may still have changed behavior) in #{options[:as]} inputs, use :default instead, see commit 09fc6b4 and issue #152 on github.com/justinfrench/formtastic")
979
- options[:default] = options[:selected]
980
- end
981
-
982
1015
  position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
983
1016
  i18n_date_order = ::I18n.t(:order, :scope => [:date])
984
1017
  i18n_date_order = nil unless i18n_date_order.is_a?(Array)
@@ -986,13 +1019,13 @@ module Formtastic #:nodoc:
986
1019
  labels = options.delete(:labels) || {}
987
1020
 
988
1021
  time_inputs = [:hour, :minute]
989
- time_inputs << [:second] if options[:include_seconds]
1022
+ time_inputs << :second if options[:include_seconds]
990
1023
 
991
1024
  list_items_capture = ""
992
1025
  hidden_fields_capture = ""
993
1026
 
994
- datetime = options.key?(:default) ? options[:default] : Time.now # can't do an || because nil is an important value
995
- datetime = @object.send(method) if @object && @object.send(method) # object trumps :default
1027
+ datetime = options[:selected]
1028
+ datetime = @object.send(method) if @object && @object.send(method) # object value trumps :selected value
996
1029
 
997
1030
  html_options = options.delete(:input_html) || {}
998
1031
  input_ids = []
@@ -1010,10 +1043,10 @@ module Formtastic #:nodoc:
1010
1043
  opts = strip_formtastic_options(options).merge(:prefix => @object_name, :field_name => field_name, :default => datetime)
1011
1044
  item_label_text = labels[input] || ::I18n.t(input.to_s, :default => input.to_s.humanize, :scope => [:datetime, :prompts])
1012
1045
 
1013
- list_items_capture << template.content_tag(:li, [
1014
- !item_label_text.blank? ? template.content_tag(:label, item_label_text, :for => input_id) : "",
1046
+ list_items_capture << template.content_tag(:li, Formtastic::Util.html_safe([
1047
+ !item_label_text.blank? ? template.content_tag(:label, Formtastic::Util.html_safe(item_label_text), :for => input_id) : "",
1015
1048
  template.send(:"select_#{input}", datetime, opts, html_options.merge(:id => input_id))
1016
- ].join("")
1049
+ ].join(""))
1017
1050
  )
1018
1051
  end
1019
1052
  end
@@ -1083,15 +1116,15 @@ module Formtastic #:nodoc:
1083
1116
  # f.input :author, :as => :check_boxes, :value_method => Proc.new { |a| "author_#{a.login}" }
1084
1117
  #
1085
1118
  # You can pre-select/check a specific checkbox value by passing in the :selected option (alias :checked works as well).
1086
- #
1119
+ #
1087
1120
  # Examples:
1088
- #
1121
+ #
1089
1122
  # f.input :authors, :as => :check_boxes, :selected => @justin
1090
1123
  # f.input :authors, :as => :check_boxes, :selected => Author.most_popular.collect(&:id)
1091
1124
  # f.input :authors, :as => :check_boxes, :selected => nil # override any defaults: select none
1092
1125
  #
1093
- # Finally, you can set :value_as_class => true if you want the li wrapper around each checkbox / label
1094
- # combination to contain a class with the value of the radio button (useful for applying specific
1126
+ # Finally, you can set :value_as_class => true if you want the li wrapper around each checkbox / label
1127
+ # combination to contain a class with the value of the radio button (useful for applying specific
1095
1128
  # CSS or Javascript to a particular checkbox).
1096
1129
  #
1097
1130
  def check_boxes_input(method, options)
@@ -1108,6 +1141,9 @@ module Formtastic #:nodoc:
1108
1141
  selected_values = (options.key?(:checked) ? options[:checked] : options[:selected]) if selected_option_is_present
1109
1142
  selected_values = [*selected_values].compact
1110
1143
 
1144
+ disabled_option_is_present = options.key?(:disabled)
1145
+ disabled_values = [*options[:disabled]] if disabled_option_is_present
1146
+
1111
1147
  list_item_content = collection.map do |c|
1112
1148
  label = c.is_a?(Array) ? c.first : c
1113
1149
  value = c.is_a?(Array) ? c.last : c
@@ -1115,28 +1151,29 @@ module Formtastic #:nodoc:
1115
1151
  input_ids << input_id
1116
1152
 
1117
1153
  html_options[:checked] = selected_values.include?(value) if selected_option_is_present
1154
+ html_options[:disabled] = disabled_values.include?(value) if disabled_option_is_present
1118
1155
  html_options[:id] = input_id
1119
1156
 
1120
1157
  li_content = template.content_tag(:label,
1121
- "#{self.check_box(input_name, html_options, value, unchecked_value)} #{label}",
1158
+ Formtastic::Util.html_safe("#{self.check_box(input_name, html_options, value, unchecked_value)} #{label}"),
1122
1159
  :for => input_id
1123
1160
  )
1124
1161
 
1125
1162
  li_options = value_as_class ? { :class => [method.to_s.singularize, value.to_s.downcase].join('_') } : {}
1126
- template.content_tag(:li, li_content, li_options)
1163
+ template.content_tag(:li, Formtastic::Util.html_safe(li_content), li_options)
1127
1164
  end
1128
1165
 
1129
1166
  field_set_and_list_wrapping_for_method(method, options, list_item_content)
1130
1167
  end
1131
1168
 
1132
- # Outputs a country select input, wrapping around a regular country_select helper.
1169
+ # Outputs a country select input, wrapping around a regular country_select helper.
1133
1170
  # Rails doesn't come with a country_select helper by default any more, so you'll need to install
1134
1171
  # the "official" plugin, or, if you wish, any other country_select plugin that behaves in the
1135
1172
  # same way.
1136
1173
  #
1137
1174
  # The Rails plugin iso-3166-country-select plugin can be found "here":http://github.com/rails/iso-3166-country-select.
1138
1175
  #
1139
- # By default, Formtastic includes a handfull of english-speaking countries as "priority counties",
1176
+ # By default, Formtastic includes a handfull of english-speaking countries as "priority counties",
1140
1177
  # which you can change to suit your market and user base (see README for more info on config).
1141
1178
  #
1142
1179
  # Examples:
@@ -1145,7 +1182,7 @@ module Formtastic #:nodoc:
1145
1182
  #
1146
1183
  def country_input(method, options)
1147
1184
  raise "To use the :country input, please install a country_select plugin, like this one: http://github.com/rails/iso-3166-country-select" unless self.respond_to?(:country_select)
1148
-
1185
+
1149
1186
  html_options = options.delete(:input_html) || {}
1150
1187
  priority_countries = options.delete(:priority_countries) || @@priority_countries
1151
1188
 
@@ -1153,34 +1190,14 @@ module Formtastic #:nodoc:
1153
1190
  self.country_select(method, priority_countries, strip_formtastic_options(options), html_options)
1154
1191
  end
1155
1192
 
1156
- # Outputs a currency select input, wrapping around a regular curency_select helper.
1157
- # You need to install the currency_select plugin "here":http://github.com/gavinlaking/currency_select
1158
- #
1159
- # By default, Formtastic includes a handfull of common currencies as "priority currencies",
1160
- # which you can change to suit your market and user base (see README for more info on config).
1161
- #
1162
- # Examples:
1163
- # f.input :ticket_currency, :as => :currency # use Formtastic::SemanticFormBuilder.priority_curencies array for the priority currencies
1164
- # f.input :ticket_currency, :as => :currency, :priority_currencies => /US Dollar/ # set your own
1165
- #
1166
- def currency_input(method, options)
1167
- raise "To use the :currency input, please install the currency_select plugin http://github.com/gavinlaking/currency_select" unless self.respond_to?(:currency_select)
1168
-
1169
- html_options = options.delete(:input_html) || {}
1170
- priority_currencies = options.delete(:priority_currencies) || @@priority_currencies
1171
-
1172
- self.label(method, options_for_label(options)) <<
1173
- self.currency_select(method, priority_currencies, strip_formtastic_options(options), html_options)
1174
- end
1175
-
1176
1193
  # Outputs a label containing a checkbox and the label text. The label defaults
1177
1194
  # to the column name (method name) and can be altered with the :label option.
1178
1195
  # :checked_value and :unchecked_value options are also available.
1179
1196
  #
1180
1197
  # You can pre-select/check the boolean checkbox by passing in the :selected option (alias :checked works as well).
1181
- #
1198
+ #
1182
1199
  # Examples:
1183
- #
1200
+ #
1184
1201
  # f.input :allow_comments, :as => :boolean, :selected => true # override any default value: selected/checked
1185
1202
  #
1186
1203
  def boolean_input(method, options)
@@ -1208,13 +1225,13 @@ module Formtastic #:nodoc:
1208
1225
  def inline_hints_for(method, options) #:nodoc:
1209
1226
  options[:hint] = localized_string(method, options[:hint], :hint)
1210
1227
  return if options[:hint].blank?
1211
- template.content_tag(:p, options[:hint], :class => 'inline-hints')
1228
+ template.content_tag(:p, Formtastic::Util.html_safe(options[:hint]), :class => 'inline-hints')
1212
1229
  end
1213
1230
 
1214
1231
  # Creates an error sentence by calling to_sentence on the errors array.
1215
1232
  #
1216
1233
  def error_sentence(errors) #:nodoc:
1217
- template.content_tag(:p, errors.to_sentence.untaint, :class => 'inline-errors')
1234
+ template.content_tag(:p, Formtastic::Util.html_safe(errors.to_sentence.untaint), :class => 'inline-errors')
1218
1235
  end
1219
1236
 
1220
1237
  # Creates an error li list.
@@ -1222,15 +1239,15 @@ module Formtastic #:nodoc:
1222
1239
  def error_list(errors) #:nodoc:
1223
1240
  list_elements = []
1224
1241
  errors.each do |error|
1225
- list_elements << template.content_tag(:li, error.untaint)
1242
+ list_elements << template.content_tag(:li, Formtastic::Util.html_safe(error.untaint))
1226
1243
  end
1227
- template.content_tag(:ul, list_elements.join("\n"), :class => 'errors')
1244
+ template.content_tag(:ul, Formtastic::Util.html_safe(list_elements.join("\n")), :class => 'errors')
1228
1245
  end
1229
1246
 
1230
1247
  # Creates an error sentence containing only the first error
1231
1248
  #
1232
1249
  def error_first(errors) #:nodoc:
1233
- template.content_tag(:p, errors.first.untaint, :class => 'inline-errors')
1250
+ template.content_tag(:p, Formtastic::Util.html_safe(errors.first.untaint), :class => 'inline-errors')
1234
1251
  end
1235
1252
 
1236
1253
  # Generates the required or optional string. If the value set is a proc,
@@ -1277,7 +1294,7 @@ module Formtastic #:nodoc:
1277
1294
 
1278
1295
  legend = html_options.delete(:name).to_s
1279
1296
  legend %= parent_child_index(html_options[:parent]) if html_options[:parent]
1280
- legend = template.content_tag(:legend, template.content_tag(:span, legend)) unless legend.blank?
1297
+ legend = template.content_tag(:legend, template.content_tag(:span, Formtastic::Util.html_safe(legend))) unless legend.blank?
1281
1298
 
1282
1299
  if block_given?
1283
1300
  contents = if template.respond_to?(:is_haml?) && template.is_haml?
@@ -1290,11 +1307,11 @@ module Formtastic #:nodoc:
1290
1307
  # Ruby 1.9: String#to_s behavior changed, need to make an explicit join.
1291
1308
  contents = contents.join if contents.respond_to?(:join)
1292
1309
  fieldset = template.content_tag(:fieldset,
1293
- legend << template.content_tag(:ol, contents),
1310
+ Formtastic::Util.html_safe(legend) << template.content_tag(:ol, Formtastic::Util.html_safe(contents)),
1294
1311
  html_options.except(:builder, :parent)
1295
1312
  )
1296
1313
 
1297
- template.concat(fieldset) if block_given?
1314
+ template.concat(fieldset) if block_given? && (!defined?(Rails::VERSION) || Rails::VERSION::MAJOR == 2)
1298
1315
  fieldset
1299
1316
  end
1300
1317
 
@@ -1322,7 +1339,7 @@ module Formtastic #:nodoc:
1322
1339
  template.content_tag(:legend,
1323
1340
  self.label(method, options_for_label(options).merge(:for => options.delete(:label_for))), :class => 'label'
1324
1341
  ) <<
1325
- template.content_tag(:ol, contents)
1342
+ template.content_tag(:ol, Formtastic::Util.html_safe(contents))
1326
1343
  )
1327
1344
  end
1328
1345
 
@@ -1340,8 +1357,7 @@ module Formtastic #:nodoc:
1340
1357
  case column.type
1341
1358
  when :string
1342
1359
  return :password if method.to_s =~ /password/
1343
- return :country if method.to_s =~ /country/
1344
- return :currency if method.to_s =~ /currency/
1360
+ return :country if method.to_s =~ /country$/
1345
1361
  return :time_zone if method.to_s =~ /time_zone/
1346
1362
  when :integer
1347
1363
  return :select if method.to_s =~ /_id$/
@@ -1351,7 +1367,7 @@ module Formtastic #:nodoc:
1351
1367
  when :timestamp
1352
1368
  return :datetime
1353
1369
  end
1354
-
1370
+
1355
1371
  # Try look for hints in options hash. Quite common senario: Enum keys stored as string in the database.
1356
1372
  return :select if column.type == :string && options.key?(:collection)
1357
1373
  # Try 3: Assume the input name will be the same as the column type (e.g. string_input).
@@ -1398,7 +1414,13 @@ module Formtastic #:nodoc:
1398
1414
  collection = if options[:collection]
1399
1415
  options.delete(:collection)
1400
1416
  elsif reflection = self.reflection_for(column)
1401
- reflection.klass.find(:all, options[:find_options] || {})
1417
+ options[:find_options] ||= {}
1418
+
1419
+ if conditions = reflection.options[:conditions]
1420
+ options[:find_options][:conditions] = reflection.klass.merge_conditions(conditions, options[:find_options][:conditions])
1421
+ end
1422
+
1423
+ reflection.klass.find(:all, options[:find_options])
1402
1424
  else
1403
1425
  create_boolean_collection(options)
1404
1426
  end
@@ -1407,7 +1429,7 @@ module Formtastic #:nodoc:
1407
1429
  collection
1408
1430
  end
1409
1431
 
1410
- # Detects the label and value methods from a collection values set in
1432
+ # Detects the label and value methods from a collection values set in
1411
1433
  # @@collection_label_methods. It will use and delete
1412
1434
  # the options :label_method and :value_methods when present
1413
1435
  #
@@ -1429,16 +1451,16 @@ module Formtastic #:nodoc:
1429
1451
  def detect_group_association(method, group_by)
1430
1452
  object_to_method_reflection = self.reflection_for(method)
1431
1453
  method_class = object_to_method_reflection.klass
1432
-
1454
+
1433
1455
  method_to_group_association = method_class.reflect_on_association(group_by)
1434
1456
  group_class = method_to_group_association.klass
1435
-
1457
+
1436
1458
  # This will return in the normal case
1437
1459
  return method.to_s.pluralize.to_sym if group_class.reflect_on_association(method.to_s.pluralize)
1438
-
1460
+
1439
1461
  # This is for belongs_to associations named differently than their class
1440
1462
  # form.input :parent, :group_by => :customer
1441
- # eg.
1463
+ # eg.
1442
1464
  # class Project
1443
1465
  # belongs_to :parent, :class_name => 'Project', :foreign_key => 'parent_id'
1444
1466
  # belongs_to :customer
@@ -1448,9 +1470,9 @@ module Formtastic #:nodoc:
1448
1470
  # end
1449
1471
  group_method = method_class.to_s.underscore.pluralize.to_sym
1450
1472
  return group_method if group_class.reflect_on_association(group_method) # :projects
1451
-
1473
+
1452
1474
  # This is for has_many associations named differently than their class
1453
- # eg.
1475
+ # eg.
1454
1476
  # class Project
1455
1477
  # belongs_to :parent, :class_name => 'Project', :foreign_key => 'parent_id'
1456
1478
  # belongs_to :customer
@@ -1460,9 +1482,9 @@ module Formtastic #:nodoc:
1460
1482
  # end
1461
1483
  possible_associations = group_class.reflect_on_all_associations(:has_many).find_all{|assoc| assoc.klass == object_class}
1462
1484
  return possible_associations.first.name.to_sym if possible_associations.count == 1
1463
-
1485
+
1464
1486
  raise "Cannot infer group association for #{method} grouped by #{group_by}, there were #{possible_associations.empty? ? 'no' : possible_associations.size} possible associations. Please specify using :group_association"
1465
-
1487
+
1466
1488
  end
1467
1489
 
1468
1490
  # Returns a hash to be used by radio and select inputs when a boolean field
@@ -1489,7 +1511,7 @@ module Formtastic #:nodoc:
1489
1511
  if [:has_and_belongs_to_many, :has_many].include?(reflection.macro)
1490
1512
  "#{method.to_s.singularize}_ids"
1491
1513
  else
1492
- reflection.options[:foreign_key] || reflection.options[:class_name].try(:foreign_key) || "#{method}_id"
1514
+ reflection.options[:foreign_key] || "#{method}_id"
1493
1515
  end
1494
1516
  else
1495
1517
  method
@@ -1577,23 +1599,23 @@ module Formtastic #:nodoc:
1577
1599
 
1578
1600
  # Internal generic method for looking up localized values within Formtastic
1579
1601
  # using I18n, if no explicit value is set and I18n-lookups are enabled.
1580
- #
1602
+ #
1581
1603
  # Enabled/Disable this by setting:
1582
1604
  #
1583
1605
  # Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true/false
1584
1606
  #
1585
1607
  # Lookup priority:
1586
1608
  #
1587
- # 'formtastic.{{type}}.{{model}}.{{action}}.{{attribute}}'
1588
- # 'formtastic.{{type}}.{{model}}.{{attribute}}'
1589
- # 'formtastic.{{type}}.{{attribute}}'
1590
- #
1609
+ # 'formtastic.%{type}.%{model}.%{action}.%{attribute}'
1610
+ # 'formtastic.%{type}.%{model}.%{attribute}'
1611
+ # 'formtastic.%{type}.%{attribute}'
1612
+ #
1591
1613
  # Example:
1592
- #
1614
+ #
1593
1615
  # 'formtastic.labels.post.edit.title'
1594
1616
  # 'formtastic.labels.post.title'
1595
1617
  # 'formtastic.labels.title'
1596
- #
1618
+ #
1597
1619
  # NOTE: Generic, but only used for form input titles/labels/hints/actions (titles = legends, actions = buttons).
1598
1620
  #
1599
1621
  def localized_string(key, value, type, options = {}) #:nodoc:
@@ -1605,15 +1627,16 @@ module Formtastic #:nodoc:
1605
1627
  use_i18n = value.nil? ? @@i18n_lookups_by_default : (value != false)
1606
1628
 
1607
1629
  if use_i18n
1608
- model_name = self.model_name.underscore
1630
+ model_name, nested_model_name = normalize_model_name(self.model_name.underscore)
1609
1631
  action_name = template.params[:action].to_s rescue ''
1610
1632
  attribute_name = key.to_s
1611
1633
 
1612
1634
  defaults = ::Formtastic::I18n::SCOPES.collect do |i18n_scope|
1613
1635
  i18n_path = i18n_scope.dup
1614
- i18n_path.gsub!('{{action}}', action_name)
1615
- i18n_path.gsub!('{{model}}', model_name)
1616
- i18n_path.gsub!('{{attribute}}', attribute_name)
1636
+ i18n_path.gsub!('%{action}', action_name)
1637
+ i18n_path.gsub!('%{model}', model_name)
1638
+ i18n_path.gsub!('%{nested_model}', nested_model_name) unless nested_model_name.nil?
1639
+ i18n_path.gsub!('%{attribute}', attribute_name)
1617
1640
  i18n_path.gsub!('..', '.')
1618
1641
  i18n_path.to_sym
1619
1642
  end
@@ -1630,6 +1653,14 @@ module Formtastic #:nodoc:
1630
1653
  @object.present? ? @object.class.name : @object_name.to_s.classify
1631
1654
  end
1632
1655
 
1656
+ def normalize_model_name(name)
1657
+ if name =~ /(.+)\[(.+)\]/
1658
+ [$1, $2]
1659
+ else
1660
+ [name]
1661
+ end
1662
+ end
1663
+
1633
1664
  def send_or_call(duck, object)
1634
1665
  if duck.is_a?(Proc)
1635
1666
  duck.call(object)
@@ -1651,11 +1682,15 @@ module Formtastic #:nodoc:
1651
1682
  #
1652
1683
  # * semantic_form_for(@post)
1653
1684
  # * semantic_fields_for(@post)
1685
+ # * semantic_form_remote_for(@post)
1686
+ # * semantic_remote_form_for(@post)
1654
1687
  #
1655
1688
  # Each of which are the equivalent of:
1656
1689
  #
1657
1690
  # * form_for(@post, :builder => Formtastic::SemanticFormBuilder))
1658
1691
  # * fields_for(@post, :builder => Formtastic::SemanticFormBuilder))
1692
+ # * form_remote_for(@post, :builder => Formtastic::SemanticFormBuilder))
1693
+ # * remote_form_for(@post, :builder => Formtastic::SemanticFormBuilder))
1659
1694
  #
1660
1695
  # Example Usage:
1661
1696
  #
@@ -1665,7 +1700,7 @@ module Formtastic #:nodoc:
1665
1700
  # <% end %>
1666
1701
  #
1667
1702
  # The above examples use a resource-oriented style of form_for() helper where only the @post
1668
- # object is given as an argument, but the generic style is also supported, as are forms with
1703
+ # object is given as an argument, but the generic style is also supported, as are forms with
1669
1704
  # inline objects (Post.new) rather than objects with instance variables (@post):
1670
1705
  #
1671
1706
  # <% semantic_form_for :post, @post, :url => posts_path do |f| %>
@@ -1678,9 +1713,9 @@ module Formtastic #:nodoc:
1678
1713
  module SemanticFormHelper
1679
1714
  @@builder = ::Formtastic::SemanticFormBuilder
1680
1715
  mattr_accessor :builder
1681
-
1716
+
1682
1717
  @@default_field_error_proc = nil
1683
-
1718
+
1684
1719
  # Override the default ActiveRecordHelper behaviour of wrapping the input.
1685
1720
  # This gets taken care of semantically by adding an error class to the LI tag
1686
1721
  # containing the input.
@@ -1688,7 +1723,7 @@ module Formtastic #:nodoc:
1688
1723
  FIELD_ERROR_PROC = proc do |html_tag, instance_tag|
1689
1724
  html_tag
1690
1725
  end
1691
-
1726
+
1692
1727
  def with_custom_field_error_proc(&block)
1693
1728
  @@default_field_error_proc = ::ActionView::Base.field_error_proc
1694
1729
  ::ActionView::Base.field_error_proc = FIELD_ERROR_PROC
@@ -1696,8 +1731,18 @@ module Formtastic #:nodoc:
1696
1731
  ::ActionView::Base.field_error_proc = @@default_field_error_proc
1697
1732
  result
1698
1733
  end
1734
+
1735
+ def semantic_remote_form_for_wrapper(record_or_name_or_array, *args, &proc)
1736
+ options = args.extract_options!
1737
+ if self.respond_to? :remote_form_for
1738
+ semantic_remote_form_for_real(record_or_name_or_array, *(args << options), &proc)
1739
+ else
1740
+ options[:remote] = true
1741
+ semantic_form_for(record_or_name_or_array, *(args << options), &proc)
1742
+ end
1743
+ end
1699
1744
 
1700
- [:form_for, :fields_for].each do |meth|
1745
+ [:form_for, :fields_for, :remote_form_for].each do |meth|
1701
1746
  module_eval <<-END_SRC, __FILE__, __LINE__ + 1
1702
1747
  def semantic_#{meth}(record_or_name_or_array, *args, &proc)
1703
1748
  options = args.extract_options!
@@ -1712,13 +1757,16 @@ module Formtastic #:nodoc:
1712
1757
  else ActionController::RecordIdentifier.singular_class_name(record_or_name_or_array.class) # @post => "post"
1713
1758
  end
1714
1759
  options[:html][:class] = class_names.join(" ")
1715
-
1760
+
1716
1761
  with_custom_field_error_proc do
1717
1762
  #{meth}(record_or_name_or_array, *(args << options), &proc)
1718
1763
  end
1719
1764
  end
1720
1765
  END_SRC
1721
1766
  end
1722
-
1767
+ alias :semantic_remote_form_for_real :semantic_remote_form_for
1768
+ alias :semantic_remote_form_for :semantic_remote_form_for_wrapper
1769
+ alias :semantic_form_remote_for :semantic_remote_form_for
1770
+
1723
1771
  end
1724
1772
  end