~michaelforrest/use-case-mapper/trunk

« back to all changes in this revision

Viewing changes to vendor/rails/actionpack/lib/action_view/helpers/active_record_helper.rb

  • Committer: Michael Forrest
  • Date: 2010-10-15 16:28:50 UTC
  • Revision ID: michael.forrest@canonical.com-20101015162850-tj2vchanv0kr0dun
refrozeĀ gems

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
require 'cgi'
 
2
require 'action_view/helpers/form_helper'
 
3
 
 
4
module ActionView
 
5
  class Base
 
6
    @@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>".html_safe! }
 
7
    cattr_accessor :field_error_proc
 
8
  end
 
9
 
 
10
  module Helpers
 
11
    # The Active Record Helper makes it easier to create forms for records kept in instance variables. The most far-reaching is the +form+
 
12
    # method that creates a complete form for all the basic content types of the record (not associations or aggregations, though). This
 
13
    # is a great way of making the record quickly available for editing, but likely to prove lackluster for a complicated real-world form.
 
14
    # In that case, it's better to use the +input+ method and the specialized +form+ methods in link:classes/ActionView/Helpers/FormHelper.html
 
15
    module ActiveRecordHelper
 
16
      # Returns a default input tag for the type of object returned by the method. For example, if <tt>@post</tt>
 
17
      # has an attribute +title+ mapped to a +VARCHAR+ column that holds "Hello World":
 
18
      #
 
19
      #   input("post", "title")
 
20
      #   # => <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
 
21
      def input(record_name, method, options = {})
 
22
        InstanceTag.new(record_name, method, self).to_tag(options)
 
23
      end
 
24
 
 
25
      # Returns an entire form with all needed input tags for a specified Active Record object. For example, if <tt>@post</tt>
 
26
      # has attributes named +title+ of type +VARCHAR+ and +body+ of type +TEXT+ then
 
27
      #
 
28
      #   form("post")
 
29
      #
 
30
      # would yield a form like the following (modulus formatting):
 
31
      #
 
32
      #   <form action='/posts/create' method='post'>
 
33
      #     <p>
 
34
      #       <label for="post_title">Title</label><br />
 
35
      #       <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
 
36
      #     </p>
 
37
      #     <p>
 
38
      #       <label for="post_body">Body</label><br />
 
39
      #       <textarea cols="40" id="post_body" name="post[body]" rows="20"></textarea>
 
40
      #     </p>
 
41
      #     <input name="commit" type="submit" value="Create" />
 
42
      #   </form>
 
43
      #
 
44
      # It's possible to specialize the form builder by using a different action name and by supplying another
 
45
      # block renderer. For example, if <tt>@entry</tt> has an attribute +message+ of type +VARCHAR+ then
 
46
      #
 
47
      #   form("entry",
 
48
      #     :action => "sign",
 
49
      #     :input_block => Proc.new { |record, column|
 
50
      #       "#{column.human_name}: #{input(record, column.name)}<br />"
 
51
      #   })
 
52
      #
 
53
      # would yield a form like the following (modulus formatting):
 
54
      #
 
55
      #   <form action="/entries/sign" method="post">
 
56
      #     Message:
 
57
      #     <input id="entry_message" name="entry[message]" size="30" type="text" /><br />
 
58
      #     <input name="commit" type="submit" value="Sign" />
 
59
      #   </form>
 
60
      #
 
61
      # It's also possible to add additional content to the form by giving it a block, such as:
 
62
      #
 
63
      #   form("entry", :action => "sign") do |form|
 
64
      #     form << content_tag("b", "Department")
 
65
      #     form << collection_select("department", "id", @departments, "id", "name")
 
66
      #   end
 
67
      #
 
68
      # The following options are available:
 
69
      #
 
70
      # * <tt>:action</tt> - The action used when submitting the form (default: +create+ if a new record, otherwise +update+).
 
71
      # * <tt>:input_block</tt> - Specialize the output using a different block, see above.
 
