Skip to content
This repository
Browse code

Merge pull request #4488 from rafaelfranca/av-refactor

ActionView::Helpers::FormHelper refactoring
  • Loading branch information...
commit 57aaaa61977e82b9de2c43c26b00e636030685c4 2 parents ba154bd + 6f1bf52
Xavier Noria authored January 17, 2012

Showing 29 changed files with 688 additions and 436 deletions. Show diff stats Hide diff stats

  1. 4  actionpack/lib/action_view/helpers/active_model_helper.rb
  2. 66  actionpack/lib/action_view/helpers/date_helper.rb
  3. 317  actionpack/lib/action_view/helpers/form_helper.rb
  4. 72  actionpack/lib/action_view/helpers/form_options_helper.rb
  5. 28  actionpack/lib/action_view/helpers/tags.rb
  6. 134  actionpack/lib/action_view/helpers/tags/base.rb
  7. 54  actionpack/lib/action_view/helpers/tags/check_box.rb
  8. 16  actionpack/lib/action_view/helpers/tags/checkable.rb
  9. 23  actionpack/lib/action_view/helpers/tags/collection_select.rb
  10. 64  actionpack/lib/action_view/helpers/tags/date_select.rb
  11. 8  actionpack/lib/action_view/helpers/tags/datetime_select.rb
  12. 8  actionpack/lib/action_view/helpers/tags/email_field.rb
  13. 12  actionpack/lib/action_view/helpers/tags/file_field.rb
  14. 24  actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb
  15. 12  actionpack/lib/action_view/helpers/tags/hidden_field.rb
  16. 65  actionpack/lib/action_view/helpers/tags/label.rb
  17. 19  actionpack/lib/action_view/helpers/tags/number_field.rb
  18. 12  actionpack/lib/action_view/helpers/tags/password_field.rb
  19. 31  actionpack/lib/action_view/helpers/tags/radio_button.rb
  20. 8  actionpack/lib/action_view/helpers/tags/range_field.rb
  21. 24  actionpack/lib/action_view/helpers/tags/search_field.rb
  22. 31  actionpack/lib/action_view/helpers/tags/select.rb
  23. 8  actionpack/lib/action_view/helpers/tags/tel_field.rb
  24. 20  actionpack/lib/action_view/helpers/tags/text_area.rb
  25. 24  actionpack/lib/action_view/helpers/tags/text_field.rb
  26. 8  actionpack/lib/action_view/helpers/tags/time_select.rb
  27. 20  actionpack/lib/action_view/helpers/tags/time_zone_select.rb
  28. 8  actionpack/lib/action_view/helpers/tags/url_field.rb
  29. 4  actionpack/test/template/form_helper_test.rb
4  actionpack/lib/action_view/helpers/active_model_helper.rb
@@ -16,9 +16,7 @@ def object
16 16
         end
17 17
       end
18 18
 
19  
-      %w(content_tag to_date_select_tag to_datetime_select_tag to_time_select_tag).each do |meth|
20  
-        module_eval "def #{meth}(*) error_wrapping(super) end", __FILE__, __LINE__
21  
-      end
  19
+      module_eval "def content_tag(*) error_wrapping(super) end", __FILE__, __LINE__
22 20
 
23 21
       def tag(type, options, *)
24 22
         tag_generate_errors?(options) ? error_wrapping(super) : super
66  actionpack/lib/action_view/helpers/date_helper.rb
@@ -213,7 +213,7 @@ def time_ago_in_words(from_time, include_seconds = false)
213 213
       # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
214 214
       # all month choices are valid.
215 215
       def date_select(object_name, method, options = {}, html_options = {})
216  
-        InstanceTag.new(object_name, method, self, options.delete(:object)).to_date_select_tag(options, html_options)
  216
+        Tags::DateSelect.new(object_name, method, self, options, html_options).render
217 217
       end
218 218
 
219 219
       # Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a
@@ -251,7 +251,7 @@ def date_select(object_name, method, options = {}, html_options = {})
251 251
       # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
252 252
       # all month choices are valid.
253 253
       def time_select(object_name, method, options = {}, html_options = {})
254  
-        InstanceTag.new(object_name, method, self, options.delete(:object)).to_time_select_tag(options, html_options)
  254
+        Tags::TimeSelect.new(object_name, method, self, options, html_options).render
255 255
       end
256 256
 
257 257
       # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a
@@ -287,7 +287,7 @@ def time_select(object_name, method, options = {}, html_options = {})
287 287
       #
288 288
       # The selects are prepared for multi-parameter assignment to an Active Record object.
289 289
       def datetime_select(object_name, method, options = {}, html_options = {})
290  
-        InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options)
  290
+        Tags::DatetimeSelect.new(object_name, method, self, options, html_options).render
291 291
       end
292 292
 
293 293
       # Returns a set of html select-tags (one for year, month, day, hour, minute, and second) pre-selected with the
@@ -974,66 +974,6 @@ def separator(type)
974 974
         end
975 975
     end
976 976
 
