blue_light_special 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/README.rdoc +28 -0
  2. data/VERSION +1 -1
  3. data/app/controllers/blue_light_special/impersonations_controller.rb +6 -3
  4. data/test/rails_root/app/controllers/admin/admin_controller.rb +14 -0
  5. data/test/rails_root/app/controllers/admin/users_controller.rb +52 -0
  6. data/test/rails_root/config/initializers/blue_light_special.rb +20 -3
  7. data/test/rails_root/db/migrate/{20100305173127_blue_light_special_create_users.rb → 20100727214301_blue_light_special_create_users.rb} +4 -1
  8. data/test/rails_root/db/migrate/{20100305173129_create_delayed_jobs.rb → 20100727214302_create_delayed_jobs.rb} +0 -0
  9. data/test/rails_root/test/factories/user.rb +10 -0
  10. data/test/rails_root/test/integration/admin/users_test.rb +201 -0
  11. data/test/rails_root/test/integration/edit_profile_test.rb +35 -0
  12. data/test/rails_root/test/integration/facebook_test.rb +48 -36
  13. data/test/rails_root/test/integration/impersonation_test.rb +5 -4
  14. data/test/rails_root/test/integration/password_reset_test.rb +5 -4
  15. data/test/rails_root/test/integration/sign_in_test.rb +0 -6
  16. data/test/rails_root/test/integration/sign_up_test.rb +3 -40
  17. data/test/rails_root/vendor/gems/delayed_job-1.8.4/generators/delayed_job/delayed_job_generator.rb +22 -0
  18. data/test/rails_root/vendor/gems/delayed_job-1.8.4/generators/delayed_job/templates/migration.rb +20 -0
  19. data/test/rails_root/vendor/gems/delayed_job-1.8.4/init.rb +1 -0
  20. data/test/rails_root/vendor/gems/delayed_job-1.8.4/lib/delayed/command.rb +76 -0
  21. data/test/rails_root/vendor/gems/delayed_job-1.8.4/lib/delayed/job.rb +270 -0
  22. data/test/rails_root/vendor/gems/delayed_job-1.8.4/lib/delayed/message_sending.rb +22 -0
  23. data/test/rails_root/vendor/gems/delayed_job-1.8.4/lib/delayed/performable_method.rb +55 -0
  24. data/test/rails_root/vendor/gems/delayed_job-1.8.4/lib/delayed/recipes.rb +27 -0
  25. data/test/rails_root/vendor/gems/delayed_job-1.8.4/lib/delayed/tasks.rb +15 -0
  26. data/test/rails_root/vendor/gems/delayed_job-1.8.4/lib/delayed/worker.rb +54 -0
  27. data/test/rails_root/vendor/gems/delayed_job-1.8.4/lib/delayed_job.rb +13 -0
  28. data/test/rails_root/vendor/gems/delayed_job-1.8.4/recipes/delayed_job.rb +1 -0
  29. data/test/rails_root/vendor/gems/delayed_job-1.8.4/spec/database.rb +42 -0
  30. data/test/rails_root/vendor/gems/delayed_job-1.8.4/spec/delayed_method_spec.rb +150 -0
  31. data/test/rails_root/vendor/gems/delayed_job-1.8.4/spec/job_spec.rb +406 -0
  32. data/test/rails_root/vendor/gems/delayed_job-1.8.4/spec/story_spec.rb +17 -0
  33. data/test/rails_root/vendor/gems/justinfrench-formtastic-0.2.4/generators/formtastic_stylesheets/formtastic_stylesheets_generator.rb +21 -0
  34. data/test/rails_root/vendor/gems/justinfrench-formtastic-0.2.4/lib/formtastic.rb +1312 -0
  35. data/test/rails_root/vendor/gems/justinfrench-formtastic-0.2.4/lib/justin_french/formtastic.rb +10 -0
  36. data/test/rails_root/vendor/gems/justinfrench-formtastic-0.2.4/rails/init.rb +3 -0
  37. data/test/rails_root/vendor/gems/justinfrench-formtastic-0.2.4/spec/formtastic_spec.rb +3079 -0
  38. data/test/rails_root/vendor/gems/justinfrench-formtastic-0.2.4/spec/test_helper.rb +14 -0
  39. data/test/rails_root/vendor/gems/mini_fb-0.2.2/lib/mini_fb.rb +284 -0
  40. data/test/rails_root/vendor/gems/mini_fb-0.2.2/test/test_mini_fb.rb +29 -0
  41. metadata +111 -6
