benaldred-clearance 0.8.8

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