977  
-    module DateHelperInstanceTag
978  
-      def to_date_select_tag(options = {}, html_options = {})
979  
-        datetime_selector(options, html_options).select_date.html_safe
980  
-      end
981  
-
982  
-      def to_time_select_tag(options = {}, html_options = {})
983  
-        datetime_selector(options, html_options).select_time.html_safe
984  
-      end
985  
-
986  
-      def to_datetime_select_tag(options = {}, html_options = {})
987  
-        datetime_selector(options, html_options).select_datetime.html_safe
988  
-      end
989  
-
990  
-      private
991  
-        def datetime_selector(options, html_options)
992  
-          datetime = value(object) || default_datetime(options)
993  
-          @auto_index ||= nil
994  
-
995  
-          options = options.dup
996  
-          options[:field_name]           = @method_name
997  
-          options[:include_position]     = true
998  
-          options[:prefix]             ||= @object_name
999  
-          options[:index]                = @auto_index if @auto_index && !options.has_key?(:index)
1000  
-
1001  
-          DateTimeSelector.new(datetime, options, html_options)
1002  
-        end
1003  
-
1004  
-        def default_datetime(options)
1005  
-          return if options[:include_blank] || options[:prompt]
1006  
-
1007  
-          case options[:default]
1008  
-            when nil
1009  
-              Time.current
1010  
-            when Date, Time
1011  
-              options[:default]
1012  
-            else
1013  
-              default = options[:default].dup
1014  
-
1015  
-              # Rename :minute and :second to :min and :sec
1016  
-              default[:min] ||= default[:minute]
1017  
-              default[:sec] ||= default[:second]
1018  
-
1019  
-              time = Time.current
1020  
-
1021  
-              [:year, :month, :day, :hour, :min, :sec].each do |key|
1022  
-                default[key] ||= time.send(key)
1023  
-              end
1024  
-
1025  
-              Time.utc_time(
1026  
-                default[:year], default[:month], default[:day],
1027  
-                default[:hour], default[:min], default[:sec]
1028  
-              )
1029  
-          end
1030  
-        end
1031  
-    end
1032  
-
1033  
-    class InstanceTag #:nodoc:
1034  
-      include DateHelperInstanceTag
1035  
-    end
1036  
-
1037 977
     class FormBuilder
1038 978
       def date_select(method, options = {}, html_options = {})
1039 979
         @template.date_select(@object_name, method, objectify_options(options), html_options)
317  actionpack/lib/action_view/helpers/form_helper.rb
@@ -3,6 +3,7 @@
3 3
 require 'action_view/helpers/tag_helper'
4 4
 require 'action_view/helpers/form_tag_helper'
5 5
 require 'action_view/helpers/active_model_helper'
  6
+require 'action_view/helpers/tags'
6 7
 require 'active_support/core_ext/class/attribute'
7 8
 require 'active_support/core_ext/hash/slice'
8 9
 require 'active_support/core_ext/object/blank'
@@ -654,16 +655,7 @@ def fields_for(record_name, record_object = nil, options = {}, &block)
654 655
       #     'Accept <a href="/terms">Terms</a>.'.html_safe
655 656
       #   end
656 657
       def label(object_name, method, content_or_options = nil, options = nil, &block)
657  
-        content_is_options = content_or_options.is_a?(Hash)
658  
-        if content_is_options || block_given?
659  
-          options = content_or_options if content_is_options
660  
-          text = nil
661  
-        else
662  
-          text = content_or_options
663  
-        end
664  
-
665  
-        options ||= {}
666  
-        InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options, &block)
  658
+        Tags::Label.new(object_name, method, self, content_or_options, options).render(&block)
667 659
       end
668 660
 
669 661
       # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -685,7 +677,7 @@ def label(object_name, method, content_or_options = nil, options = nil, &block)
685 677
       #   # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
686 678
       #
687 679
       def text_field(object_name, method, options = {})
688  
-        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
  680
+        Tags::TextField.new(object_name, method, self, options).render
689 681
       end
690 682
 
691 683
       # Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -707,7 +699,7 @@ def text_field(object_name, method, options = {})
707 699
       #   # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" />
708 700
       #
709 701
       def password_field(object_name, method, options = {})
710  
-        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", { :value => nil }.merge!(options))
  702
+        Tags::PasswordField.new(object_name, method, self, options).render
711 703
       end
712 704
 
713 705
       # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -725,7 +717,7 @@ def password_field(object_name, method, options = {})
725 717
       #   hidden_field(:user, :token)
726 718
       #   # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
727 719
       def hidden_field(object_name, method, options = {})
728  
-        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
  720
+        Tags::HiddenField.new(object_name, method, self, options).render
729 721
       end
730 722
 
731 723
       # Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -746,7 +738,7 @@ def hidden_field(object_name, method, options = {})
746 738
       #   # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
747 739
       #
748 740
       def file_field(object_name, method, options = {})
749  
-        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options.update({:size => nil}))
  741
+        Tags::FileField.new(object_name, method, self, options).render
750 742
       end
751 743
 
752 744
       # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
@@ -774,7 +766,7 @@ def file_field(object_name, method, options = {})
774 766
       #   #      #{@entry.body}
775 767
       #   #    </textarea>
776 768
       def text_area(object_name, method, options = {})
777  
-        InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
  769
+        Tags::TextArea.new(object_name, method, self, options).render
778 770
       end
779 771
 
780 772
       # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -836,7 +828,7 @@ def text_area(object_name, method, options = {})
836 828
       #   #    <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
837 829
       #
838 830
       def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