@@ -0,0 +1,17 @@
1
+ require File.dirname(__FILE__) + '/database'
2
+
3
+ describe "A story" do
4
+
5
+ before(:all) do
6
+ @story = Story.create :text => "Once upon a time..."
7
+ end
8
+
9
+ it "should be shared" do
10
+ @story.tell.should == 'Once upon a time...'
11
+ end
12
+
13
+ it "should not return its result if it storytelling is delayed" do
14
+ @story.send_later(:tell).should_not == 'Once upon a time...'
15
+ end
16
+
17
+ end
@@ -0,0 +1,21 @@
1
+ class FormtasticStylesheetsGenerator < Rails::Generator::Base
2
+
3
+ def initialize(*runtime_args)
4
+ super
5
+ end
6
+
7
+ def manifest
8
+ record do |m|
9
+ m.directory File.join('public', 'stylesheets')
10
+ m.template 'formtastic.css', File.join('public', 'stylesheets', 'formtastic.css')
11
+ m.template 'formtastic_changes.css', File.join('public', 'stylesheets', 'formtastic_changes.css')
12
+ end
13
+ end
14
+
15
+ protected
16
+
17
+ def banner
18
+ %{Usage: #{$0} #{spec.name}\nCopies formtastic.css and formtastic_changes.css to public/}
19
+ end
20
+
21
+ end
@@ -0,0 +1,1312 @@
1
+ # Override the default ActiveRecordHelper behaviour of wrapping the input.
2
+ # This gets taken care of semantically by adding an error class to the LI tag
3
+ # containing the input.
4
+ ActionView::Base.field_error_proc = proc do |html_tag, instance_tag|
5
+ html_tag
6
+ end
7
+
8
+ module Formtastic #:nodoc:
9
+
10
+ class SemanticFormBuilder < ActionView::Helpers::FormBuilder
11
+
12
+ @@default_text_field_size = 50
13
+ @@all_fields_required_by_default = true
14
+ @@required_string = proc { %{<abbr title="#{I18n.t 'formtastic.required', :default => 'required'}">*</abbr>} }
15
+ @@optional_string = ''
16
+ @@inline_errors = :sentence
17
+ @@label_str_method = :humanize
18
+ @@collection_label_methods = %w[to_label display_name full_name name title username login value to_s]
19
+ @@inline_order = [ :input, :hints, :errors ]
20
+ @@file_methods = [ :file?, :public_filename ]
21
+ @@priority_countries = ["Australia", "Canada", "United Kingdom", "United States"]
22
+ @@i18n_lookups_by_default = false
23
+
24
+ cattr_accessor :default_text_field_size, :all_fields_required_by_default, :required_string,
25
+ :optional_string, :inline_errors, :label_str_method, :collection_label_methods,
26
+ :inline_order, :file_methods, :priority_countries, :i18n_lookups_by_default
27
+
28
+ I18N_SCOPES = [ '{{model}}.{{action}}.{{attribute}}',
29
+ '{{model}}.{{attribute}}',
30
+ '{{attribute}}']
31
+
32
+ # Keeps simple mappings in a hash
33
+ INPUT_MAPPINGS = {
34
+ :string => :text_field,
35
+ :password => :password_field,
36
+ :numeric => :text_field,
37
+ :text => :text_area,
38
+ :file => :file_field
39
+ }
40
+ STRING_MAPPINGS = [ :string, :password, :numeric ]
41
+
42
+ attr_accessor :template
43
+
44
+ # Returns a suitable form input for the given +method+, using the database column information
45
+ # and other factors (like the method name) to figure out what you probably want.
46
+ #
47
+ # Options:
48
+ #
49
+ # * :as - override the input type (eg force a :string to render as a :password field)
50
+ # * :label - use something other than the method name as the label text, when false no label is printed
51
+ # * :required - specify if the column is required (true) or not (false)
52
+ # * :hint - provide some text to hint or help the user provide the correct information for a field
53
+ # * :input_html - provide options that will be passed down to the generated input
54
+ # * :wrapper_html - provide options that will be passed down to the li wrapper
55
+ #
56
+ # Input Types:
57
+ #
58
+ # Most inputs map directly to one of ActiveRecord's column types by default (eg string_input),
59
+ # but there are a few special cases and some simplification (:integer, :float and :decimal
60
+ # columns all map to a single numeric_input, for example).
61
+ #
62
+ # * :select (a select menu for associations) - default to association names
63
+ # * :check_boxes (a set of check_box inputs for associations) - alternative to :select has_many and has_and_belongs_to_many associations
64
+ # * :radio (a set of radio inputs for associations) - alternative to :select belongs_to associations
65
+ # * :time_zone (a select menu with time zones)
66
+ # * :password (a password input) - default for :string column types with 'password' in the method name
67
+ # * :text (a textarea) - default for :text column types
68
+ # * :date (a date select) - default for :date column types
69
+ # * :datetime (a date and time select) - default for :datetime and :timestamp column types
70
+ # * :time (a time select) - default for :time column types
71
+ # * :boolean (a checkbox) - default for :boolean column types (you can also have booleans as :select and :radio)
72
+ # * :string (a text field) - default for :string column types
73
+ # * :numeric (a text field, like string) - default for :integer, :float and :decimal column types
74
+ # * :country (a select menu of country names) - requires a country_select plugin to be installed
75
+ # * :hidden (a hidden field) - creates a hidden field (added for compatibility)
76
+ #
77
+ # Example:
78
+ #
79
+ # <% semantic_form_for @employee do |form| %>
80
+ # <% form.inputs do -%>
81
+ # <%= form.input :name, :label => "Full Name"%>
82
+ # <%= form.input :manager_id, :as => :radio %>
83
+ # <%= form.input :hired_at, :as => :date, :label => "Date Hired" %>
84
+ # <%= form.input :phone, :required => false, :hint => "Eg: +1 555 1234" %>
85
+ # <% end %>
86
+ # <% end %>
87
+ #
88
+ def input(method, options = {})
89
+ options[:required] = method_required?(method) unless options.key?(:required)
90
+ options[:as] ||= default_input_type(method)
91
+
92
+ html_class = [ options[:as], (options[:required] ? :required : :optional) ]
93
+ html_class << 'error' if @object && @object.respond_to?(:errors) && @object.errors[method.to_sym]
94
+
95
+ wrapper_html = options.delete(:wrapper_html) || {}
96
+ wrapper_html[:id] ||= generate_html_id(method)
97
+ wrapper_html[:class] = (html_class << wrapper_html[:class]).flatten.compact.join(' ')
98
+
99
+ if [:boolean_select, :boolean_radio].include?(options[:as])
100
+ ::ActiveSupport::Deprecation.warn(":as => :#{options[:as]} is deprecated, use :as => :#{options[:as].to_s[8..-1]} instead", caller[3..-1])
101
+ end
102
+
103
+ if options[:input_html] && options[:input_html][:id]
104
+ options[:label_html] ||= {}
105
+ options[:label_html][:for] ||= options[:input_html][:id]
106
+ end
107
+
108
+ input_parts = @@inline_order.dup
109
+ input_parts.delete(:errors) if options[:as] == :hidden
110
+
111
+ list_item_content = input_parts.map do |type|
112
+ send(:"inline_#{type}_for", method, options)
113
+ end.compact.join("\n")
114
+
115
+ return template.content_tag(:li, list_item_content, wrapper_html)
116
+ end
117
+
118
+ # Creates an input fieldset and ol tag wrapping for use around a set of inputs. It can be
119
+ # called either with a block (in which you can do the usual Rails form stuff, HTML, ERB, etc),
120
+ # or with a list of fields. These two examples are functionally equivalent:
121
+ #
122
+ # # With a block:
123
+ # <% semantic_form_for @post do |form| %>
124
+ # <% form.inputs do %>
125
+ # <%= form.input :title %>
126
+ # <%= form.input :body %>
127
+ # <% end %>
128
+ # <% end %>
129
+ #
130
+ # # With a list of fields:
131
+ # <% semantic_form_for @post do |form| %>
132
+ # <%= form.inputs :title, :body %>
133
+ # <% end %>
134
+ #
135
+ # # Output:
136
+ # <form ...>
137
+ # <fieldset class="inputs">
138
+ # <ol>
139
+ # <li class="string">...</li>
140
+ # <li class="text">...</li>
141
+ # </ol>
142
+ # </fieldset>
143
+ # </form>
144
+ #
145
+ # === Quick Forms
146
+ #
147
+ # When called without a block or a field list, an input is rendered for each column in the
148
+ # model's database table, just like Rails' scaffolding. You'll obviously want more control
149
+ # than this in a production application, but it's a great way to get started, then come back
150
+ # later to customise the form with a field list or a block of inputs. Example:
151
+ #
152
+ # <% semantic_form_for @post do |form| %>
153
+ # <%= form.inputs %>
154
+ # <% end %>
155
+ #
156
+ # === Options
157
+ #
158
+ # All options (with the exception of :name) are passed down to the fieldset as HTML
159
+ # attributes (id, class, style, etc). If provided, the :name option is passed into a
160
+ # legend tag inside the fieldset (otherwise a legend is not generated).
161
+ #
162
+ # # With a block:
163
+ # <% semantic_form_for @post do |form| %>
164
+ # <% form.inputs :name => "Create a new post", :style => "border:1px;" do %>
165
+ # ...
166
+ # <% end %>
167
+ # <% end %>
168
+ #
169
+ # # With a list (the options must come after the field list):
170
+ # <% semantic_form_for @post do |form| %>
171
+ # <%= form.inputs :title, :body, :name => "Create a new post", :style => "border:1px;" %>
172
+ # <% end %>
173
+ #
174
+ # === It's basically a fieldset!
175
+ #
176
+ # Instead of hard-coding fieldsets & legends into your form to logically group related fields,
177
+ # use inputs:
178
+ #
179
+ # <% semantic_form_for @post do |f| %>
180
+ # <% f.inputs do %>
181
+ # <%= f.input :title %>
182
+ # <%= f.input :body %>
183
+ # <% end %>
184
+ # <% f.inputs :name => "Advanced", :id => "advanced" do %>
185
+ # <%= f.input :created_at %>
186
+ # <%= f.input :user_id, :label => "Author" %>
187
+ # <% end %>
188
+ # <% end %>
189
+ #
190
+ # # Output:
191
+ # <form ...>
192
+ # <fieldset class="inputs">
193
+ # <ol>
194
+ # <li class="string">...</li>
195
+ # <li class="text">...</li>
196
+ # </ol>
197
+ # </fieldset>
198
+ # <fieldset class="inputs" id="advanced">
199
+ # <legend><span>Advanced</span></legend>
200
+ # <ol>
201
+ # <li class="datetime">...</li>
202
+ # <li class="select">...</li>
203
+ # </ol>
204
+ # </fieldset>
205
+ # </form>
206
+ #
207
+ # === Nested attributes
208
+ #
209
+ # As in Rails, you can use semantic_fields_for to nest attributes:
210
+ #
211
+ # <% semantic_form_for @post do |form| %>
212
+ # <%= form.inputs :title, :body %>
213
+ #
214
+ # <% form.semantic_fields_for :author, @bob do |author_form| %>
215
+ # <% author_form.inputs do %>
216
+ # <%= author_form.input :first_name, :required => false %>
217
+ # <%= author_form.input :last_name %>
218
+ # <% end %>
219
+ # <% end %>
220
+ # <% end %>
221
+ #
222
+ # But this does not look formtastic! This is equivalent:
223
+ #
224
+ # <% semantic_form_for @post do |form| %>
225
+ # <%= form.inputs :title, :body %>
226
+ # <% form.inputs :for => [ :author, @bob ] do |author_form| %>
227
+ # <%= author_form.input :first_name, :required => false %>
228
+ # <%= author_form.input :last_name %>
229
+ # <% end %>
230
+ # <% end %>
231
+ #
232
+ # And if you don't need to give options to your input call, you could do it
233
+ # in just one line:
234
+ #
235
+ # <% semantic_form_for @post do |form| %>
236
+ # <%= form.inputs :title, :body %>
237
+ # <%= form.inputs :first_name, :last_name, :for => @bob %>
238
+ # <% end %>
239
+ #
240
+ # Just remember that calling inputs generates a new fieldset to wrap your
241
+ # inputs. If you have two separate models, but, semantically, on the page
242
+ # they are part of the same fieldset, you should use semantic_fields_for
243
+ # instead (just as you would do with Rails' form builder).
244
+ #
245
+ def inputs(*args, &block)
246
+ html_options = args.extract_options!
247
+ html_options[:class] ||= "inputs"
248
+
249
+ if html_options[:for]
250
+ inputs_for_nested_attributes(args, html_options, &block)
251
+ elsif block_given?
252
+ field_set_and_list_wrapping(html_options, &block)
253
+ else
254
+ if @object && args.empty?
255
+ args = @object.class.reflections.map { |n,_| n if _.macro == :belongs_to }
256
+ args += @object.class.content_columns.map(&:name)
257
+ args -= %w[created_at updated_at created_on updated_on lock_version]
258
+ args.compact!
259
+ end
260
+ contents = args.map { |method| input(method.to_sym) }
261
+
262
+ field_set_and_list_wrapping(html_options, contents)
263
+ end
264
+ end
265
+ alias :input_field_set :inputs
266
+
267
+ # Creates a fieldset and ol tag wrapping for form buttons / actions as list items.
268
+ # See inputs documentation for a full example. The fieldset's default class attriute
269
+ # is set to "buttons".
270
+ #
271
+ # See inputs for html attributes and special options.
272
+ def buttons(*args, &block)
273
+ html_options = args.extract_options!
274
+ html_options[:class] ||= "buttons"
275
+
276
+ if block_given?
277
+ field_set_and_list_wrapping(html_options, &block)
278
+ else
279
+ args = [:commit] if args.empty?
280
+ contents = args.map { |button_name| send(:"#{button_name}_button") }
281
+ field_set_and_list_wrapping(html_options, contents)
282
+ end
283
+ end
284
+ alias :button_field_set :buttons
285
+
286
+ # Creates a submit input tag with the value "Save [model name]" (for existing records) or
287
+ # "Create [model name]" (for new records) by default:
288
+ #
289
+ # <%= form.commit_button %> => <input name="commit" type="submit" value="Save Post" />
290
+ #
291
+ # The value of the button text can be overridden:
292
+ #
293
+ # <%= form.commit_button "Go" %> => <input name="commit" type="submit" value="Go" />
294
+ #
295
+ # And you can pass html atributes down to the input, with or without the button text:
296
+ #
297
+ # <%= form.commit_button "Go" %> => <input name="commit" type="submit" value="Go" />
298
+ # <%= form.commit_button :class => "pretty" %> => <input name="commit" type="submit" value="Save Post" class="pretty" />
299
+
300
+ def commit_button(*args)
301
+ value = args.first.is_a?(String) ? args.shift : save_or_create_button_text
302
+ options = args.shift || {}
303
+ button_html = options.delete(:button_html) || {}
304
+ template.content_tag(:li, self.submit(value, button_html), :class => "commit")
305
+ end
306
+
307
+ # A thin wrapper around #fields_for to set :builder => Formtastic::SemanticFormBuilder
308
+ # for nesting forms:
309
+ #
310
+ # # Example:
311
+ # <% semantic_form_for @post do |post| %>
312
+ # <% post.semantic_fields_for :author do |author| %>
313
+ # <% author.inputs :name %>
314
+ # <% end %>
315
+ # <% end %>
316
+ #
317
+ # # Output:
318
+ # <form ...>
319
+ # <fieldset class="inputs">
320
+ # <ol>
321
+ # <li class="string"><input type='text' name='post[author][name]' id='post_author_name' /></li>
322
+ # </ol>
323
+ # </fieldset>
324
+ # </form>
325
+ #
326
+ def semantic_fields_for(record_or_name_or_array, *args, &block)
327
+ opts = args.extract_options!
328
+ opts.merge!(:builder => Formtastic::SemanticFormHelper.builder)
329
+ args.push(opts)
330
+ fields_for(record_or_name_or_array, *args, &block)
331
+ end
332
+
333
+ # Generates the label for the input. It also accepts the same arguments as
334
+ # Rails label method. It has three options that are not supported by Rails
335
+ # label method:
336
+ #
337
+ # * :required - Appends an abbr tag if :required is true
338
+ # * :label - An alternative form to give the label content. Whenever label
339
+ # is false, a blank string is returned.
340
+ # * :as_span - When true returns a span tag with class label instead of a label element
341
+ # * :input_name - Gives the input to match for. This is needed when you want to
342
+ # to call f.label :authors but it should match :author_ids.
343
+ #
344
+ # == Examples
345
+ #
346
+ # f.label :title # like in rails, except that it searches the label on I18n API too
347
+ #
348
+ # f.label :title, "Your post title"
349
+ # f.label :title, :label => "Your post title" # Added for formtastic API
350
+ #
351
+ # f.label :title, :required => true # Returns <label>Title<abbr title="required">*</abbr></label>
352
+ #
353
+ def label(method, options_or_text=nil, options=nil)
354
+ if options_or_text.is_a?(Hash)
355
+ return "" if options_or_text[:label] == false
356
+ options = options_or_text
357
+ text = options.delete(:label)
358
+ else
359
+ text = options_or_text
360
+ options ||= {}
361
+ end
362
+
363
+ text = localized_attribute_string(method, text, :label) || humanized_attribute_name(method)
364
+ text += required_or_optional_string(options.delete(:required))
365
+
366
+ input_name = options.delete(:input_name) || method
367
+ if options.delete(:as_span)
368
+ options[:class] ||= 'label'
369
+ template.content_tag(:span, text, options)
370
+ else
371
+ super(input_name, text, options)
372
+ end
373
+ end
374
+
375
+ # Generates error messages for the given method. Errors can be shown as list
376
+ # or as sentence. If :none is set, no error is shown.
377
+ #
378
+ # This method is also aliased as errors_on, so you can call on your custom
379
+ # inputs as well:
380
+ #
381
+ # semantic_form_for :post do |f|
382
+ # f.text_field(:body)
383
+ # f.errors_on(:body)
384
+ # end
385
+ #
386
+ def inline_errors_for(method, options=nil) #:nodoc:
387
+ return nil unless @object && @object.respond_to?(:errors) && [:sentence, :list].include?(@@inline_errors)
388
+
389
+ errors = @object.errors[method.to_sym]
390
+ send("error_#{@@inline_errors}", Array(errors)) unless errors.blank?
391
+ end
392
+ alias :errors_on :inline_errors_for
393
+
394
+ protected
395
+
396
+ # Prepare options to be sent to label
397
+ #
398
+ def options_for_label(options)
399
+ options.slice(:label, :required).merge!(options.fetch(:label_html, {}))
400
+ end
401
+
402
+ # Deals with :for option when it's supplied to inputs methods. Additional
403
+ # options to be passed down to :for should be supplied using :for_options
404
+ # key.
405
+ #
406
+ # It should raise an error if a block with arity zero is given.
407
+ #
408
+ def inputs_for_nested_attributes(args, options, &block)
409
+ args << options.merge!(:parent => { :builder => self, :for => options[:for] })
410
+
411
+ fields_for_block = if block_given?
412
+ raise ArgumentError, 'You gave :for option with a block to inputs method, ' <<
413
+ 'but the block does not accept any argument.' if block.arity <= 0
414
+
415
+ proc { |f| f.inputs(*args){ block.call(f) } }
416
+ else
417
+ proc { |f| f.inputs(*args) }
418
+ end
419
+
420
+ fields_for_args = [options.delete(:for), options.delete(:for_options) || {}].flatten
421
+ semantic_fields_for(*fields_for_args, &fields_for_block)
422
+ end
423
+
424
+ # Remove any Formtastic-specific options before passing the down options.
425
+ #
426
+ def set_options(options)
427
+ options.except(:value_method, :label_method, :collection, :required, :label,
428
+ :as, :hint, :input_html, :label_html, :value_as_class)
429
+ end
430
+
431
+ # Create a default button text. If the form is working with a object, it
432
+ # defaults to "Create model" or "Save model" depending if we are working
433
+ # with a new_record or not.
434
+ #
435
+ # When not working with models, it defaults to "Submit object".
436
+ #
437
+ def save_or_create_button_text(prefix='Submit') #:nodoc:
438
+ if @object
439
+ prefix = @object.new_record? ? 'Create' : 'Save'
440
+ object_name = @object.class.human_name
441
+ else
442
+ object_name = @object_name.to_s.send(@@label_str_method)
443
+ end
444
+
445
+ button_text = I18n.t(prefix.downcase, :default => prefix, :scope => [:formtastic])
446
+ "#{button_text} #{object_name}"
447
+ end
448
+
449
+ # Determins if the attribute (eg :title) should be considered required or not.
450
+ #
451
+ # * if the :required option was provided in the options hash, the true/false value will be
452
+ # returned immediately, allowing the view to override any guesswork that follows:
453
+ #
454
+ # * if the :required option isn't provided in the options hash, and the ValidationReflection
455
+ # plugin is installed (http://github.com/redinger/validation_reflection), true is returned
456
+ # if the validates_presence_of macro has been used in the class for this attribute, or false
457
+ # otherwise.
458
+ #
459
+ # * if the :required option isn't provided, and the plugin isn't available, the value of the
460
+ # configuration option @@all_fields_required_by_default is used.
461
+ #
462
+ def method_required?(attribute) #:nodoc:
463
+ if @object && @object.class.respond_to?(:reflect_on_all_validations)
464
+ attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym
465
+
466
+ @object.class.reflect_on_all_validations.any? do |validation|
467
+ validation.macro == :validates_presence_of && validation.name == attribute_sym
468
+ end
469
+ else
470
+ @@all_fields_required_by_default
471
+ end
472
+ end
473
+
474
+ # A method that deals with most of inputs (:string, :password, :file,
475
+ # :textarea and :numeric). :select, :radio, :boolean and :datetime inputs
476
+ # are not handled by this method, since they need more detailed approach.
477
+ #
478
+ # If input_html is given as option, it's passed down to the input.
479
+ #
480
+ def input_simple(type, method, options)
481
+ html_options = options.delete(:input_html) || {}
482
+ html_options = default_string_options(method, type).merge(html_options) if STRING_MAPPINGS.include?(type)
483
+
484
+ self.label(method, options_for_label(options)) +
485
+ self.send(INPUT_MAPPINGS[type], method, html_options)
486
+ end
487
+
488
+ # Outputs a hidden field inside the wrapper, which should be hidden with CSS.
489
+ # Additionals options can be given and will be sent straight to hidden input
490
+ # element.
491
+ #
492
+ def hidden_input(method, options)
493
+ self.hidden_field(method, set_options(options))
494
+ end
495
+
496
+ # Outputs a label and a select box containing options from the parent
497
+ # (belongs_to, has_many, has_and_belongs_to_many) association. If an association
498
+ # is has_many or has_and_belongs_to_many the select box will be set as multi-select
499
+ # and size = 5
500
+ #
501
+ # Example (belongs_to):
502
+ #
503
+ # f.input :author
504
+ #
505
+ # <label for="book_author_id">Author</label>
506
+ # <select id="book_author_id" name="book[author_id]">
507
+ # <option value=""></option>
508
+ # <option value="1">Justin French</option>
509
+ # <option value="2">Jane Doe</option>
510
+ # </select>
511
+ #
512
+ # Example (has_many):
513
+ #
514
+ # f.input :chapters
515
+ #
516
+ # <label for="book_chapter_ids">Chapters</label>
517
+ # <select id="book_chapter_ids" name="book[chapter_ids]">
518
+ # <option value=""></option>
519
+ # <option value="1">Chapter 1</option>
520
+ # <option value="2">Chapter 2</option>
521
+ # </select>
522
+ #
523
+ # Example (has_and_belongs_to_many):
524
+ #
525
+ # f.input :authors
526
+ #
527
+ # <label for="book_author_ids">Authors</label>
528
+ # <select id="book_author_ids" name="book[author_ids]">
529
+ # <option value=""></option>
530
+ # <option value="1">Justin French</option>
531
+ # <option value="2">Jane Doe</option>
532
+ # </select>
533
+ #
534
+ #
535
+ # You can customize the options available in the select by passing in a collection (an Array or
536
+ # Hash) through the :collection option. If not provided, the choices are found by inferring the
537
+ # parent's class name from the method name and simply calling find(:all) on it
538
+ # (VehicleOwner.find(:all) in the example above).
539
+ #
540
+ # Examples:
541
+ #
542
+ # f.input :author, :collection => @authors
543
+ # f.input :author, :collection => Author.find(:all)
544
+ # f.input :author, :collection => [@justin, @kate]
545
+ # f.input :author, :collection => {@justin.name => @justin.id, @kate.name => @kate.id}
546
+ # f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
547
+ #
548
+ # Note: This input looks for a label method in the parent association.
549
+ #
550
+ # You can customize the text label inside each option tag, by naming the correct method
551
+ # (:full_name, :display_name, :account_number, etc) to call on each object in the collection
552
+ # by passing in the :label_method option. By default the :label_method is whichever element of
553
+ # Formtastic::SemanticFormBuilder.collection_label_methods is found first.
554
+ #
555
+ # Examples:
556
+ #
557
+ # f.input :author, :label_method => :full_name
558
+ # f.input :author, :label_method => :display_name
559
+ # f.input :author, :label_method => :to_s
560
+ # f.input :author, :label_method => :label
561
+ #
562
+ # You can also customize the value inside each option tag, by passing in the :value_method option.
563
+ # Usage is the same as the :label_method option
564
+ #
565
+ # Examples:
566
+ #
567
+ # f.input :author, :value_method => :full_name
568
+ # f.input :author, :value_method => :display_name
569
+ # f.input :author, :value_method => :to_s
570
+ # f.input :author, :value_method => :value
571
+ #
572
+ # You can pass html_options to the select tag using :input_html => {}
573
+ #
574
+ # Examples:
575
+ #
576
+ # f.input :authors, :input_html => {:size => 20, :multiple => true}
577
+ #
578
+ # By default, all select inputs will have a blank option at the top of the list. You can add
579
+ # a prompt with the :prompt option, or disable the blank option with :include_blank => false.
580
+ #
581
+ def select_input(method, options)
582
+ collection = find_collection_for_column(method, options)
583
+ html_options = options.delete(:input_html) || {}
584
+
585
+ unless options.key?(:include_blank) || options.key?(:prompt)
586
+ options[:include_blank] = true
587
+ end
588
+
589
+ reflection = find_reflection(method)
590
+ if reflection && [ :has_many, :has_and_belongs_to_many ].include?(reflection.macro)
591
+ options[:include_blank] = false
592
+ html_options[:multiple] ||= true
593
+ html_options[:size] ||= 5
594
+ end
595
+
596
+ input_name = generate_association_input_name(method)
597
+ self.label(method, options_for_label(options).merge(:input_name => input_name)) +
598
+ self.select(input_name, collection, set_options(options), html_options)
599
+ end
600
+ alias :boolean_select_input :select_input
601
+
602
+ # Outputs a timezone select input as Rails' time_zone_select helper. You
603
+ # can give priority zones as option.
604
+ #
605
+ # Examples:
606
+ #
607
+ # f.input :time_zone, :as => :time_zone, :priority_zones => /Australia/
608
+ #
609
+ def time_zone_input(method, options)
610
+ html_options = options.delete(:input_html) || {}
611
+
612
+ self.label(method, options_for_label(options)) +
613
+ self.time_zone_select(method, options.delete(:priority_zones), set_options(options), html_options)
614
+ end
615
+
616
+ # Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
617
+ # items, one for each possible choice in the belongs_to association. Each li contains a
618
+ # label and a radio input.
619
+ #
620
+ # Example:
621
+ #
622
+ # f.input :author, :as => :radio
623
+ #
624
+ # Output:
625
+ #
626
+ # <fieldset>
627
+ # <legend><span>Author</span></legend>
628
+ # <ol>
629
+ # <li>
630
+ # <label for="book_author_id_1"><input id="book_author_id_1" name="book[author_id]" type="radio" value="1" /> Justin French</label>
631
+ # </li>
632
+ # <li>
633
+ # <label for="book_author_id_2"><input id="book_author_id_2" name="book[owner_id]" type="radio" value="2" /> Kate French</label>
634
+ # </li>
635
+ # </ol>
636
+ # </fieldset>
637
+ #
638
+ # You can customize the options available in the select by passing in a collection (an Array or
639
+ # Hash) through the :collection option. If not provided, the choices are found by inferring the
640
+ # parent's class name from the method name and simply calling find(:all) on it
641
+ # (Author.find(:all) in the example above).
642
+ #
643
+ # Examples:
644
+ #
645
+ # f.input :author, :as => :radio, :collection => @authors
646
+ # f.input :author, :as => :radio, :collection => Author.find(:all)
647
+ # f.input :author, :as => :radio, :collection => [@justin, @kate]
648
+ # f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
649
+ #
650
+ # You can also customize the text label inside each option tag, by naming the correct method
651
+ # (:full_name, :display_name, :account_number, etc) to call on each object in the collection
652
+ # by passing in the :label_method option. By default the :label_method is whichever element of
653
+ # Formtastic::SemanticFormBuilder.collection_label_methods is found first.
654
+ #
655
+ # Examples:
656
+ #
657
+ # f.input :author, :as => :radio, :label_method => :full_name
658
+ # f.input :author, :as => :radio, :label_method => :display_name
659
+ # f.input :author, :as => :radio, :label_method => :to_s
660
+ # f.input :author, :as => :radio, :label_method => :label
661
+ #
662
+ # Finally, you can set :value_as_class => true if you want that LI wrappers
663
+ # contains a class with the wrapped radio input value.
664
+ #
665
+ def radio_input(method, options)
666
+ collection = find_collection_for_column(method, options)
667
+ html_options = set_options(options).merge(options.delete(:input_html) || {})
668
+
669
+ input_name = generate_association_input_name(method)
670
+ value_as_class = options.delete(:value_as_class)
671
+
672
+ list_item_content = collection.map do |c|
673
+ label = c.is_a?(Array) ? c.first : c
674
+ value = c.is_a?(Array) ? c.last : c
675
+
676
+ li_content = template.content_tag(:label,
677
+ "#{self.radio_button(input_name, value, html_options)} #{label}",
678
+ :for => generate_html_id(input_name, value.to_s.downcase)
679
+ )
680
+
681
+ li_options = value_as_class ? { :class => value.to_s.downcase } : {}
682
+ template.content_tag(:li, li_content, li_options)
683
+ end
684
+
685
+ field_set_and_list_wrapping_for_method(method, options, list_item_content)
686
+ end
687
+ alias :boolean_radio_input :radio_input
688
+
689
+ # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
690
+ # items (li), one for each fragment for the date (year, month, day). Each li contains a label
691
+ # (eg "Year") and a select box. See date_or_datetime_input for a more detailed output example.
692
+ #
693
+ # Some of Rails' options for select_date are supported, but not everything yet.
694
+ def date_input(method, options)
695
+ date_or_datetime_input(method, options.merge(:discard_hour => true))
696
+ end
697
+
698
+
699
+ # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
700
+ # items (li), one for each fragment for the date (year, month, day, hour, min, sec). Each li
701
+ # contains a label (eg "Year") and a select box. See date_or_datetime_input for a more
702
+ # detailed output example.
703
+ #
704
+ # Some of Rails' options for select_date are supported, but not everything yet.
705
+ def datetime_input(method, options)
706
+ date_or_datetime_input(method, options)
707
+ end
708
+
709
+
710
+ # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
711
+ # items (li), one for each fragment for the time (hour, minute, second). Each li contains a label
712
+ # (eg "Hour") and a select box. See date_or_datetime_input for a more detailed output example.
713
+ #
714
+ # Some of Rails' options for select_time are supported, but not everything yet.
715
+ def time_input(method, options)
716
+ date_or_datetime_input(method, options.merge(:discard_year => true, :discard_month => true, :discard_day => true))
717
+ end
718
+
719
+
720
+ # <fieldset>
721
+ # <legend>Created At</legend>
722
+ # <ol>
723
+ # <li>
724
+ # <label for="user_created_at_1i">Year</label>
725
+ # <select id="user_created_at_1i" name="user[created_at(1i)]">
726
+ # <option value="2003">2003</option>
727
+ # ...
728
+ # <option value="2013">2013</option>
729
+ # </select>
730
+ # </li>
731
+ # <li>
732
+ # <label for="user_created_at_2i">Month</label>
733
+ # <select id="user_created_at_2i" name="user[created_at(2i)]">
734
+ # <option value="1">January</option>
735
+ # ...
736
+ # <option value="12">December</option>
737
+ # </select>
738
+ # </li>
739
+ # <li>
740
+ # <label for="user_created_at_3i">Day</label>
741
+ # <select id="user_created_at_3i" name="user[created_at(3i)]">
742
+ # <option value="1">1</option>
743
+ # ...
744
+ # <option value="31">31</option>
745
+ # </select>
746
+ # </li>
747
+ # </ol>
748
+ # </fieldset>
749
+ #
750
+ # This is an absolute abomination, but so is the official Rails select_date().
751
+ #
752
+ def date_or_datetime_input(method, options)
753
+ position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
754
+ inputs = options.delete(:order) || I18n.translate(:'date.order') || [:year, :month, :day]
755
+
756
+ time_inputs = [:hour, :minute]
757
+ time_inputs << [:second] if options[:include_seconds]
758
+
759
+ list_items_capture = ""
760
+ hidden_fields_capture = ""
761
+
762
+ # Gets the datetime object. It can be a Fixnum, Date or Time, or nil.
763
+ datetime = @object ? @object.send(method) : nil
764
+ html_options = options.delete(:input_html) || {}
765
+
766
+ (inputs + time_inputs).each do |input|
767
+ html_id = generate_html_id(method, "#{position[input]}i")
768
+ field_name = "#{method}(#{position[input]}i)"
769
+ if options["discard_#{input}".intern]
770
+ break if time_inputs.include?(input)
771
+
772
+ hidden_value = datetime.respond_to?(input) ? datetime.send(input) : datetime
773
+ hidden_fields_capture << template.hidden_field_tag("#{@object_name}[#{field_name}]", (hidden_value || 1), :id => html_id)
774
+ else
775
+ opts = set_options(options).merge(:prefix => @object_name, :field_name => field_name)
776
+ item_label_text = I18n.t(input.to_s, :default => input.to_s.humanize, :scope => [:datetime, :prompts])
777
+
778
+ list_items_capture << template.content_tag(:li,
779
+ template.content_tag(:label, item_label_text, :for => html_id) +
780
+ template.send("select_#{input}".intern, datetime, opts, html_options.merge(:id => html_id))
781
+ )
782
+ end
783
+ end
784
+
785
+ hidden_fields_capture + field_set_and_list_wrapping_for_method(method, options, list_items_capture)
786
+ end
787
+
788
+
789
+ # Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
790
+ # items, one for each possible choice in the belongs_to association. Each li contains a
791
+ # label and a check_box input.
792
+ #
793
+ # This is an alternative for has many and has and belongs to many associations.
794
+ #
795
+ # Example:
796
+ #
797
+ # f.input :author, :as => :check_boxes
798
+ #
799
+ # Output:
800
+ #
801
+ # <fieldset>
802
+ # <legend><span>Authors</span></legend>
803
+ # <ol>
804
+ # <li>
805
+ # <input type="hidden" name="book[author_id][1]" value="">
806
+ # <label for="book_author_id_1"><input id="book_author_id_1" name="book[author_id][1]" type="checkbox" value="1" /> Justin French</label>
807
+ # </li>
808
+ # <li>
809
+ # <input type="hidden" name="book[author_id][2]" value="">
810
+ # <label for="book_author_id_2"><input id="book_author_id_2" name="book[owner_id][2]" type="checkbox" value="2" /> Kate French</label>
811
+ # </li>
812
+ # </ol>
813
+ # </fieldset>
814
+ #
815
+ # Notice that the value of the checkbox is the same as the id and the hidden
816
+ # field has empty value. You can override the hidden field value using the
817
+ # unchecked_value option.
818
+ #
819
+ # You can customize the options available in the set by passing in a collection (Array) of
820
+ # ActiveRecord objects through the :collection option. If not provided, the choices are found
821
+ # by inferring the parent's class name from the method name and simply calling find(:all) on
822
+ # it (Author.find(:all) in the example above).
823
+ #
824
+ # Examples:
825
+ #
826
+ # f.input :author, :as => :check_boxes, :collection => @authors
827
+ # f.input :author, :as => :check_boxes, :collection => Author.find(:all)
828
+ # f.input :author, :as => :check_boxes, :collection => [@justin, @kate]
829
+ #
830
+ # You can also customize the text label inside each option tag, by naming the correct method
831
+ # (:full_name, :display_name, :account_number, etc) to call on each object in the collection
832
+ # by passing in the :label_method option. By default the :label_method is whichever element of
833
+ # Formtastic::SemanticFormBuilder.collection_label_methods is found first.
834
+ #
835
+ # Examples:
836
+ #
837
+ # f.input :author, :as => :check_boxes, :label_method => :full_name
838
+ # f.input :author, :as => :check_boxes, :label_method => :display_name
839
+ # f.input :author, :as => :check_boxes, :label_method => :to_s
840
+ # f.input :author, :as => :check_boxes, :label_method => :label
841
+ #
842
+ # You can set :value_as_class => true if you want that LI wrappers contains
843
+ # a class with the wrapped checkbox input value.
844
+ #
845
+ def check_boxes_input(method, options)
846
+ collection = find_collection_for_column(method, options)
847
+ html_options = options.delete(:input_html) || {}
848
+
849
+ input_name = generate_association_input_name(method)
850
+ value_as_class = options.delete(:value_as_class)
851
+ unchecked_value = options.delete(:unchecked_value) || ''
852
+ html_options = { :name => "#{@object_name}[#{input_name}][]" }.merge(html_options)
853
+
854
+ list_item_content = collection.map do |c|
855
+ label = c.is_a?(Array) ? c.first : c
856
+ value = c.is_a?(Array) ? c.last : c
857
+
858
+ html_options.merge!(:id => generate_html_id(input_name, value.to_s.downcase))
859
+
860
+ li_content = template.content_tag(:label,
861
+ "#{self.check_box(input_name, html_options, value, unchecked_value)} #{label}",
862
+ :for => html_options[:id]
863
+ )
864
+
865
+ li_options = value_as_class ? { :class => value.to_s.downcase } : {}
866
+ template.content_tag(:li, li_content, li_options)
867
+ end
868
+
869
+ field_set_and_list_wrapping_for_method(method, options, list_item_content)
870
+ end
871
+
872
+
873
+ # Outputs a country select input, wrapping around a regular country_select helper.
874
+ # Rails doesn't come with a country_select helper by default any more, so you'll need to install
875
+ # the "official" plugin, or, if you wish, any other country_select plugin that behaves in the
876
+ # same way.
877
+ #
878
+ # The Rails plugin iso-3166-country-select plugin can be found "here":http://github.com/rails/iso-3166-country-select.
879
+ #
880
+ # By default, Formtastic includes a handfull of english-speaking countries as "priority counties",
881
+ # which you can change to suit your market and user base (see README for more info on config).
882
+ #
883
+ # Examples:
884
+ # f.input :location, :as => :country # use Formtastic::SemanticFormBuilder.priority_countries array for the priority countries
885
+ # f.input :location, :as => :country, :priority_countries => /Australia/ # set your own
886
+ #
887
+ def country_input(method, options)
888
+ 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)
889
+
890
+ html_options = options.delete(:input_html) || {}
891
+ priority_countries = options.delete(:priority_countries) || @@priority_countries
892
+
893
+ self.label(method, options_for_label(options)) +
894
+ self.country_select(method, priority_countries, set_options(options), html_options)
895
+ end
896
+
897
+
898
+ # Outputs a label containing a checkbox and the label text. The label defaults
899
+ # to the column name (method name) and can be altered with the :label option.
900
+ # :checked_value and :unchecked_value options are also available.
901
+ #
902
+ def boolean_input(method, options)
903
+ html_options = options.delete(:input_html) || {}
904
+
905
+ input = self.check_box(method, set_options(options).merge(html_options),
906
+ options.delete(:checked_value) || '1', options.delete(:unchecked_value) || '0')
907
+
908
+ label = options.delete(:label) || humanized_attribute_name(method)
909
+ self.label(method, input + label, options_for_label(options))
910
+ end
911
+
912
+ # Generates an input for the given method using the type supplied with :as.
913
+ #
914
+ # If the input is included in INPUT_MAPPINGS, it uses input_simple
915
+ # implementation which maps most of the inputs. All others have specific
916
+ # code and then a proper handler should be called (like radio_input) for
917
+ # :radio types.
918
+ #
919
+ def inline_input_for(method, options)
920
+ input_type = options.delete(:as)
921
+
922
+ if INPUT_MAPPINGS.key?(input_type)
923
+ input_simple(input_type, method, options)
924
+ else
925
+ send("#{input_type}_input", method, options)
926
+ end
927
+ end
928
+
929
+ # Generates hints for the given method using the text supplied in :hint.
930
+ #
931
+ def inline_hints_for(method, options) #:nodoc:
932
+ options[:hint] = localized_attribute_string(method, options[:hint], :hint)
933
+ return if options[:hint].blank?
934
+ template.content_tag(:p, options[:hint], :class => 'inline-hints')
935
+ end
936
+
937
+ # Creates an error sentence by calling to_sentence on the errors array.
938
+ #
939
+ def error_sentence(errors) #:nodoc:
940
+ template.content_tag(:p, errors.to_sentence.untaint, :class => 'inline-errors')
941
+ end
942
+
943
+ # Creates an error li list.
944
+ #
945
+ def error_list(errors) #:nodoc:
946
+ list_elements = []
947
+ errors.each do |error|
948
+ list_elements << template.content_tag(:li, error.untaint)
949
+ end
950
+ template.content_tag(:ul, list_elements.join("\n"), :class => 'errors')
951
+ end
952
+
953
+ # Generates the required or optional string. If the value set is a proc,
954
+ # it evaluates the proc first.
955
+ #
956
+ def required_or_optional_string(required) #:nodoc:
957
+ string_or_proc = case required
958
+ when true
959
+ @@required_string
960
+ when false
961
+ @@optional_string
962
+ else
963
+ required
964
+ end
965
+
966
+ if string_or_proc.is_a?(Proc)
967
+ string_or_proc.call
968
+ else
969
+ string_or_proc.to_s
970
+ end
971
+ end
972
+
973
+ # Generates a fieldset and wraps the content in an ordered list. When working
974
+ # with nested attributes (in Rails 2.3), it allows %i as interpolation option
975
+ # in :name. So you can do:
976
+ #
977
+ # f.inputs :name => 'Task #%i', :for => :tasks
978
+ #
979
+ # And it will generate a fieldset for each task with legend 'Task #1', 'Task #2',
980
+ # 'Task #3' and so on.
981
+ #
982
+ def field_set_and_list_wrapping(html_options, contents='', &block) #:nodoc:
983
+ html_options[:name] ||= html_options.delete(:title)
984
+ html_options[:name] = localized_attribute_string(html_options[:name], html_options[:name], :title) if html_options[:name].is_a?(Symbol)
985
+
986
+ legend = html_options.delete(:name).to_s
987
+ legend %= parent_child_index(html_options[:parent]) if html_options[:parent]
988
+ legend = template.content_tag(:legend, template.content_tag(:span, legend)) unless legend.blank?
989
+
990
+ contents = template.capture(&block) if block_given?
991
+
992
+ # Ruby 1.9: String#to_s behavior changed, need to make an explicit join.
993
+ contents = contents.join if contents.respond_to?(:join)
994
+ fieldset = template.content_tag(:fieldset,
995
+ legend + template.content_tag(:ol, contents),
996
+ html_options.except(:builder, :parent)
997
+ )
998
+
999
+ template.concat(fieldset) if block_given?
1000
+ fieldset
1001
+ end
1002
+
1003
+ # Also generates a fieldset and an ordered list but with label based in
1004
+ # method. This methods is currently used by radio and datetime inputs.
1005
+ #
1006
+ def field_set_and_list_wrapping_for_method(method, options, contents)
1007
+ contents = contents.join if contents.respond_to?(:join)
1008
+
1009
+ template.content_tag(:fieldset,
1010
+ %{<legend>#{self.label(method, options_for_label(options).merge!(:as_span => true))}</legend>} +
1011
+ template.content_tag(:ol, contents)
1012
+ )
1013
+ end
1014
+
1015
+ # For methods that have a database column, take a best guess as to what the input method
1016
+ # should be. In most cases, it will just return the column type (eg :string), but for special
1017
+ # cases it will simplify (like the case of :integer, :float & :decimal to :numeric), or do
1018
+ # something different (like :password and :select).
1019
+ #
1020
+ # If there is no column for the method (eg "virtual columns" with an attr_accessor), the
1021
+ # default is a :string, a similar behaviour to Rails' scaffolding.
1022
+ #
1023
+ def default_input_type(method) #:nodoc:
1024
+ column = @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
1025
+
1026
+ if column
1027
+ # handle the special cases where the column type doesn't map to an input method
1028
+ return :time_zone if column.type == :string && method.to_s =~ /time_zone/
1029
+ return :select if column.type == :integer && method.to_s =~ /_id$/
1030
+ return :datetime if column.type == :timestamp
1031
+ return :numeric if [:integer, :float, :decimal].include?(column.type)
1032
+ return :password if column.type == :string && method.to_s =~ /password/
1033
+ return :country if column.type == :string && method.to_s =~ /country/
1034
+
1035
+ # otherwise assume the input name will be the same as the column type (eg string_input)
1036
+ return column.type
1037
+ else
1038
+ if @object
1039
+ return :select if find_reflection(method)
1040
+
1041
+ file = @object.send(method) if @object.respond_to?(method)
1042
+ return :file if file && @@file_methods.any? { |m| file.respond_to?(m) }
1043
+ end
1044
+
1045
+ return :password if method.to_s =~ /password/
1046
+ return :string
1047
+ end
1048
+ end
1049
+
1050
+ # Used by select and radio inputs. The collection can be retrieved by
1051
+ # three ways:
1052
+ #
1053
+ # * Explicitly provided through :collection
1054
+ # * Retrivied through an association
1055
+ # * Or a boolean column, which will generate a localized { "Yes" => true, "No" => false } hash.
1056
+ #
1057
+ # If the collection is not a hash or an array of strings, fixnums or arrays,
1058
+ # we use label_method and value_method to retreive an array with the
1059
+ # appropriate label and value.
1060
+ #
1061
+ def find_collection_for_column(column, options)
1062
+ reflection = find_reflection(column)
1063
+
1064
+ collection = if options[:collection]
1065
+ options.delete(:collection)
1066
+ elsif reflection || column.to_s =~ /_id$/
1067
+ parent_class = if reflection
1068
+ reflection.klass
1069
+ else
1070
+ ::ActiveSupport::Deprecation.warn("The _id way of doing things is deprecated. Please use the association method (#{column.to_s.sub(/_id$/,'')})", caller[3..-1])
1071
+ column.to_s.sub(/_id$/,'').camelize.constantize
1072
+ end
1073
+
1074
+ parent_class.find(:all)
1075
+ else
1076
+ create_boolean_collection(options)
1077
+ end
1078
+
1079
+ collection = collection.to_a if collection.instance_of?(Hash)
1080
+
1081
+ # Return if we have an Array of strings, fixnums or arrays
1082
+ return collection if collection.instance_of?(Array) &&
1083
+ [Array, Fixnum, String, Symbol].include?(collection.first.class)
1084
+
1085
+ label = options.delete(:label_method) || detect_label_method(collection)
1086
+ value = options.delete(:value_method) || :id
1087
+
1088
+ collection.map { |o| [o.send(label), o.send(value)] }
1089
+ end
1090
+
1091
+ # Detected the label collection method when none is supplied using the
1092
+ # values set in @@collection_label_methods.
1093
+ #
1094
+ def detect_label_method(collection) #:nodoc:
1095
+ @@collection_label_methods.detect { |m| collection.first.respond_to?(m) }
1096
+ end
1097
+
1098
+ # Returns a hash to be used by radio and select inputs when a boolean field
1099
+ # is provided.
1100
+ #
1101
+ def create_boolean_collection(options)
1102
+ options[:true] ||= I18n.t('yes', :default => 'Yes', :scope => [:formtastic])
1103
+ options[:false] ||= I18n.t('no', :default => 'No', :scope => [:formtastic])
1104
+ options[:value_as_class] = true unless options.key?(:value_as_class)
1105
+
1106
+ [ [ options.delete(:true), true], [ options.delete(:false), false ] ]
1107
+ end
1108
+
1109
+ # Used by association inputs (select, radio) to generate the name that should
1110
+ # be used for the input
1111
+ #
1112
+ # belongs_to :author; f.input :author; will generate 'author_id'
1113
+ # belongs_to :entity, :foreign_key = :owner_id; f.input :author; will generate 'owner_id'
1114
+ # has_many :authors; f.input :authors; will generate 'author_ids'
1115
+ # has_and_belongs_to_many will act like has_many
1116
+ #
1117
+ def generate_association_input_name(method)
1118
+ if reflection = find_reflection(method)
1119
+ if [:has_and_belongs_to_many, :has_many].include?(reflection.macro)
1120
+ "#{method.to_s.singularize}_ids"
1121
+ else
1122
+ reflection.options[:foreign_key] || "#{method}_id"
1123
+ end
1124
+ else
1125
+ method
1126
+ end
1127
+ end
1128
+
1129
+ # If an association method is passed in (f.input :author) try to find the
1130
+ # reflection object.
1131
+ #
1132
+ def find_reflection(method)
1133
+ @object.class.reflect_on_association(method) if @object.class.respond_to?(:reflect_on_association)
1134
+ end
1135
+
1136
+ # Generates default_string_options by retrieving column information from
1137
+ # the database.
1138
+ #
1139
+ def default_string_options(method, type) #:nodoc:
1140
+ column = @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
1141
+
1142
+ if type == :numeric || column.nil? || column.limit.nil?
1143
+ { :size => @@default_text_field_size }
1144
+ else
1145
+ { :maxlength => column.limit, :size => [column.limit, @@default_text_field_size].min }
1146
+ end
1147
+ end
1148
+
1149
+ # Generate the html id for the li tag.
1150
+ # It takes into account options[:index] and @auto_index to generate li
1151
+ # elements with appropriate index scope. It also sanitizes the object
1152
+ # and method names.
1153
+ #
1154
+ def generate_html_id(method_name, value='input')
1155
+ if options.has_key?(:index)
1156
+ index = "_#{options[:index]}"
1157
+ elsif defined?(@auto_index)
1158
+ index = "_#{@auto_index}"
1159
+ else
1160
+ index = ""
1161
+ end
1162
+ sanitized_method_name = method_name.to_s.gsub(/[\?\/\-]$/, '')
1163
+
1164
+ "#{sanitized_object_name}#{index}_#{sanitized_method_name}_#{value}"
1165
+ end
1166
+
1167
+ # Gets the nested_child_index value from the parent builder. In Rails 2.3
1168
+ # it always returns a fixnum. In next versions it returns a hash with each
1169
+ # association that the parent builds.
1170
+ #
1171
+ def parent_child_index(parent)
1172
+ duck = parent[:builder].instance_variable_get('@nested_child_index')
1173
+
1174
+ if duck.is_a?(Hash)
1175
+ child = parent[:for]
1176
+ child = child.first if child.respond_to?(:first)
1177
+ duck[child].to_i + 1
1178
+ else
1179
+ duck.to_i + 1
1180
+ end
1181
+ end
1182
+
1183
+ def sanitized_object_name
1184
+ @sanitized_object_name ||= @object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
1185
+ end
1186
+
1187
+ def humanized_attribute_name(method)
1188
+ if @object && @object.class.respond_to?(:human_attribute_name)
1189
+ @object.class.human_attribute_name(method.to_s)
1190
+ else
1191
+ method.to_s.send(@@label_str_method)
1192
+ end
1193
+ end
1194
+
1195
+ # Internal generic method for looking up localized values within Formtastic
1196
+ # using I18n, if no explicit value is set and I18n-lookups are enabled.
1197
+ #
1198
+ # Enabled/Disable this by setting:
1199
+ #
1200
+ # Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true/false
1201
+ #
1202
+ # Lookup priority:
1203
+ #
1204
+ # 'formtastic.{{type}}.{{model}}.{{action}}.{{attribute}}'
1205
+ # 'formtastic.{{type}}.{{model}}.{{attribute}}'
1206
+ # 'formtastic.{{type}}.{{attribute}}'
1207
+ #
1208
+ # Example:
1209
+ #
1210
+ # 'formtastic.labels.post.edit.title'
1211
+ # 'formtastic.labels.post.title'
1212
+ # 'formtastic.labels.title'
1213
+ #
1214
+ # NOTE: Generic, but only used for form input labels/hints.
1215
+ #
1216
+ def localized_attribute_string(attr_name, attr_value, i18n_key)
1217
+ if attr_value.is_a?(String)
1218
+ attr_value
1219
+ else
1220
+ use_i18n = attr_value.nil? ? @@i18n_lookups_by_default : (attr_value != false)
1221
+
1222
+ if use_i18n
1223
+ model_name = @object.class.name.underscore
1224
+ action_name = template.params[:action].to_s rescue ''
1225
+ attribute_name = attr_name.to_s
1226
+
1227
+ defaults = I18N_SCOPES.collect do |i18n_scope|
1228
+ i18n_path = i18n_scope.dup
1229
+ i18n_path.gsub!('{{action}}', action_name)
1230
+ i18n_path.gsub!('{{model}}', model_name)
1231
+ i18n_path.gsub!('{{attribute}}', attribute_name)
1232
+ i18n_path.gsub!('..', '.')
1233
+ i18n_path.to_sym
1234
+ end
1235
+ defaults << ''
1236
+
1237
+ i18n_value = ::I18n.t(defaults.shift, :default => defaults,
1238
+ :scope => "formtastic.#{i18n_key.to_s.pluralize}")
1239
+ i18n_value.blank? ? nil : i18n_value
1240
+ end
1241
+ end
1242
+ end
1243
+
1244
+ end
1245
+
1246
+ # Wrappers around form_for (etc) with :builder => SemanticFormBuilder.
1247
+ #
1248
+ # * semantic_form_for(@post)
1249
+ # * semantic_fields_for(@post)
1250
+ # * semantic_form_remote_for(@post)
1251
+ # * semantic_remote_form_for(@post)
1252
+ #
1253
+ # Each of which are the equivalent of:
1254
+ #
1255
+ # * form_for(@post, :builder => Formtastic::SemanticFormBuilder))
1256
+ # * fields_for(@post, :builder => Formtastic::SemanticFormBuilder))
1257
+ # * form_remote_for(@post, :builder => Formtastic::SemanticFormBuilder))
1258
+ # * remote_form_for(@post, :builder => Formtastic::SemanticFormBuilder))
1259
+ #
1260
+ # Example Usage:
1261
+ #
1262
+ # <% semantic_form_for @post do |f| %>
1263
+ # <%= f.input :title %>
1264
+ # <%= f.input :body %>
1265
+ # <% end %>
1266
+ #
1267
+ # The above examples use a resource-oriented style of form_for() helper where only the @post
1268
+ # object is given as an argument, but the generic style is also supported if you really want it,
1269
+ # as is forms with inline objects (Post.new) rather than objects with instance variables (@post):
1270
+ #
1271
+ # <% semantic_form_for :post, @post, :url => posts_path do |f| %>
1272
+ # ...
1273
+ # <% end %>
1274
+ #
1275
+ # <% semantic_form_for :post, Post.new, :url => posts_path do |f| %>
1276
+ # ...
1277
+ # <% end %>
1278
+ #
1279
+ # The shorter, resource-oriented style is most definitely preferred, and has recieved the most
1280
+ # testing to date.
1281
+ #
1282
+ # Please note: Although it's possible to call Rails' built-in form_for() helper without an
1283
+ # object, all semantic forms *must* have an object (either Post.new or @post), as Formtastic
1284
+ # has too many dependencies on an ActiveRecord object being present.
1285
+ #
1286
+ module SemanticFormHelper
1287
+ @@builder = Formtastic::SemanticFormBuilder
1288
+ mattr_accessor :builder
1289
+
1290
+ [:form_for, :fields_for, :form_remote_for, :remote_form_for].each do |meth|
1291
+ src = <<-END_SRC
1292
+ def semantic_#{meth}(record_or_name_or_array, *args, &proc)
1293
+ options = args.extract_options!
1294
+ options[:builder] = @@builder
1295
+ options[:html] ||= {}
1296
+
1297
+ class_names = options[:html][:class] ? options[:html][:class].split(" ") : []
1298
+ class_names << "formtastic"
1299
+ class_names << case record_or_name_or_array
1300
+ when String, Symbol then record_or_name_or_array.to_s # :post => "post"
1301
+ when Array then record_or_name_or_array.last.class.to_s.underscore # [@post, @comment] # => "comment"
1302
+ else record_or_name_or_array.class.to_s.underscore # @post => "post"
1303
+ end
1304
+ options[:html][:class] = class_names.join(" ")
1305
+
1306
+ #{meth}(record_or_name_or_array, *(args << options), &proc)
1307
+ end
1308
+ END_SRC
1309
+ module_eval src, __FILE__, __LINE__
1310
+ end
1311
+ end
1312
+ end