formtastic-rails3 0.9.7 → 0.9.10.0

Sign up to get free protection for your applications and to get access to all the features.
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