839  
-        InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
  831
+        Tags::CheckBox.new(object_name, method, self, checked_value, unchecked_value, options).render
840 832
       end
841 833
 
842 834
       # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
@@ -858,7 +850,7 @@ def check_box(object_name, method, options = {}, checked_value = "1", unchecked_
858 850
       #   # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
859 851
       #   #    <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
860 852
       def radio_button(object_name, method, tag_value, options = {})
861  
-        InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
  853
+        Tags::RadioButton.new(object_name, method, self, tag_value, options).render
862 854
       end
863 855
 
864 856
       # Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
@@ -884,20 +876,7 @@ def radio_button(object_name, method, tag_value, options = {})
884 876
       #   # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" size="30" type="search" />
885 877
       #
886 878
       def search_field(object_name, method, options = {})
887  
-        options = options.stringify_keys
888  
-
889  
-        if options["autosave"]
890  
-          if options["autosave"] == true
891  
-            options["autosave"] = request.host.split(".").reverse.join(".")
892  
-          end
893  
-          options["results"] ||= 10
894  
-        end
895  
-
896  
-        if options["onsearch"]
897  
-          options["incremental"] = true unless options.has_key?("incremental")
898  
-        end
899  
-
900  
-        InstanceTag.new(object_name, method, self, options.delete("object")).to_input_field_tag("search", options)
  879
+        Tags::SearchField.new(object_name, method, self, options).render
901 880
       end
902 881
 
903 882
       # Returns a text_field of type "tel".
@@ -906,7 +885,7 @@ def search_field(object_name, method, options = {})
906 885
       #   # => <input id="user_phone" name="user[phone]" size="30" type="tel" />
907 886
       #
908 887
       def telephone_field(object_name, method, options = {})
909  
-        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("tel", options)
  888
+        Tags::TelField.new(object_name, method, self, options).render
910 889
       end
911 890
       alias phone_field telephone_field
912 891
 
@@ -916,7 +895,7 @@ def telephone_field(object_name, method, options = {})
916 895
       #   # => <input id="user_homepage" size="30" name="user[homepage]" type="url" />
917 896
       #
918 897
       def url_field(object_name, method, options = {})
919  
-        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("url", options)
  898
+        Tags::UrlField.new(object_name, method, self, options).render
920 899
       end
921 900
 
922 901
       # Returns a text_field of type "email".
@@ -925,7 +904,7 @@ def url_field(object_name, method, options = {})
925 904
       #   # => <input id="user_address" size="30" name="user[address]" type="email" />
926 905
       #
927 906
       def email_field(object_name, method, options = {})
928  
-        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("email", options)
  907
+        Tags::EmailField.new(object_name, method, self, options).render
929 908
       end
930 909
 
931 910
       # Returns an input tag of type "number".
@@ -933,7 +912,7 @@ def email_field(object_name, method, options = {})
933 912
       # ==== Options
934 913
       # * Accepts same options as number_field_tag
935 914
       def number_field(object_name, method, options = {})
936  
-        InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("number", options)
  915
+        Tags::NumberField.new(object_name, method, self, options).render
937 916
       end
938 917
 
939 918
       # Returns an input tag of type "range".
@@ -941,7 +920,7 @@ def number_field(object_name, method, options = {})
941 920
       # ==== Options
942 921
       # * Accepts same options as range_field_tag
943 922
       def range_field(object_name, method, options = {})
944  
-        InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("range", options)
  923
+        Tags::RangeField.new(object_name, method, self, options).render
945 924
       end
946 925
 
947 926
       private
@@ -961,272 +940,6 @@ def instantiate_builder(record_name, record_object, options, &block)
961 940
         end
962 941
     end
963 942
 
964  
-    class InstanceTag
965  
-      include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
966  
-
967  
-      attr_reader :object, :method_name, :object_name
968  
-
969  
-      DEFAULT_FIELD_OPTIONS     = { "size" => 30 }
970  
-      DEFAULT_RADIO_OPTIONS     = { }
971  
-      DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }
972  
-
973  
-      def initialize(object_name, method_name, template_object, object = nil)
974  
-        @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
975  
-        @template_object = template_object
976  
-
977  
-        @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
978  
-        @object = retrieve_object(object)
979  
-        @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
980  
-      end
981  
-
982  
-      def to_label_tag(text = nil, options = {}, &block)
983  
-        options = options.stringify_keys
984  
-        tag_value = options.delete("value")
985  
-        name_and_id = options.dup
986  
-
987  
-        if name_and_id["for"]
988  
-          name_and_id["id"] = name_and_id["for"]
989  
-        else
990  
-          name_and_id.delete("id")
991  
-        end
992  
-
993  
-        add_default_name_and_id_for_value(tag_value, name_and_id)
994  
-        options.delete("index")
995  
-        options.delete("namespace")
996  
-        options["for"] ||= name_and_id["id"]
997  
-
998  
-        if block_given?
999  
-          @template_object.label_tag(name_and_id["id"], options, &block)
1000  
-        else
1001  
-          content = if text.blank?
1002  
-            object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1')
1003  
-            method_and_value = tag_value.present? ? "#{method_name}.#{tag_value}" : method_name
1004  
-
1005  
-            if object.respond_to?(:to_model)
1006  
-              key = object.class.model_name.i18n_key
1007  
-              i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
1008  
-            end
1009  
-
1010  
-            i18n_default ||= ""
1011  
-            I18n.t("#{object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
1012  
-          else
1013  
-            text.to_s
1014  
-          end
1015  
-
1016  
-          content ||= if object && object.class.respond_to?(:human_attribute_name)
1017  
-            object.class.human_attribute_name(method_name)
1018  
-          end
1019  
-
1020  
-          content ||= method_name.humanize
1021  
-
1022  
-          label_tag(name_and_id["id"], content, options)
1023  
-        end
1024  
-      end
1025  
-
1026  
-      def to_input_field_tag(field_type, options = {})
1027  
-        options = options.stringify_keys
1028  
-        options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
1029  
-        options = DEFAULT_FIELD_OPTIONS.merge(options)
1030  
-        if field_type == "hidden"
1031  
-          options.delete("size")
1032  
-        end
1033  
-        options["type"]  ||= field_type
1034  
-        options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file"
1035  
-        options["value"] &&= ERB::Util.html_escape(options["value"])
1036  
-        add_default_name_and_id(options)
1037  
-        tag("input", options)
1038  
-      end
1039  
-
1040  
-      def to_number_field_tag(field_type, options = {})
1041  
-        options = options.stringify_keys
1042  
-        options['size'] ||= nil
1043  
-
1044  
-        if range = options.delete("in") || options.delete("within")
1045  
-          options.update("min" => range.min, "max" => range.max)
1046  
-        end
1047  
-        to_input_field_tag(field_type, options)
1048  
-      end
1049  
-
1050  
-      def to_radio_button_tag(tag_value, options = {})
1051  
-        options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
1052  
-        options["type"]     = "radio"
1053  
-        options["value"]    = tag_value
1054  
-        if options.has_key?("checked")
1055  
-          cv = options.delete "checked"
1056  
-          checked = cv == true || cv == "checked"
1057  
-        else
1058  
-          checked = self.class.radio_button_checked?(value(object), tag_value)
1059  
-        end
1060  
-        options["checked"]  = "checked" if checked
1061  
-        add_default_name_and_id_for_value(tag_value, options)
1062  
-        tag("input", options)
1063  
-      end
1064  
-
1065  
-      def to_text_area_tag(options = {})
1066  
-        options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
1067  
-        add_default_name_and_id(options)
1068  
-
1069  
-        if size = options.delete("size")
1070  
-          options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
1071  
-        end
1072  
-
1073  
-        content_tag("textarea", ERB::Util.html_escape(options.delete('value') || value_before_type_cast(object)), options)
1074  
-      end
1075  
-
1076  
-      def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
1077  
-        options = options.stringify_keys
1078  
-        options["type"]     = "checkbox"
1079  
-        options["value"]    = checked_value
1080  
-        if options.has_key?("checked")
1081  
-          cv = options.delete "checked"
1082  
-          checked = cv == true || cv == "checked"
1083  
-        else
1084  
-          checked = self.class.check_box_checked?(value(object), checked_value)
1085  
-        end
1086  
-        options["checked"] = "checked" if checked
1087  
-        if options["multiple"]
1088  
-          add_default_name_and_id_for_value(checked_value, options)
1089  
-          options.delete("multiple")
1090  
-        else
1091  
-          add_default_name_and_id(options)
1092  
-        end
1093  
-        hidden = unchecked_value ? tag("input", "name" => options["name"], "type" => "hidden", "value" => unchecked_value, "disabled" => options["disabled"]) : ""
1094  
-        checkbox = tag("input", options)
1095  
-        hidden + checkbox
1096  
-      end
1097  
-
1098  
-      def to_boolean_select_tag(options = {})
1099  
-        options = options.stringify_keys
1100  
-        add_default_name_and_id(options)
1101  
-        value = value(object)
1102  
-        tag_text = "<select"
1103  
-        tag_text << tag_options(options)
1104  
-        tag_text << "><option value=\"false\""
1105  
-        tag_text << " selected" if value == false
1106  
-        tag_text << ">False</option><option value=\"true\""
1107  
-        tag_text << " selected" if value
1108  
-        tag_text << ">True</option></select>"
1109  
-      end
1110  
-
1111  
-      def to_content_tag(tag_name, options = {})
1112  
-        content_tag(tag_name, value(object), options)
1113  
-      end
1114  
-
1115  
-      def retrieve_object(object)
1116  
-        if object
1117  
-          object
1118  
-        elsif @template_object.instance_variable_defined?("@#{@object_name}")
1119  
-          @template_object.instance_variable_get("@#{@object_name}")
1120  
-        end
1121  
-      rescue NameError
1122  
-        # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
1123  
-        nil
1124  
-      end
1125  
-
1126  
-      def retrieve_autoindex(pre_match)
1127  
-        object = self.object || @template_object.instance_variable_get("@#{pre_match}")
1128  
-        if object && object.respond_to?(:to_param)
1129  
-          object.to_param
1130  
-        else
1131  
-          raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
1132  
-        end
1133  
-      end
1134  
-
1135  
-      def value(object)
1136  
-        self.class.value(object, @method_name)
1137  
-      end
1138  
-
1139  
-      def value_before_type_cast(object)
1140  
-        self.class.value_before_type_cast(object, @method_name)
1141  
-      end
1142  
-
1143  
-      class << self
1144  
-        def value(object, method_name)
1145  
-          object.send method_name if object
1146  
-        end
1147  
-
1148  
-        def value_before_type_cast(object, method_name)
1149  
-          unless object.nil?
1150  
-            object.respond_to?(method_name + "_before_type_cast") ?
1151  
-            object.send(method_name + "_before_type_cast") :
1152  
-            object.send(method_name)
1153  
-          end
1154  
-        end
1155  
-
1156  
-        def check_box_checked?(value, checked_value)
1157  
-          case value
1158  
-          when TrueClass, FalseClass
1159  
-            value
1160  
-          when NilClass
1161  
-            false
1162  
-          when Integer
1163  
-            value != 0
1164  
-          when String
1165  
-            value == checked_value
1166  
-          when Array
1167  
-            value.include?(checked_value)
1168  
-          else
1169  
-            value.to_i != 0
1170  
-          end
1171  
-        end
1172  
-
1173  
-        def radio_button_checked?(value, checked_value)
1174  
-          value.to_s == checked_value.to_s
1175  
-        end
1176  
-      end
1177  
-
1178  
-      private
1179  
-        def add_default_name_and_id_for_value(tag_value, options)
1180  
-          unless tag_value.nil?
1181  
-            pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
1182  
-            specified_id = options["id"]
1183  
-            add_default_name_and_id(options)
1184  
-            options["id"] += "_#{pretty_tag_value}" if specified_id.blank? && options["id"].present?
1185  
-          else
1186  
-            add_default_name_and_id(options)
1187  
-          end
1188  
-        end
1189  
-
1190  
-        def add_default_name_and_id(options)
1191  
-          if options.has_key?("index")
1192  
-            options["name"] ||= tag_name_with_index(options["index"])
1193  
-            options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
1194  
-            options.delete("index")
1195  
-          elsif defined?(@auto_index)
1196  
-            options["name"] ||= tag_name_with_index(@auto_index)
1197  
-            options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
1198  
-          else
1199  
-            options["name"] ||= tag_name + (options['multiple'] ? '[]' : '')
1200  
-            options["id"] = options.fetch("id"){ tag_id }
1201  
-          end
1202  
-          options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
1203  
-        end
1204  
-
1205  
-        def tag_name
1206  
-          "#{@object_name}[#{sanitized_method_name}]"
1207  
-        end
1208  
-
1209  
-        def tag_name_with_index(index)
1210  
-          "#{@object_name}[#{index}][#{sanitized_method_name}]"
1211  
-        end
1212  
-
1213  
-        def tag_id
1214  
-          "#{sanitized_object_name}_#{sanitized_method_name}"
1215  
-        end
1216  
-
1217  
-        def tag_id_with_index(index)
1218  
-          "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
1219  
-        end
1220  
-
1221  
-        def sanitized_object_name
1222  
-          @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
1223  
-        end
1224  
-
1225  
-        def sanitized_method_name
1226  
-          @sanitized_method_name ||= @method_name.sub(/\?$/,"")
1227  
-        end
1228  
-    end
1229  
-
1230 943
     class FormBuilder
1231 944
       # The methods which wrap a form helper call.
1232 945
       class_attribute :field_helpers
72  actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -154,7 +154,7 @@ module FormOptionsHelper
154 154
       # key in the query string, that works for ordinary forms.
155 155
       #
156 156
       def select(object, method, choices, options = {}, html_options = {})
157  
-        InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options)
  157