72
      # * <tt>:method</tt> - The method used when submitting the form (default: +post+).
 
73
      # * <tt>:multipart</tt> - Whether to change the enctype of the form to "multipart/form-data", used when uploading a file (default: +false+).
 
74
      # * <tt>:submit_value</tt> - The text of the submit button (default: "Create" if a new record, otherwise "Update").
 
75
      def form(record_name, options = {})
 
76
        record = instance_variable_get("@#{record_name}")
 
77
 
 
78
        options = options.symbolize_keys
 
79
        options[:action] ||= record.new_record? ? "create" : "update"
 
80
        action = url_for(:action => options[:action], :id => record)
 
81
 
 
82
        submit_value = options[:submit_value] || options[:action].gsub(/[^\w]/, '').capitalize
 
83
 
 
84
        contents = form_tag({:action => action}, :method =>(options[:method] || 'post'), :enctype => options[:multipart] ? 'multipart/form-data': nil)
 
85
        contents << hidden_field(record_name, :id) unless record.new_record?
 
86
        contents << all_input_tags(record, record_name, options)
 
87
        yield contents if block_given?
 
88
        contents << submit_tag(submit_value)
 
89
        contents << '</form>'
 
90
      end
 
91
 
 
92
      # Returns a string containing the error message attached to the +method+ on the +object+ if one exists.
 
93
      # This error message is wrapped in a <tt>DIV</tt> tag, which can be extended to include a <tt>:prepend_text</tt>
 
94
      # and/or <tt>:append_text</tt> (to properly explain the error), and a <tt>:css_class</tt> to style it
 
95
      # accordingly. +object+ should either be the name of an instance variable or the actual object. The method can be
 
96
      # passed in either as a string or a symbol.
 
97
      # As an example, let's say you have a model <tt>@post</tt> that has an error message on the +title+ attribute:
 
98
      #
 
99
      #   <%= error_message_on "post", "title" %>
 
100
      #   # => <div class="formError">can't be empty</div>
 
101
      #
 
102
      #   <%= error_message_on @post, :title %>
 
103
      #   # => <div class="formError">can't be empty</div>
 
104
      #
 
105
      #   <%= error_message_on "post", "title",
 
106
      #       :prepend_text => "Title simply ",
 
107
      #       :append_text => " (or it won't work).",
 
108
      #       :css_class => "inputError" %>
 
109
      def error_message_on(object, method, *args)
 
110
        options = args.extract_options!
 
111
        unless args.empty?
 
112
          ActiveSupport::Deprecation.warn('error_message_on takes an option hash instead of separate ' +
 
113
            'prepend_text, append_text, and css_class arguments', caller)
 
114
 
 
115
          options[:prepend_text] = args[0] || ''
 
116
          options[:append_text] = args[1] || ''
 
117
          options[:css_class] = args[2] || 'formError'
 
118
        end
 
119
        options.reverse_merge!(:prepend_text => '', :append_text => '', :css_class => 'formError')
 
120
 
 
121
        if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) &&
 
122
          (errors = obj.errors.on(method))
 
123
          content_tag("div",
 
124
            "#{options[:prepend_text]}#{ERB::Util.html_escape(errors.is_a?(Array) ? errors.first : errors)}#{options[:append_text]}",
 
125
            :class => options[:css_class]
 
126
          )
 
127
        else
 
128
          ''
 
129
        end
 
130
      end
 
131
 
 
132
      # Returns a string with a <tt>DIV</tt> containing all of the error messages for the objects located as instance variables by the names
 
133
      # given.  If more than one object is specified, the errors for the objects are displayed in the order that the object names are
 
134
      # provided.
 
135
      #
 
136
      # This <tt>DIV</tt> can be tailored by the following options:
 
137
      #
 
138
      # * <tt>:header_tag</tt> - Used for the header of the error div (default: "h2").
 
139
      # * <tt>:id</tt> - The id of the error div (default: "errorExplanation").
 
