From 7b5ed66122873eebb773a6418f3a94d946cc4f8c Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 19 Dec 2004 11:25:55 +0000 Subject: Added respondence to *_before_type_cast for all attributes to return their string-state before they were type casted by the column type. Added use of *_before_type_cast for all input and text fields. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@215 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- actionpack/CHANGELOG | 3 +++ .../action_view/helpers/active_record_helper.rb | 2 -- actionpack/lib/action_view/helpers/form_helper.rb | 14 ++++++++----- .../test/template/active_record_helper_test.rb | 5 +++++ actionpack/test/template/form_helper_test.rb | 7 ++++++- activerecord/CHANGELOG | 3 +++ activerecord/lib/active_record/base.rb | 23 +++++++++++++--------- activerecord/test/abstract_unit.rb | 2 ++ activerecord/test/fixtures/developer.rb | 6 ++---- activerecord/test/fixtures/developers.yml | 3 +++ activerecord/test/validations_test.rb | 9 ++++++++- 11 files changed, 55 insertions(+), 22 deletions(-) diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 548c9b530f..d81c12f027 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,8 @@ *SVN* +* Added use of *_before_type_cast for all input and text fields. This is helpful for getting "100,000" back on a integer-based + validation where the value would normally be "100". + * Added Request#port_string to get something like ":8080" back on 8080 and "" on 80 (or 443 with https). * Added Request#domain (returns string) and Request#subdomains (returns array). diff --git a/actionpack/lib/action_view/helpers/active_record_helper.rb b/actionpack/lib/action_view/helpers/active_record_helper.rb index 07ede179e6..6855fa49c5 100644 --- a/actionpack/lib/action_view/helpers/active_record_helper.rb +++ b/actionpack/lib/action_view/helpers/active_record_helper.rb @@ -132,7 +132,6 @@ module ActionView end alias_method :tag_without_error_wrapping, :tag - def tag(name, options) if object.respond_to?("errors") && object.errors.respond_to?("on") error_wrapping(tag_without_error_wrapping(name, options), object.errors.on(@method_name)) @@ -142,7 +141,6 @@ module ActionView end alias_method :content_tag_without_error_wrapping, :content_tag - def content_tag(name, value, options) if object.respond_to?("errors") && object.errors.respond_to?("on") error_wrapping(content_tag_without_error_wrapping(name, value, options), object.errors.on(@method_name)) diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 253b389a79..d9a1c7d55e 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -135,15 +135,15 @@ module ActionView html_options.merge!({ "size" => options["maxlength"]}) if options["maxlength"] && !options["size"] html_options.delete("size") if field_type == "hidden" html_options.merge!({ "type" => field_type}) - html_options.merge!({ "value" => value.to_s }) unless options["value"] + html_options.merge!({ "value" => value_before_type_cast }) unless options["value"] add_default_name_and_id(html_options) tag("input", html_options) end def to_radio_button_tag(tag_value, options={}) html_options = DEFAULT_FIELD_OPTIONS.merge(options) - html_options.merge!({"checked"=>"checked"}) if value == tag_value - html_options.merge!({"type"=>"radio", "value"=>tag_value.to_s}) + html_options.merge!({ "checked" => "checked" }) if value == tag_value + html_options.merge!({ "type" => "radio", "value"=> tag_value.to_s }) add_default_name_and_id(html_options) tag("input", html_options) @@ -152,11 +152,11 @@ module ActionView def to_text_area_tag(options = {}) options = DEFAULT_TEXT_AREA_OPTIONS.merge(options) add_default_name_and_id(options) - content_tag("textarea", html_escape(value), options) + content_tag("textarea", html_escape(value_before_type_cast), options) end def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0") - options.merge!({"checked" => "checked"}) if !value.nil? && ((value.is_a?(TrueClass) || value.is_a?(FalseClass)) ? value : value.to_i > 0) + options.merge!({ "checked" => "checked" }) if !value.nil? && ((value.is_a?(TrueClass) || value.is_a?(FalseClass)) ? value : value.to_i > 0) options.merge!({ "type" => "checkbox", "value" => checked_value }) add_default_name_and_id(options) tag("input", options) << tag("input", ({ "name" => options['name'], "type" => "hidden", "value" => unchecked_value })) @@ -191,6 +191,10 @@ module ActionView object.send(@method_name) unless object.nil? end + def value_before_type_cast + object.send(@method_name + "_before_type_cast") unless object.nil? + end + private def add_default_name_and_id(options) options['name'] = tag_name unless options.has_key? "name" diff --git a/actionpack/test/template/active_record_helper_test.rb b/actionpack/test/template/active_record_helper_test.rb index f9a2727067..e650b4f0b8 100644 --- a/actionpack/test/template/active_record_helper_test.rb +++ b/actionpack/test/template/active_record_helper_test.rb @@ -14,6 +14,11 @@ class ActiveRecordHelperTest < Test::Unit::TestCase include ActionView::Helpers::UrlHelper Post = Struct.new("Post", :title, :author_name, :body, :secret, :written_on) + Post.class_eval do + alias_method :title_before_type_cast, :title unless respond_to?(:title_before_type_cast) + alias_method :body_before_type_cast, :body unless respond_to?(:body_before_type_cast) + alias_method :author_name_before_type_cast, :author_name unless respond_to?(:author_name_before_type_cast) + end Column = Struct.new("Column", :type, :name, :human_name) def setup diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index d81e2ac270..c6128a9337 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -6,7 +6,12 @@ class FormHelperTest < Test::Unit::TestCase include ActionView::Helpers::FormHelper old_verbose, $VERBOSE = $VERBOSE, nil - Post = Struct.new("Post", :title, :author_name, :body, :secret, :written_on) + Post = Struct.new("Post", :title, :author_name, :body, :secret, :written_on, :cost) + Post.class_eval do + alias_method :title_before_type_cast, :title unless respond_to?(:title_before_type_cast) + alias_method :body_before_type_cast, :body unless respond_to?(:body_before_type_cast) + alias_method :author_name_before_type_cast, :author_name unless respond_to?(:author_name_before_type_cast) + end $VERBOSE = old_verbose def setup diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index e9927ae980..573fa9f618 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,8 @@ *SVN* +* Added respondence to *_before_type_cast for all attributes to return their string-state before they were type casted by the column type. + This is helpful for getting "100,000" back on a integer-based validation where the value would normally be "100". + * Fixed the automated timestamping feature when running under Rails' development environment that resets the inheritable attributes on each request. * Added Base#update_attributes that'll accept a hash of attributes and save the record (returning true if it passed validation, false otherwise). diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index b0de050185..38002ad2a0 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -920,10 +920,10 @@ module ActiveRecord #:nodoc: def method_missing(method_id, *arguments) method_name = method_id.id2name - - if method_name =~ read_method? && @attributes.include?($1) return read_attribute($1) + elsif method_name =~ read_untyped_method? && @attributes.include?($1) + return read_attribute_before_type_cast($1) elsif method_name =~ write_method? && @attributes.include?($1) write_attribute($1, arguments[0]) elsif method_name =~ query_method? && @attributes.include?($1) @@ -933,25 +933,30 @@ module ActiveRecord #:nodoc: end end - def read_method?() /^([a-zA-Z][-_\w]*)[^=?]*$/ end - def write_method?() /^([a-zA-Z][-_\w]*)=.*$/ end - def query_method?() /^([a-zA-Z][-_\w]*)\?$/ end + def read_method?() /^([a-zA-Z][-_\w]*)[^=?]*$/ end + def read_untyped_method?() /^([a-zA-Z][-_\w]*)_before_type_cast$/ end + def write_method?() /^([a-zA-Z][-_\w]*)=.*$/ end + def query_method?() /^([a-zA-Z][-_\w]*)\?$/ end - # Returns the value of attribute identified by attr_name after it has been type cast (for example, + # Returns the value of attribute identified by attr_name after it has been type cast (for example, # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). def read_attribute(attr_name) #:doc: if @attributes.keys.include? attr_name if column = column_for_attribute(attr_name) - @attributes[attr_name] = unserializable_attribute?(attr_name, column) ? + unserializable_attribute?(attr_name, column) ? unserialize_attribute(attr_name) : column.type_cast(@attributes[attr_name]) + else + @attributes[attr_name] end - - @attributes[attr_name] else nil end end + def read_attribute_before_type_cast(attr_name) + @attributes[attr_name] + end + # Returns true if the attribute is of a text column and marked for serialization. def unserializable_attribute?(attr_name, column) @attributes[attr_name] && column.send(:type) == :text && @attributes[attr_name].is_a?(String) && self.class.serialized_attributes[attr_name] diff --git a/activerecord/test/abstract_unit.rb b/activerecord/test/abstract_unit.rb index 293f3ba6b3..67984a4609 100755 --- a/activerecord/test/abstract_unit.rb +++ b/activerecord/test/abstract_unit.rb @@ -4,6 +4,8 @@ $:.unshift(File.dirname(__FILE__) + '/../lib') require 'test/unit' require 'active_record' require 'active_record/fixtures' +require 'active_record/support/binding_of_caller' +require 'active_record/support/breakpoint' require 'connection' class Test::Unit::TestCase #:nodoc: diff --git a/activerecord/test/fixtures/developer.rb b/activerecord/test/fixtures/developer.rb index 1c4cc30506..78f8f54db7 100644 --- a/activerecord/test/fixtures/developer.rb +++ b/activerecord/test/fixtures/developer.rb @@ -1,8 +1,6 @@ class Developer < ActiveRecord::Base has_and_belongs_to_many :projects - protected - def validate - errors.add_on_boundary_breaking("name", 3..20) - end + validates_inclusion_of :salary, :in => 50000..200000 + validates_length_of :name, :within => 3..20 end diff --git a/activerecord/test/fixtures/developers.yml b/activerecord/test/fixtures/developers.yml index 733455f789..fc33af99b6 100644 --- a/activerecord/test/fixtures/developers.yml +++ b/activerecord/test/fixtures/developers.yml @@ -1,13 +1,16 @@ david: id: 1 name: David + salary: 80000 jamis: id: 2 name: Jamis + salary: 150000 <% for digit in 3..10 %> dev_<%= digit %>: id: <%= digit %> name: fixture_<%= digit %> + salary: 100000 <% end %> \ No newline at end of file diff --git a/activerecord/test/validations_test.rb b/activerecord/test/validations_test.rb index b6c9cfed7d..8949206706 100755 --- a/activerecord/test/validations_test.rb +++ b/activerecord/test/validations_test.rb @@ -348,4 +348,11 @@ class ValidationsTest < Test::Unit::TestCase assert_equal "hoo 5", t.errors["title"] end -end + + def test_throw_away_typing + d = Developer.create "name" => "David", "salary" => "100,000" + assert !d.valid? + assert_not_equal "100,000", d.salary + assert_equal "100,000", d.salary_before_type_cast + end +end \ No newline at end of file -- cgit v1.2.3