+        Tags::Select.new(object, method, self, choices, options, html_options).render
158 158
       end
159 159
 
160 160
       # Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
@@ -188,10 +188,9 @@ def select(object, method, choices, options = {}, html_options = {})
188 188
       #     <option value="3">M. Clark</option>
189 189
       #   </select>
190 190
       def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
191  
-        InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
  191
+        Tags::CollectionSelect.new(object, method, self, collection, value_method, text_method, options, html_options).render
192 192
       end
193 193
 
194  
-
195 194
       # Returns <tt><select></tt>, <tt><optgroup></tt> and <tt><option></tt> tags for the collection of existing return values of
196 195
       # +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
197 196
       # be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt>
@@ -240,7 +239,7 @@ def collection_select(object, method, collection, value_method, text_method, opt
240 239
       #   </select>
241 240
       #
242 241
       def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
243  
-        InstanceTag.new(object, method, self, options.delete(:object)).to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
  242
+        Tags::GroupedCollectionSelect.new(object, method, self, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options).render
244 243
       end
245 244
 
246 245
       # Return select and option tags for the given object and method, using
@@ -274,7 +273,7 @@ def grouped_collection_select(object, method, collection, group_method, group_la
274 273
       #
275 274
       #   time_zone_select( "user", "time_zone", ActiveSupport::TimeZone.all.sort, :model => ActiveSupport::TimeZone)
276 275
       def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
277  
-        InstanceTag.new(object, method, self,  options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
  276
+        Tags::TimeZoneSelect.new(object, method, self, priority_zones, options, html_options).render
278 277
       end
279 278
 
280 279
       # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
@@ -573,69 +572,6 @@ def extract_values_from_collection(collection, value_method, selected)
573 572
         end
574 573
     end
575 574
 
576  
-    class InstanceTag #:nodoc:
577  
-      include FormOptionsHelper
578  
-
579  
-      def to_select_tag(choices, options, html_options)
580  
-        selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
581  
-
582  
-        # Grouped choices look like this:
583  
-        #
584  
-        #   [nil, []]
585  
-        #   { nil => [] }
586  
-        #
587  
-        if !choices.empty? && choices.first.respond_to?(:last) && Array === choices.first.last
588  
-          option_tags = grouped_options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
589  
-        else
590  
-          option_tags = options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
591  
-        end
592  
-
593  
-        select_content_tag(option_tags, options, html_options)
594  
-      end
595  
-
596  
-      def to_collection_select_tag(collection, value_method, text_method, options, html_options)
597  
-        selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
598  
-        select_content_tag(
599  
-          options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => options[:disabled]), options, html_options
600  
-        )
601  
-      end
602  
-
603  
-      def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
604  
-        select_content_tag(
605  
-          option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value(object)), options, html_options
606  
-        )
607  
-      end
608  
-
609  
-      def to_time_zone_select_tag(priority_zones, options, html_options)
610  
-        select_content_tag(
611  
-            time_zone_options_for_select(value(object) || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone), options, html_options
612  
-        )
613  
-      end
614  
-
615  
-      private
616  
-        def add_options(option_tags, options, value = nil)
617  
-          if options[:include_blank]
618  
-            option_tags = "<option value=\"\">#{ERB::Util.html_escape(options[:include_blank]) if options[:include_blank].kind_of?(String)}</option>\n" + option_tags
619  
-          end
620  
-          if value.blank? && options[:prompt]
621  
-            prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select')
622  
-            option_tags = "<option value=\"\">#{ERB::Util.html_escape(prompt)}</option>\n" + option_tags
623  
-          end
624  
-          option_tags.html_safe
625  
-        end
626  
-
627  
-        def select_content_tag(option_tags, options, html_options)
628  
-          html_options = html_options.stringify_keys
629  
-          add_default_name_and_id(html_options)
630  
-          select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
631  
-          if html_options["multiple"]
632  
-            tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
633  
-          else
634  
-            select
635  
-          end
636  
-        end
637  
-    end
638  
-
639 575
     class FormBuilder
640 576
       def select(method, choices, options = {}, html_options = {})
641 577
         @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options))