140
      # * <tt>:class</tt> - The class of the error div (default: "errorExplanation").
 
141
      # * <tt>:object</tt> - The object (or array of objects) for which to display errors,
 
142
      #   if you need to escape the instance variable convention.
 
143
      # * <tt>:object_name</tt> - The object name to use in the header, or any text that you prefer.
 
144
      #   If <tt>:object_name</tt> is not set, the name of the first object will be used.
 
145
      # * <tt>:header_message</tt> - The message in the header of the error div.  Pass +nil+
 
146
      #   or an empty string to avoid the header message altogether. (Default: "X errors
 
147
      #   prohibited this object from being saved").
 
148
      # * <tt>:message</tt> - The explanation message after the header message and before
 
149
      #   the error list.  Pass +nil+ or an empty string to avoid the explanation message
 
150
      #   altogether. (Default: "There were problems with the following fields:").
 
151
      #
 
152
      # To specify the display for one object, you simply provide its name as a parameter.
 
153
      # For example, for the <tt>@user</tt> model:
 
154
      #
 
155
      #   error_messages_for 'user'
 
156
      #
 
157
      # To specify more than one object, you simply list them; optionally, you can add an extra <tt>:object_name</tt> parameter, which
 
158
      # will be the name used in the header message:
 
159
      #
 
160
      #   error_messages_for 'user_common', 'user', :object_name => 'user'
 
161
      #
 
162
      # If the objects cannot be located as instance variables, you can add an extra <tt>:object</tt> parameter which gives the actual
 
163
      # object (or array of objects to use):
 
164
      #
 
165
      #   error_messages_for 'user', :object => @question.user
 
166
      #
 
167
      # NOTE: This is a pre-packaged presentation of the errors with embedded strings and a certain HTML structure. If what
 
168
      # you need is significantly different from the default presentation, it makes plenty of sense to access the <tt>object.errors</tt>
 
169
      # instance yourself and set it up. View the source of this method to see how easy it is.
 
170
      def error_messages_for(*params)
 
171
        options = params.extract_options!.symbolize_keys
 
172
 
 
173
        if object = options.delete(:object)
 
174
          objects = Array.wrap(object)
 
175
        else
 
176
          objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
 
177
        end
 
178
 
 
179
        count  = objects.inject(0) {|sum, object| sum + object.errors.count }
 
180
        unless count.zero?
 
181
          html = {}
 
182
          [:id, :class].each do |key|
 
183
            if options.include?(key)
 
184
              value = options[key]
 
185
              html[key] = value unless value.blank?
 
186
            else
 
187
              html[key] = 'errorExplanation'
 
188
            end
 
189
          end
 
190
          options[:object_name] ||= params.first
 
191
 
 
192
          I18n.with_options :locale => options[:locale], :scope => [:activerecord, :errors, :template] do |locale|
 
193
            header_message = if options.include?(:header_message)
 
194
              options[:header_message]
 
195
            else
 
196
              object_name = options[:object_name].to_s.gsub('_', ' ')
 
197
              object_name = I18n.t(object_name, :default => object_name, :scope => [:activerecord, :models], :count => 1)
 
198
              locale.t :header, :count => count, :model => object_name
 
199
            end
 
200
            message = options.include?(:message) ? options[:message] : locale.t(:body)
 
201
            error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, ERB::Util.html_escape(msg)) } }.join
 
202
 
 
203
            contents = ''
 
204
            contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank?
 
205
            contents << content_tag(:p, message) unless message.blank?
 
206
            contents << content_tag(:ul, error_messages)
 
207
 
 
208
            content_tag(:div, contents, html)
 
209
          end
 
210
        else
 
211
          ''
 
212
        end
 
213
      end
 
214
 
 
215
      private
 
216
        def all_input_tags(record, record_name, options)
 
217
          input_block = options[:input_block] || default_input_block
 
218
          record.class.content_columns.collect{ |column| input_block.call(record_name, column) }.join("\n")
 
