diff options
23 files changed, 264 insertions, 28 deletions
@@ -14,7 +14,7 @@ gem 'jquery-rails' if ENV['JOURNEY'] gem 'journey', :path => ENV['JOURNEY'] else - gem 'journey' + gem 'journey', :git => 'git://github.com/rails/journey.git', :branch => '1-0-stable' end # This needs to be with require false to avoid diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 4760887456..b67a2d01b9 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -367,7 +367,26 @@ module ActionDispatch SEPARATORS, anchor) - Journey::Path::Pattern.new(strexp) + pattern = Journey::Path::Pattern.new(strexp) + + builder = Journey::GTG::Builder.new pattern.spec + + # Get all the symbol nodes followed by literals that are not the + # dummy node. + symbols = pattern.spec.grep(Journey::Nodes::Symbol).find_all { |n| + builder.followpos(n).first.literal? + } + + # Get all the symbol nodes preceded by literals. + symbols.concat pattern.spec.find_all(&:literal?).map { |n| + builder.followpos(n).first + }.find_all(&:symbol?) + + symbols.each { |x| + x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/ + } + + pattern end private :build_path diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 2806348337..2b128e8445 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -734,7 +734,7 @@ module ActionView def select_day if @options[:use_hidden] || @options[:discard_day] - build_hidden(:day, day) + build_hidden(:day, day || 1) else build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false, :use_two_digit_numbers => @options[:use_two_digit_numbers]) end @@ -742,7 +742,7 @@ module ActionView def select_month if @options[:use_hidden] || @options[:discard_month] - build_hidden(:month, month) + build_hidden(:month, month || 1) else month_options = [] 1.upto(12) do |month_number| @@ -756,7 +756,7 @@ module ActionView def select_year if !@datetime || @datetime == 0 - val = '' + val = '1' middle_year = Date.today.year else val = middle_year = year @@ -783,7 +783,7 @@ module ActionView private %w( sec min hour day month year ).each do |method| define_method(method) do - @datetime.kind_of?(Fixnum) ? @datetime : @datetime.send(method) if @datetime + @datetime.kind_of?(Numeric) ? @datetime : @datetime.send(method) if @datetime end end diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 3ee0d8ebc5..0dd4b19573 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -578,6 +578,7 @@ module ActionView def to_select_tag(choices, options, html_options) selected_value = options.has_key?(:selected) ? options[:selected] : value(object) + choices = choices.to_a if choices.is_a?(Range) # Grouped choices look like this: # diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index a325eb59dc..eda16903b0 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -265,6 +265,7 @@ module ActionView # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ""). # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator # (defaults to +false+). + # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid. # # ==== Examples # number_with_precision(111.2345) # => 111.235 @@ -344,6 +345,7 @@ module ActionView # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ""). # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+) # * <tt>:prefix</tt> - If +:si+ formats the number using the SI prefix (defaults to :binary) + # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid. # ==== Examples # number_to_human_size(123) # => 123 Bytes # number_to_human_size(1234) # => 1.21 KB @@ -425,6 +427,7 @@ module ActionView # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>, <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, <tt>:billion</tt>, <tt>:trillion</tt>, <tt>:quadrillion</tt> # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>, <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, <tt>:pico</tt>, <tt>:femto</tt> # * <tt>:format</tt> - Sets the format of the output string (defaults to "%n %u"). The field types are: + # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid. # # %u The quantifier (ex.: 'thousand') # %n The number diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 8cefa16ad7..222497d311 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -93,6 +93,68 @@ class LegacyRouteSetTests < Test::Unit::TestCase @rs.call(params)[2].join end + def test_symbols_with_dashes + rs.draw do + match '/:artist/:song-omg', :to => lambda { |env| + resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] + [200, {}, [resp]] + } + end + + hash = JSON.load get(URI('http://example.org/journey/faithfully-omg')) + assert_equal({"artist"=>"journey", "song"=>"faithfully"}, hash) + end + + def test_id_with_dash + rs.draw do + match '/journey/:id', :to => lambda { |env| + resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] + [200, {}, [resp]] + } + end + + hash = JSON.load get(URI('http://example.org/journey/faithfully-omg')) + assert_equal({"id"=>"faithfully-omg"}, hash) + end + + def test_dash_with_custom_regexp + rs.draw do + match '/:artist/:song-omg', :constraints => { :song => /\d+/ }, :to => lambda { |env| + resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] + [200, {}, [resp]] + } + end + + hash = JSON.load get(URI('http://example.org/journey/123-omg')) + assert_equal({"artist"=>"journey", "song"=>"123"}, hash) + assert_equal 'Not Found', get(URI('http://example.org/journey/faithfully-omg')) + end + + def test_pre_dash + rs.draw do + match '/:artist/omg-:song', :to => lambda { |env| + resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] + [200, {}, [resp]] + } + end + + hash = JSON.load get(URI('http://example.org/journey/omg-faithfully')) + assert_equal({"artist"=>"journey", "song"=>"faithfully"}, hash) + end + + def test_pre_dash_with_custom_regexp + rs.draw do + match '/:artist/omg-:song', :constraints => { :song => /\d+/ }, :to => lambda { |env| + resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] + [200, {}, [resp]] + } + end + + hash = JSON.load get(URI('http://example.org/journey/omg-123')) + assert_equal({"artist"=>"journey", "song"=>"123"}, hash) + assert_equal 'Not Found', get(URI('http://example.org/journey/omg-faithfully')) + end + def test_regexp_precidence @rs.draw do match '/whois/:domain', :constraints => { diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index fadfb59572..584d1830eb 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -1587,7 +1587,7 @@ class DateHelperTest < ActionView::TestCase start_year = Time.now.year-5 end_year = Time.now.year+5 - expected = '<input name="post[written_on(3i)]" type="hidden" id="post_written_on_3i"/>' + "\n" + expected = '<input name="post[written_on(3i)]" type="hidden" id="post_written_on_3i" value="1"/>' + "\n" expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} expected << "<option value=\"\"></option>\n" start_year.upto(end_year) { |i| expected << %(<option value="#{i}">#{i}</option>\n) } @@ -1601,6 +1601,40 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, date_select("post", "written_on", :order=>[:year, :month], :include_blank=>true) end + def test_date_select_with_nil_and_blank_and_discard_month + @post = Post.new + + start_year = Time.now.year-5 + end_year = Time.now.year+5 + + expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} + expected << "<option value=\"\"></option>\n" + start_year.upto(end_year) { |i| expected << %(<option value="#{i}">#{i}</option>\n) } + expected << "</select>\n" + expected << '<input name="post[written_on(2i)]" type="hidden" id="post_written_on_2i" value="1"/>' + "\n" + expected << '<input name="post[written_on(3i)]" type="hidden" id="post_written_on_3i" value="1"/>' + "\n" + + assert_dom_equal expected, date_select("post", "written_on", :discard_month => true, :include_blank=>true) + end + + def test_date_select_with_nil_and_blank_and_discard_year + @post = Post.new + + expected = '<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="1" />' + "\n" + + expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} + expected << "<option value=\"\"></option>\n" + 1.upto(12) { |i| expected << %(<option value="#{i}">#{Date::MONTHNAMES[i]}</option>\n) } + expected << "</select>\n" + + expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} + expected << "<option value=\"\"></option>\n" + 1.upto(31) { |i| expected << %(<option value="#{i}">#{i}</option>\n) } + expected << "</select>\n" + + assert_dom_equal expected, date_select("post", "written_on", :discard_year => true, :include_blank=>true) + end + def test_date_select_cant_override_discard_hour @post = Post.new @post.written_on = Date.new(2004, 6, 15) @@ -2084,6 +2118,18 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, datetime_select("post", "updated_at", { :date_separator => " / ", :datetime_separator => " , ", :time_separator => " - ", :include_seconds => true }) end + def test_datetime_select_with_integer + @post = Post.new + @post.updated_at = 3 + datetime_select("post", "updated_at") + end + + def test_datetime_select_with_infinity # Float + @post = Post.new + @post.updated_at = (-1.0/0) + datetime_select("post", "updated_at") + end + def test_datetime_select_with_default_prompt @post = Post.new @post.updated_at = nil diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index 4a889beadd..62ab208c2e 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -159,6 +159,13 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_range_options_for_select + assert_dom_equal( + "<option value=\"1\">1</option>\n<option value=\"2\">2</option>\n<option value=\"3\">3</option>", + options_for_select(1..3) + ) + end + def test_hash_options_for_select assert_dom_equal( "<option value=\"<Kroner>\"><DKR></option>\n<option value=\"Dollar\">$</option>", @@ -671,6 +678,15 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_select_with_range + @post = Post.new + @post.category = 0 + assert_dom_equal( + "<select id=\"post_category\" name=\"post[category]\"><option value=\"1\">1</option>\n<option value=\"2\">2</option>\n<option value=\"3\">3</option></select>", + select("post", "category", 1..3) + ) + end + def test_collection_select @post = Post.new @post.author_name = "Babe" diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 4bf7684874..05c32ecf86 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 3.2.1 (unreleased) ## + +* Fix possible race condition when two threads try to define attribute + methods for the same class. + ## Rails 3.2.0 (January 20, 2012) ## * Added a `with_lock` method to ActiveRecord objects, which starts diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 31cd2edc12..f3200aa78a 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -1,6 +1,5 @@ require 'active_support/core_ext/enumerable' require 'active_support/deprecation' -require 'thread' module ActiveRecord # = Active Record Attribute Methods @@ -39,8 +38,6 @@ module ActiveRecord def define_attribute_methods # Use a mutex; we don't want two thread simaltaneously trying to define # attribute methods. - @attribute_methods_mutex ||= Mutex.new - @attribute_methods_mutex.synchronize do return if attribute_methods_generated? superclass.define_attribute_methods unless self == base_class diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index fa5846de39..198db715b2 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -5,6 +5,7 @@ end require 'yaml' require 'set' +require 'thread' require 'active_support/benchmarkable' require 'active_support/dependencies' require 'active_support/descendants_tracker' @@ -390,12 +391,18 @@ module ActiveRecord #:nodoc: class << self # Class methods def inherited(child_class) #:nodoc: - # force attribute methods to be higher in inheritance hierarchy than other generated methods - child_class.generated_attribute_methods - child_class.generated_feature_methods + child_class.initialize_generated_modules super end + def initialize_generated_modules #:nodoc: + @attribute_methods_mutex = Mutex.new + + # force attribute methods to be higher in inheritance hierarchy than other generated methods + generated_attribute_methods + generated_feature_methods + end + def generated_feature_methods @generated_feature_methods ||= begin mod = const_set(:GeneratedFeatureMethods, Module.new) diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb index 7665f1c12e..6562041e9b 100644 --- a/activerecord/test/cases/attribute_methods/read_test.rb +++ b/activerecord/test/cases/attribute_methods/read_test.rb @@ -1,5 +1,6 @@ require "cases/helper" require 'active_support/core_ext/object/inclusion' +require 'thread' module ActiveRecord module AttributeMethods @@ -19,6 +20,13 @@ module ActiveRecord include ActiveRecord::AttributeMethods + def self.define_attribute_methods + # Created in the inherited/included hook for "proper" ARs + @attribute_methods_mutex ||= Mutex.new + + super + end + def self.column_names %w{ one two three } end diff --git a/activesupport/lib/active_support/base64.rb b/activesupport/lib/active_support/base64.rb index bb4c955eea..61a4af1a87 100644 --- a/activesupport/lib/active_support/base64.rb +++ b/activesupport/lib/active_support/base64.rb @@ -40,7 +40,7 @@ module ActiveSupport def self.decode64(value) ActiveSupport::Deprecation.warn "ActiveSupport::Base64.decode64 " \ "is deprecated. Use Base64.decode64 instead", caller - ::Base64.encode64(value) + ::Base64.decode64(value) end def self.encode64s(value) diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 0495741c15..f5fd86bf74 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -396,7 +396,7 @@ module ActiveSupport def __run_callback(key, kind, object, &blk) #:nodoc: name = __callback_runner_name(key, kind) unless object.respond_to?(name) - str = send("_#{kind}_callbacks").compile(key, object) + str = object.send("_#{kind}_callbacks").compile(key, object) class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{name}() #{str} end protected :#{name} diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index 48cf1a435d..6f3fd416c1 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -4,7 +4,7 @@ class DateTime class << self # DateTimes aren't aware of DST rules, so use a consistent non-DST offset when creating a DateTime with an offset in the local zone def local_offset - ::Time.local(2007).utc_offset.to_r / 86400 + ::Time.local(2012).utc_offset.to_r / 86400 end # Returns <tt>Time.zone.now.to_datetime</tt> when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise returns <tt>Time.now.to_datetime</tt>. diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb index 2fad91495d..d71215b447 100644 --- a/activesupport/lib/active_support/tagged_logging.rb +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -27,7 +27,7 @@ module ActiveSupport new_tags.size.times { tags.pop } end - def silence(temporary_level = ERROR, &block) + def silence(temporary_level = Logger::ERROR, &block) @logger.silence(temporary_level, &block) end deprecate :silence diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index e723121bb4..c88e7550be 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -298,7 +298,7 @@ module CallbacksTest end end end - + class AroundPersonResult < MySuper attr_reader :result @@ -309,7 +309,7 @@ module CallbacksTest def tweedle_dum @result = yield end - + def tweedle_1 :tweedle_1 end @@ -317,7 +317,7 @@ module CallbacksTest def tweedle_2 :tweedle_2 end - + def save run_callbacks :save do :running @@ -345,6 +345,55 @@ module CallbacksTest end end + + module ExtendModule + def self.extended(base) + base.class_eval do + set_callback :save, :before, :record3 + end + end + def record3 + @recorder << 3 + end + end + + module IncludeModule + def self.included(base) + base.class_eval do + set_callback :save, :before, :record2 + end + end + def record2 + @recorder << 2 + end + end + + class ExtendCallbacks + + include ActiveSupport::Callbacks + + define_callbacks :save + set_callback :save, :before, :record1 + + include IncludeModule + + def save + run_callbacks :save + end + + attr_reader :recorder + + def initialize + @recorder = [] + end + + private + + def record1 + @recorder << 1 + end + end + class AroundCallbacksTest < Test::Unit::TestCase def test_save_around around = AroundPerson.new @@ -363,7 +412,7 @@ module CallbacksTest ], around.history end end - + class AroundCallbackResultTest < Test::Unit::TestCase def test_save_around around = AroundPersonResult.new @@ -646,4 +695,12 @@ module CallbacksTest end end + class ExtendCallbacksTest < Test::Unit::TestCase + def test_save + model = ExtendCallbacks.new.extend ExtendModule + model.save + assert_equal [1, 2, 3], model.recorder + end + end + end diff --git a/activesupport/test/core_ext/base64_ext_test.rb b/activesupport/test/core_ext/base64_ext_test.rb index 544c990b3c..07052a366a 100644 --- a/activesupport/test/core_ext/base64_ext_test.rb +++ b/activesupport/test/core_ext/base64_ext_test.rb @@ -7,4 +7,13 @@ class Base64Test < Test::Unit::TestCase assert_no_match(/\n/, ActiveSupport::Base64.encode64s("oneverylongstringthatwouldnormallybesplitupbynewlinesbytheregularbase64")) end end + + def test_encode_and_decode + ActiveSupport::Deprecation.silence do + string = "foobar" + encoded_string = ActiveSupport::Base64.encode64(string) + assert_equal "Zm9vYmFy\n", encoded_string + assert_equal string, ActiveSupport::Base64.decode64(encoded_string) + end + end end diff --git a/activesupport/test/tagged_logging_test.rb b/activesupport/test/tagged_logging_test.rb index 17c4214dfc..7ecab33a9a 100644 --- a/activesupport/test/tagged_logging_test.rb +++ b/activesupport/test/tagged_logging_test.rb @@ -64,4 +64,10 @@ class TaggedLoggingTest < ActiveSupport::TestCase assert_equal "[BCX] [Jason] Funky time\n[BCX] Junky time!\n", @output.string end + + test "silence" do + assert_deprecated do + assert_nothing_raised { @logger.silence {} } + end + end end diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index b9944bed26..7da495211d 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -2,7 +2,6 @@ module Rails class Application module Finisher include Initializable - $rails_rake_task = nil initializer :add_generator_templates do config.generators.templates.unshift(*paths["lib/templates"].existent) @@ -49,7 +48,7 @@ module Rails end initializer :eager_load! do - if config.cache_classes && !$rails_rake_task + if config.cache_classes && !(defined?($rails_rake_task) && $rails_rake_task) ActiveSupport.run_load_hooks(:before_eager_load, self) eager_load! end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index ca93f9ef9d..781d7bf47c 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -1,5 +1,6 @@ require 'open-uri' require 'rbconfig' +require 'active_support/core_ext/array/wrap' module Rails module Generators diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index cfd58527ef..8d307ca536 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -29,8 +29,8 @@ module Rails case type when /(string|text|binary|integer)\{(\d+)\}/ return $1, :limit => $2.to_i - when /decimal\{(\d+),(\d+)\}/ - return :decimal, :precision => $1.to_i, :scale => $2.to_i + when /decimal\{(\d+)(,|\.|\-)(\d+)\}/ + return :decimal, :precision => $1.to_i, :scale => $3.to_i else return type, {} end diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb index cdac014965..3f4e160988 100644 --- a/railties/test/generators/migration_generator_test.rb +++ b/railties/test/generators/migration_generator_test.rb @@ -105,18 +105,18 @@ class MigrationGeneratorTest < Rails::Generators::TestCase def test_add_migration_with_attributes_index_declaration_and_attribute_options migration = "add_title_and_content_to_books" - run_generator [migration, "title:string{40}:index", "content:string{255}", "price:decimal{5,2}:index", "discount:decimal{3,2}:uniq"] + run_generator [migration, "title:string{40}:index", "content:string{255}", "price:decimal{1,2}:index", "discount:decimal{3.4}:uniq"] assert_migration "db/migrate/#{migration}.rb" do |content| assert_method :change, content do |up| assert_match(/add_column :books, :title, :string, :limit => 40/, up) assert_match(/add_column :books, :content, :string, :limit => 255/, up) assert_match(/add_column :books, :price, :decimal,/, up) - assert_match(/, :precision => 5/, up) + assert_match(/, :precision => 1/, up) assert_match(/, :scale => 2/, up) assert_match(/add_column :books, :discount, :decimal,/, up) assert_match(/, :precision => 3/, up) - assert_match(/, :scale => 2/, up) + assert_match(/, :scale => 4/, up) end assert_match(/add_index :books, :title/, content) assert_match(/add_index :books, :price/, content) |