28  actionpack/lib/action_view/helpers/tags.rb
... ...
@@ -0,0 +1,28 @@
  1
+module ActionView
  2
+  module Helpers
  3
+    module Tags
  4
+      autoload :Base,                    'action_view/helpers/tags/base'
  5
+      autoload :Label,                   'action_view/helpers/tags/label'
  6
+      autoload :TextField,               'action_view/helpers/tags/text_field'
  7
+      autoload :PasswordField,           'action_view/helpers/tags/password_field'
  8
+      autoload :HiddenField,             'action_view/helpers/tags/hidden_field'
  9
+      autoload :FileField,               'action_view/helpers/tags/file_field'
  10
+      autoload :SearchField,             'action_view/helpers/tags/search_field'
  11
+      autoload :TelField,                'action_view/helpers/tags/tel_field'
  12
+      autoload :UrlField,                'action_view/helpers/tags/url_field'
  13
+      autoload :EmailField,              'action_view/helpers/tags/email_field'
  14
+      autoload :NumberField,             'action_view/helpers/tags/number_field'
  15
+      autoload :RangeField,              'action_view/helpers/tags/range_field'
  16
+      autoload :TextArea,                'action_view/helpers/tags/text_area'
  17
+      autoload :CheckBox,                'action_view/helpers/tags/check_box'
  18