219
        end
 
220
 
 
221
        def default_input_block
 
222
          Proc.new { |record, column| %(<p><label for="#{record}_#{column.name}">#{column.human_name}</label><br />#{input(record, column.name)}</p>) }
 
223
        end
 
224
    end
 
225
 
 
226
    class InstanceTag #:nodoc:
 
227
      def to_tag(options = {})
 
228
        case column_type
 
229
          when :string
 
230
            field_type = @method_name.include?("password") ? "password" : "text"
 
231
            to_input_field_tag(field_type, options)
 
232
          when :text
 
233
            to_text_area_tag(options)
 
234
          when :integer, :float, :decimal
 
235
            to_input_field_tag("text", options)
 
236
          when :date
 
237
            to_date_select_tag(options)
 
238
          when :datetime, :timestamp
 
239
            to_datetime_select_tag(options)
 
240
          when :time
 
241
            to_time_select_tag(options)
 
242
          when :boolean
 
243
            to_boolean_select_tag(options)
 
244
        end
 
245
      end
 
246
 
 
247
      alias_method :tag_without_error_wrapping, :tag
 
248
      def tag(name, options)
 
249
        if object.respond_to?(:errors) && object.errors.respond_to?(:on)
 
250
          error_wrapping(tag_without_error_wrapping(name, options), object.errors.on(@method_name))
 
251
        else
 
252
          tag_without_error_wrapping(name, options)
 
253
        end
 
254
      end
 
255
 
 
256
      alias_method :content_tag_without_error_wrapping, :content_tag
 
257
      def content_tag(name, value, options)
 
258
        if object.respond_to?(:errors) && object.errors.respond_to?(:on)
 
259
          error_wrapping(content_tag_without_error_wrapping(name, value, options), object.errors.on(@method_name))
 
260
        else
 
261
          content_tag_without_error_wrapping(name, value, options)
 
262
        end
 
263
      end
 
264
 
 
265
      alias_method :to_date_select_tag_without_error_wrapping, :to_date_select_tag
 
266
      def to_date_select_tag(options = {}, html_options = {})
 
267
        if object.respond_to?(:errors) && object.errors.respond_to?(:on)
 
268
          error_wrapping(to_date_select_tag_without_error_wrapping(options, html_options), object.errors.on(@method_name))
 
269
        else
 
270
          to_date_select_tag_without_error_wrapping(options, html_options)
 
271
        end
 
272
      end
 
273
 
 
274
      alias_method :to_datetime_select_tag_without_error_wrapping, :to_datetime_select_tag
 
275
      def to_datetime_select_tag(options = {}, html_options = {})
 
276
        if object.respond_to?(:errors) && object.errors.respond_to?(:on)
 
277
            error_wrapping(to_datetime_select_tag_without_error_wrapping(options, html_options), object.errors.on(@method_name))
 
278
          else
 
279
            to_datetime_select_tag_without_error_wrapping(options, html_options)
 
280
        end
 
281
      end
 
282
 
 
283
      alias_method :to_time_select_tag_without_error_wrapping, :to_time_select_tag
 
284
      def to_time_select_tag(options = {}, html_options = {})
 
285
        if object.respond_to?(:errors) && object.errors.respond_to?(:on)
 
286
          error_wrapping(to_time_select_tag_without_error_wrapping(options, html_options), object.errors.on(@method_name))
 
287
        else
 
288
          to_time_select_tag_without_error_wrapping(options, html_options)
 
289
        end
 
290
      end
 
291
 
 
292
      def error_wrapping(html_tag, has_error)
 
293
        has_error ? Base.field_error_proc.call(html_tag, self).html_safe! : html_tag
 
294
      end
 
295
 
 
296
      def error_message
 
297
        object.errors.on(@method_name)
 
298
      end
 
299
 
 
300
      def column_type
 
301
        object.send(:column_for_attribute, @method_name).type
 
302
      end
 
303
    end
 
304
  end
 
305
end