+      autoload :RadioButton,             'action_view/helpers/tags/radio_button'
  19
+      autoload :Select,                  'action_view/helpers/tags/select'
  20
+      autoload :CollectionSelect,        'action_view/helpers/tags/collection_select'
  21
+      autoload :GroupedCollectionSelect, 'action_view/helpers/tags/grouped_collection_select'
  22
+      autoload :TimeZoneSelect,          'action_view/helpers/tags/time_zone_select'
  23
+      autoload :DateSelect,              'action_view/helpers/tags/date_select'
  24
+      autoload :TimeSelect,              'action_view/helpers/tags/time_select'
  25
+      autoload :DatetimeSelect,          'action_view/helpers/tags/datetime_select'
  26
+    end
  27
+  end
  28
+end
134  actionpack/lib/action_view/helpers/tags/base.rb
... ...
@@ -0,0 +1,134 @@
  1
+module ActionView
  2
+  module Helpers
  3
+    module Tags
  4
+      class Base #:nodoc:
  5
+        include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
  6
+        include FormOptionsHelper
  7
+
  8
+        DEFAULT_FIELD_OPTIONS = { "size" => 30 }
  9
+
  10
+        attr_reader :object
  11
+
  12
+        def initialize(object_name, method_name, template_object, options = {})
  13
+          @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
  14
+          @template_object = template_object
  15
+
  16
+          @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
  17
+          @object = retrieve_object(options.delete(:object))
  18
+          @options = options
  19
+          @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
  20
+        end
  21
+
  22
+        def render(&block)
  23
+          raise "Abstract Method called"
  24
+        end
  25
+
  26
+        private
  27
+
  28
+        def value(object)
  29
+          object.send @method_name if object
  30
+        end
  31
+
  32
+        def value_before_type_cast(object)
  33
+          unless object.nil?
  34
+            object.respond_to?(@method_name + "_before_type_cast") ?
  35
+            object.send(@method_name + "_before_type_cast") :
  36
+            object.send(@method_name)
  37
+          end
  38
+        end
  39
+
  40
+        def retrieve_object(object)
  41
+          if object
  42
+            object
  43
+          elsif @template_object.instance_variable_defined?("@#{@object_name}")
  44
+            @template_object.instance_variable_get("@#{@object_name}")
  45
+          end
  46
+        rescue NameError
  47
+          # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
  48
+          nil
  49
+        end
  50
+
  51
+        def retrieve_autoindex(pre_match)
  52
+          object = self.object || @template_object.instance_variable_get("@#{pre_match}")
  53
+          if object && object.respond_to?(:to_param)
  54
+            object.to_param
  55
+          else
  56
+            raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
  57
+          end
  58
+        end
  59
+
  60
+        def add_default_name_and_id_for_value(tag_value, options)
  61
+          unless tag_value.nil?
  62
+            pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
  63
+            specified_id = options["id"]
  64
+            add_default_name_and_id(options)
  65
+            options["id"] += "_#{pretty_tag_value}" if specified_id.blank? && options["id"].present?
  66
+          else
  67
+            add_default_name_and_id(options)
  68
+          end
  69
+        end
  70
+
  71
+        def add_default_name_and_id(options)
  72
+          if options.has_key?("index")
  73
+            options["name"] ||= tag_name_with_index(options["index"])
  74
+            options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
  75
+            options.delete("index")
  76
+          elsif defined?(@auto_index)
  77
+            options["name"] ||= tag_name_with_index(@auto_index)
  78
+            options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
  79
+          else
  80
+            options["name"] ||= tag_name + (options['multiple'] ? '[]' : '')
  81
+            options["id"] = options.fetch("id"){ tag_id }
  82
+          end
  83
+          options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
  84
+        end
  85
+
  86
+        def tag_name
  87
+          "#{@object_name}[#{sanitized_method_name}]"
  88
+        end
  89
+
  90
+        def tag_name_with_index(index)
  91
+          "#{@object_name}[#{index}][#{sanitized_method_name}]"
  92
+        end
  93
+
  94
+        def tag_id
  95
+          "#{sanitized_object_name}_#{sanitized_method_name}"
  96
+        end
  97
+
  98
+        def tag_id_with_index(index)
  99
+          "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
  100
+        end
  101
+
  102
+        def sanitized_object_name
  103
+          @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
  104
+        end
  105
+
  106
+        def sanitized_method_name
  107
+          @sanitized_method_name ||= @method_name.sub(/\?$/,"")
  108
+        end
  109
+
  110
+        def select_content_tag(option_tags, options, html_options)
  111
+          html_options = html_options.stringify_keys
  112
+          add_default_name_and_id(html_options)
  113
+          select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
  114
+          if html_options["multiple"]
  115
+            tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
  116
+          else
  117
+            select
  118
+          end
  119
+        end
  120
+
  121
+        def add_options(option_tags, options, value = nil)
  122
+          if options[:include_blank]
  123
+            option_tags = "<option value=\"\">#{ERB::Util.html_escape(options[:include_blank]) if options[:include_blank].kind_of?(String)}</option>\n" + option_tags
  124
+          end
  125
+          if value.blank? && options[:prompt]
  126
+            prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select')
  127
+            option_tags = "<option value=\"\">#{ERB::Util.html_escape(prompt)}</option>\n" + option_tags
  128
+          end
  129
+          option_tags.html_safe
  130
+        end
  131
+      end
  132
+    end
  133
+  end
  134
+end
54  actionpack/lib/action_view/helpers/tags/check_box.rb
... ...
@@ -0,0 +1,54 @@
  1
+require 'action_view/helpers/tags/checkable'
  2
+
  3
+module ActionView
  4
+  module Helpers
  5
+    module Tags
  6
+      class CheckBox < Base #:nodoc:
  7
+        include Checkable
  8
+
  9
+        def initialize(object_name, method_name, template_object, checked_value, unchecked_value, options)
  10
+          @checked_value   = checked_value
  11
+          @unchecked_value = unchecked_value
  12
+          super(object_name, method_name, template_object, options)
  13
+        end
  14
+
  15
+        def render
  16
+          options = @options.stringify_keys
  17
+          options["type"]     = "checkbox"
  18
+          options["value"]    = @checked_value
  19
+          options["checked"] = "checked" if input_checked?(object, options)
  20
+
  21
+          if options["multiple"]
  22
+            add_default_name_and_id_for_value(@checked_value, options)
  23
+            options.delete("multiple")
  24
+          else
  25
+            add_default_name_and_id(options)
  26
+          end
  27
+
  28
+          hidden = @unchecked_value ? tag("input", "name" => options["name"], "type" => "hidden", "value" => @unchecked_value, "disabled" => options["disabled"]) : ""
  29
+          checkbox = tag("input", options)
  30
+          hidden + checkbox
  31
+        end
  32
+
  33
+        private
  34
+
  35
+        def checked?(value)
  36
+          case value
  37
+          when TrueClass, FalseClass
  38
+            value
  39
+          when NilClass
  40
+            false
  41
+          when Integer
  42
+            value != 0
  43
+          when String
  44
+            value == @checked_value
  45
+          when Array
  46
+            value.include?(@checked_value)
  47
+          else
  48
+            value.to_i != 0
  49
+          end
  50
+        end
  51
+      end
  52
+    end
  53
+  end
  54
+end
16  actionpack/lib/action_view/helpers/tags/checkable.rb
... ...
@@ -0,0 +1,16 @@
  1
+module ActionView
  2
+  module Helpers
  3
+    module Tags
  4
+      module Checkable
  5
+        def input_checked?(object, options)
  6
+          if options.has_key?("checked")
  7
+            checked = options.delete "checked"
  8
+            checked == true || checked == "checked"
  9
+          else
  10
+            checked?(value(object))
  11
+          end
  12
+        end
  13
+      end
  14
+    end
  15
+  end
  16
+end
23  actionpack/lib/action_view/helpers/tags/collection_select.rb
... ...
@@ -0,0 +1,23 @@
  1
+module ActionView
  2
+  module Helpers
  3
+    module Tags
  4
+      class CollectionSelect < Base #:nodoc:
  5
+        def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
  6
+          @collection   = collection
  7
+          @value_method = value_method
  8
+          @text_method  = text_method
  9
+          @html_options = html_options
  10
+
  11
+          super(object_name, method_name, template_object, options)
  12
+        end
  13
+
  14
+        def render
  15
+          selected_value = @options.has_key?(:selected) ? @options[:selected] : value(@object)
  16
+          select_content_tag(
  17
+            options_from_collection_for_select(@collection, @value_method, @text_method, :selected => selected_value, :disabled => @options[:disabled]), @options, @html_options
  18
+          )
  19
+        end
  20
+      end
  21
+    end
  22
+  end
  23
+end
64  actionpack/lib/action_view/helpers/tags/date_select.rb
... ...
@@ -0,0 +1,64 @@
  1
+module ActionView
  2
+  module Helpers
  3
+    module Tags
  4
+      class DateSelect < Base #:nodoc:
  5
+        def initialize(object_name, method_name, template_object, options, html_options)
  6
+          @html_options = html_options
  7
+
  8
+          super(object_name, method_name, template_object, options)
  9
+        end
  10
+
  11
+        def render
  12
+          error_wrapping(datetime_selector(@options, @html_options).send("select_#{select_type}").html_safe)
  13
+        end
  14
+
  15
+        private
  16
+
  17
+        def select_type
  18
+          self.class.name.split("::").last.sub("Select", "").downcase
  19
+        end
  20
+
  21
+        def datetime_selector(options, html_options)
  22
+          datetime = value(object) || default_datetime(options)
  23
+          @auto_index ||= nil
  24
+
  25
+          options = options.dup
  26
+          options[:field_name]           = @method_name
  27
+          options[:include_position]     = true
  28
+          options[:prefix]             ||= @object_name
  29
+          options[:index]                = @auto_index if @auto_index && !options.has_key?(:index)
  30
+
  31
+          DateTimeSelector.new(datetime, options, html_options)
  32
+        end
  33
+
  34
+        def default_datetime(options)
  35
+          return if options[:include_blank] || options[:prompt]
  36
+
  37
+          case options[:default]
  38
+          when nil
  39
+            Time.current
  40
+          when Date, Time
  41
+            options[:default]
  42
+          else
  43
+            default = options[:default].dup
  44
+
  45
+            # Rename :minute and :second to :min and :sec
  46
+            default[:min] ||= default[:minute]
  47
+            default[:sec] ||= default[:second]
  48
+
  49
+            time = Time.current
  50
+
  51
+            [:year, :month, :day, :hour, :min, :sec].each do |key|
  52
+              default[key] ||= time.send(key)
  53
+            end
  54
+
  55
+            Time.utc_time(
  56
+              default[:year], default[:month], default[:day],
  57
+              default[:hour], default[:min], default[:sec]
  58
+            )
  59
+          end
  60
+        end
  61
+      end
  62
+    end
  63
+  end
  64
+end
8  actionpack/lib/action_view/helpers/tags/datetime_select.rb
... ...
@@ -0,0 +1,8 @@
  1
+module ActionView
  2
+  module Helpers
  3
+    module Tags
  4
+      class DatetimeSelect < DateSelect #:nodoc:
  5
+      end
  6
+    end
  7
+  end
  8
+end
8  actionpack/lib/action_view/helpers/tags/email_field.rb
... ...
@@ -0,0 +1,8 @@
  1
+module ActionView
  2
+  module Helpers
  3
+    module Tags
  4
+      class EmailField < TextField #:nodoc:
  5
+      end
  6
+    end
  7
+  end
  8
+end
12  actionpack/lib/action_view/helpers/tags/file_field.rb
... ...
@@ -0,0 +1,12 @@
  1
+module ActionView
  2
+  module Helpers
  3
+    module Tags
  4
+      class FileField < TextField #:nodoc:
  5
+        def render
  6
+          @options.update(:size => nil)
  7
+          super
  8
+        end
  9
+      end
  10
+    end