diff options
34 files changed, 852 insertions, 606 deletions
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index e2b9df5d02..fec0ecf477 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -507,6 +507,18 @@ class BaseTest < ActiveSupport::TestCase assert_equal("Thanks for signing up this afternoon", mail.subject) end + test "action methods should be refreshed after defining new method" do + class FooMailer < ActionMailer::Base + # this triggers action_methods + self.respond_to?(:foo) + + def notify + end + end + + assert_equal ["notify"], FooMailer.action_methods + end + protected # Execute the block setting the given values and restoring old values after diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index 8a8337858b..db0a6736e0 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -72,6 +72,13 @@ module AbstractController end end + # action_methods are cached and there is sometimes need to refresh + # them. clear_action_methods! allows you to do that, so next time + # you run action_methods, they will be recalculated + def clear_action_methods! + @action_methods = nil + end + # Returns the full controller name, underscored, without the ending Controller. # For instance, MyApp::MyPostsController would return "my_app/my_posts" for # controller_name. @@ -81,6 +88,11 @@ module AbstractController def controller_path @controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous? end + + def method_added(name) + super + clear_action_methods! + end end abstract! diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 77688fe397..d23b580d97 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -392,10 +392,9 @@ module ActionDispatch end def generate - error = ActionController::RoutingError.new("No route matches #{options.inspect}") path, params = @set.set.generate(:path_info, named_route, options, recall, opts) - raise error unless path + raise_routing_error unless path params.reject! {|k,v| !v } @@ -404,7 +403,7 @@ module ActionDispatch path << "?#{params.to_query}" if params.any? "#{script_name}#{path}" rescue Rack::Mount::RoutingError - raise error + raise_routing_error end def opts @@ -421,6 +420,10 @@ module ActionDispatch {:parameterize => parameterize} end + def raise_routing_error + raise ActionController::RoutingError.new("No route matches #{options.inspect}") + end + def different_controller? return false unless current_controller controller.to_param != current_controller.to_param diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb index 63f6154ec4..d5638a69e5 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb @@ -7,6 +7,7 @@ module ActionView # The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements. # These helper methods extend Action View making them callable within your template files. module SanitizeHelper + extend ActiveSupport::Concern # This +sanitize+ helper will html encode all tags and strip all attributes that # aren't specifically allowed. # diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 52a016c9c1..c1de5c8cb3 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -10,6 +10,9 @@ module ActionView # your views. These helper methods extend Action View making them callable # within your template files. module TextHelper + extend ActiveSupport::Concern + + include SanitizeHelper # The preferred method of outputting text in your views is to use the # <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods # do not operate as expected in an eRuby code block. If you absolutely must diff --git a/actionpack/test/abstract/abstract_controller_test.rb b/actionpack/test/abstract/abstract_controller_test.rb index 3b5013a47a..19855490b4 100644 --- a/actionpack/test/abstract/abstract_controller_test.rb +++ b/actionpack/test/abstract/abstract_controller_test.rb @@ -250,5 +250,19 @@ module AbstractController end end + class Me6 < AbstractController::Base + self.action_methods + + def index + end + end + + class TestActionMethodsReloading < ActiveSupport::TestCase + + test "action_methods should be reloaded after defining a new method" do + assert_equal ["index"], Me6.action_methods + end + end + end end diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 5ea7636427..2516377afd 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -37,12 +37,13 @@ module ActiveModel # end # # def name=(val) - # name_will_change! + # name_will_change! unless val == @name # @name = val # end # # def save # @previously_changed = changes + # @changed_attributes.clear # end # # end @@ -77,12 +78,6 @@ module ActiveModel # person.changed # => ['name'] # person.changes # => { 'name' => ['Bill', 'Bob'] } # - # Resetting an attribute returns it to its original state: - # person.reset_name! # => 'Bill' - # person.changed? # => false - # person.name_changed? # => false - # person.name # => 'Bill' - # # If an attribute is modified in-place then make use of <tt>[attribute_name]_will_change!</tt> # to mark that the attribute is changing. Otherwise ActiveModel can't track changes to # in-place attributes. diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index edafb53ad5..272ddb1554 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -86,11 +86,7 @@ module ActiveModel # p.errors[:name] # => ["can not be nil"] # p.errors['name'] # => ["can not be nil"] def [](attribute) - if errors = get(attribute.to_sym) - errors - else - set(attribute.to_sym, []) - end + get(attribute.to_sym) || set(attribute.to_sym, []) end # Adds to the supplied attribute the supplied error message. diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 1a58d4c4fb..3407c59e7a 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -118,11 +118,13 @@ module ActiveModel # end # def validate(*args, &block) - options = args.last - if options.is_a?(Hash) && options.key?(:on) + options = args.extract_options! + if options.key?(:on) + options = options.dup options[:if] = Array.wrap(options[:if]) options[:if] << "validation_context == :#{options[:on]}" end + args << options set_callback(:validate, *args, &block) end diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb index c8a77ad666..a7af4f2b4d 100644 --- a/activemodel/lib/active_model/validations/length.rb +++ b/activemodel/lib/active_model/validations/length.rb @@ -40,8 +40,6 @@ module ActiveModel CHECKS.each do |key, validity_check| next unless check_value = options[key] - default_message = options[MESSAGES[key]] - options[:message] ||= default_message if default_message valid_value = if key == :maximum value.nil? || value.size.send(validity_check, check_value) @@ -51,8 +49,13 @@ module ActiveModel next if valid_value - record.errors.add(attribute, MESSAGES[key], - options.except(*RESERVED_OPTIONS).merge!(:count => check_value)) + errors_options = options.except(*RESERVED_OPTIONS) + errors_options[:count] = check_value + + default_message = options[MESSAGES[key]] + errors_options[:message] ||= default_message if default_message + + record.errors.add(attribute, MESSAGES[key], errors_options) end end end diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index 52192d5988..163124d531 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -111,7 +111,7 @@ module ActiveModel #:nodoc: # Accepts options that will be made available through the +options+ reader. def initialize(options) - @options = options + @options = options.freeze end # Return the kind for this validator. diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb index e1a35be384..858ae9cb69 100644 --- a/activemodel/test/cases/dirty_test.rb +++ b/activemodel/test/cases/dirty_test.rb @@ -3,10 +3,11 @@ require "cases/helper" class DirtyTest < ActiveModel::TestCase class DirtyModel include ActiveModel::Dirty - define_attribute_methods [:name] + define_attribute_methods [:name, :color] def initialize @name = nil + @color = nil end def name @@ -17,13 +18,92 @@ class DirtyTest < ActiveModel::TestCase name_will_change! @name = val end + + def color + @color + end + + def color=(val) + color_will_change! unless val == @color + @color = val + end + + def save + @previously_changed = changes + @changed_attributes.clear + end + end + + setup do + @model = DirtyModel.new + end + + test "setting attribute will result in change" do + assert !@model.changed? + assert !@model.name_changed? + @model.name = "Ringo" + assert @model.changed? + assert @model.name_changed? + end + + test "list of changed attributes" do + assert_equal [], @model.changed + @model.name = "Paul" + assert_equal ['name'], @model.changed + end + + test "changes to attribute values" do + assert !@model.changes['name'] + @model.name = "John" + assert_equal [nil, "John"], @model.changes['name'] end test "changes accessible through both strings and symbols" do - model = DirtyModel.new - model.name = "David" - assert_not_nil model.changes[:name] - assert_not_nil model.changes['name'] + @model.name = "David" + assert_not_nil @model.changes[:name] + assert_not_nil @model.changes['name'] + end + + test "attribute mutation" do + @model.instance_variable_set("@name", "Yam") + assert !@model.name_changed? + @model.name.replace("Hadad") + assert !@model.name_changed? + @model.name_will_change! + @model.name.replace("Baal") + assert @model.name_changed? + end + + test "resetting attribute" do + @model.name = "Bob" + @model.reset_name! + assert_nil @model.name + #assert !@model.name_changed #Doesn't work yet + end + + test "setting color to same value should not result in change being recorded" do + @model.color = "red" + assert @model.color_changed? + @model.save + assert !@model.color_changed? + assert !@model.changed? + @model.color = "red" + assert !@model.color_changed? + assert !@model.changed? + end + + test "saving should reset model's changed status" do + @model.name = "Alf" + assert @model.changed? + @model.save + assert !@model.changed? + assert !@model.name_changed? + end + + test "saving should preserve previous changes" do + @model.name = "Jericho Cane" + @model.save + assert_equal [nil, "Jericho Cane"], @model.previous_changes['name'] end end diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb new file mode 100644 index 0000000000..79b45bb298 --- /dev/null +++ b/activemodel/test/cases/errors_test.rb @@ -0,0 +1,65 @@ +require "cases/helper" + +class ErrorsTest < ActiveModel::TestCase + class Person + extend ActiveModel::Naming + def initialize + @errors = ActiveModel::Errors.new(self) + end + + attr_accessor :name + attr_reader :errors + + def validate! + errors.add(:name, "can not be nil") if name == nil + end + + def read_attribute_for_validation(attr) + send(attr) + end + + def self.human_attribute_name(attr, options = {}) + attr + end + + def self.lookup_ancestors + [self] + end + + end + + test "method validate! should work" do + person = Person.new + person.validate! + assert_equal ["name can not be nil"], person.errors.full_messages + assert_equal ["can not be nil"], person.errors[:name] + + end + + test 'should be able to assign error' do + person = Person.new + person.errors[:name] = 'should not be nil' + assert_equal ["should not be nil"], person.errors[:name] + end + + test 'should be able to add an error on an attribute' do + person = Person.new + person.errors.add(:name, "can not be blank") + assert_equal ["can not be blank"], person.errors[:name] + end + + test 'should respond to size' do + person = Person.new + person.errors.add(:name, "can not be blank") + assert_equal 1, person.errors.size + end + + test 'to_a should return an array' do + person = Person.new + person.errors.add(:name, "can not be blank") + person.errors.add(:name, "can not be nil") + assert_equal ["name can not be blank", "name can not be nil"], person.errors.to_a + + end + +end diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb index 012c5a2f37..1e6180a938 100644 --- a/activemodel/test/cases/validations/length_validation_test.rb +++ b/activemodel/test/cases/validations/length_validation_test.rb @@ -229,6 +229,20 @@ class LengthValidationTest < ActiveModel::TestCase assert_equal ["hoo 5"], t.errors["title"] end + def test_validates_length_of_custom_errors_for_both_too_short_and_too_long + Topic.validates_length_of :title, :minimum => 3, :maximum => 5, :too_short => 'too short', :too_long => 'too long' + + t = Topic.new(:title => 'a') + assert t.invalid? + assert t.errors[:title].any? + assert_equal ['too short'], t.errors['title'] + + t = Topic.new(:title => 'aaaaaa') + assert t.invalid? + assert t.errors[:title].any? + assert_equal ['too long'], t.errors['title'] + end + def test_validates_length_of_custom_errors_for_is_with_message Topic.validates_length_of( :title, :is=>5, :message=>"boo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 7371a1ecb2..8da4fbcba7 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1400,7 +1400,7 @@ MSG # as it copies the object's attributes only, not its associations. The extent of a "deep" clone is # application specific and is therefore left to the application to implement according to its need. def initialize_copy(other) - callback(:after_initialize) if respond_to_without_attributes?(:after_initialize) + _run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks) cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast) cloned_attributes.delete(self.class.primary_key) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index d7b5bf8e31..e2b3773a99 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -30,7 +30,7 @@ module ActiveRecord if value.acts_like?(:date) || value.acts_like?(:time) "'#{quoted_date(value)}'" else - "'#{quote_string(value.to_yaml)}'" + "'#{quote_string(value.to_s)}'" end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index ffc3847a31..7dee68502f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -327,6 +327,8 @@ module ActiveRecord # # Note: SQLite doesn't support index length def add_index(table_name, column_name, options = {}) + options[:name] = options[:name].to_s if options.key?(:name) + column_names = Array.wrap(column_name) index_name = index_name(table_name, :column => column_names) diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 412be895c4..d7494ebb5a 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -50,9 +50,9 @@ module ActiveRecord def find_in_batches(options = {}) relation = self - if orders.present? || taken.present? - ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size") - end + if orders.present? || taken.present? + ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size") + end if (finder_options = options.except(:start, :batch_size)).present? raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present? @@ -73,7 +73,7 @@ module ActiveRecord break if records.size < batch_size if primary_key_offset = records.last.id - records = relation.where(primary_key.gt(primary_key_offset)).all + records = relation.where(primary_key.gt(primary_key_offset)).to_a else raise "Primary key not included in the custom select clause" end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index a192e044ea..b34c11973b 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -169,6 +169,8 @@ module ActiveRecord # Person.exists?(['name LIKE ?', "%#{query}%"]) # Person.exists? def exists?(id = nil) + id = id.id if ActiveRecord::Base === id + case id when Array, Hash where(id).exists? @@ -284,6 +286,8 @@ module ActiveRecord end def find_one(id) + id = id.id if ActiveRecord::Base === id + record = where(primary_key.eq(id)).first unless record diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index a92d180442..e71f1cca72 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -31,7 +31,7 @@ module ActiveRecord end def group(*args) - clone.tap {|r| r.group_values += args if args.present? } + clone.tap {|r| r.group_values += args.flatten if args.present? } end def order(*args) @@ -95,7 +95,7 @@ module ActiveRecord order_clause = arel.send(:order_clauses).join(', ') relation = except(:order) - if order_clause.present? + unless order_clauses.blank? relation.order(reverse_sql_order(order_clause)) else relation.order("#{@klass.table_name}.#{@klass.primary_key} DESC") @@ -238,7 +238,7 @@ module ActiveRecord end def reverse_sql_order(order_query) - order_query.to_s.split(/,/).each { |s| + order_query.split(',').each { |s| if s.match(/\s(asc|ASC)$/) s.gsub!(/\s(asc|ASC)$/, ' DESC') elsif s.match(/\s(desc|DESC)$/) diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb index e2783087ec..c1bc3214ea 100644 --- a/activerecord/lib/active_record/schema.rb +++ b/activerecord/lib/active_record/schema.rb @@ -2,7 +2,7 @@ require 'active_support/core_ext/object/blank' module ActiveRecord # = Active Record Schema - # + # # Allows programmers to programmatically define a schema in a portable # DSL. This means you can define tables, indexes, etc. without using SQL # directly, so your applications can more easily support multiple diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index a4757773d8..e9af20e1b6 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -8,13 +8,13 @@ module ActiveRecord # output format (i.e., ActiveRecord::Schema). class SchemaDumper #:nodoc: private_class_method :new - + ## # :singleton-method: - # A list of tables which should not be dumped to the schema. + # A list of tables which should not be dumped to the schema. # Acceptable values are strings as well as regexp. # This setting is only used if ActiveRecord::Base.schema_format == :ruby - cattr_accessor :ignore_tables + cattr_accessor :ignore_tables @@ignore_tables = [] def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT) @@ -71,7 +71,7 @@ HEADER else raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.' end - end + end table(tbl, stream) end end @@ -87,7 +87,7 @@ HEADER elsif @connection.respond_to?(:primary_key) pk = @connection.primary_key(table) end - + tbl.print " create_table #{table.inspect}" if columns.detect { |c| c.name == pk } if pk != 'id' @@ -105,7 +105,7 @@ HEADER next if column.name == pk spec = {} spec[:name] = column.name.inspect - + # AR has an optimisation which handles zero-scale decimals as integers. This # code ensures that the dumper still dumps the column as a decimal. spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) } @@ -148,7 +148,7 @@ HEADER tbl.puts " end" tbl.puts - + indexes(table, tbl) tbl.rewind @@ -158,7 +158,7 @@ HEADER stream.puts "# #{e.message}" stream.puts end - + stream end @@ -172,7 +172,7 @@ HEADER value.inspect end end - + def indexes(table, stream) if (indexes = @connection.indexes(table)).any? add_index_statements = indexes.map do |index| diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index d328ca630b..b31611e27a 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -32,6 +32,28 @@ class AssociationsTest < ActiveRecord::TestCase assert_equal 1, liquids[0].molecules.length end + def test_clear_association_cache_stored + firm = Firm.find(1) + assert_kind_of Firm, firm + + firm.clear_association_cache + assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort + end + + def test_clear_association_cache_new_record + firm = Firm.new + client_stored = Client.find(3) + client_new = Client.new + client_new.name = "The Joneses" + clients = [ client_stored, client_new ] + + firm.clients << clients + assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set + + firm.clear_association_cache + assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set + end + def test_loading_the_association_target_should_keep_child_records_marked_for_destruction ship = Ship.create!(:name => "The good ship Dollypop") part = ship.parts.create!(:name => "Mast") diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index d59fa0a632..d20b762853 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -1,9 +1,15 @@ require "cases/helper" -require 'models/topic' require 'models/minimalistic' +require 'models/developer' +require 'models/auto_id' +require 'models/computer' +require 'models/topic' +require 'models/company' +require 'models/category' +require 'models/reply' class AttributeMethodsTest < ActiveRecord::TestCase - fixtures :topics + fixtures :topics, :developers, :companies, :computers def setup @old_matchers = ActiveRecord::Base.send(:attribute_method_matchers).dup @@ -16,6 +22,274 @@ class AttributeMethodsTest < ActiveRecord::TestCase ActiveRecord::Base.send(:attribute_method_matchers).concat(@old_matchers) end + def test_attribute_present + t = Topic.new + t.title = "hello there!" + t.written_on = Time.now + assert t.attribute_present?("title") + assert t.attribute_present?("written_on") + assert !t.attribute_present?("content") + end + + def test_attribute_keys_on_new_instance + t = Topic.new + assert_equal nil, t.title, "The topics table has a title column, so it should be nil" + assert_raise(NoMethodError) { t.title2 } + end + + def test_boolean_attributes + assert ! Topic.find(1).approved? + assert Topic.find(2).approved? + end + + def test_set_attributes + topic = Topic.find(1) + topic.attributes = { "title" => "Budget", "author_name" => "Jason" } + topic.save + assert_equal("Budget", topic.title) + assert_equal("Jason", topic.author_name) + assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address) + end + + def test_set_attributes_without_hash + topic = Topic.new + assert_nothing_raised { topic.attributes = '' } + end + + def test_integers_as_nil + test = AutoId.create('value' => '') + assert_nil AutoId.find(test.id).value + end + + def test_set_attributes_with_block + topic = Topic.new do |t| + t.title = "Budget" + t.author_name = "Jason" + end + + assert_equal("Budget", topic.title) + assert_equal("Jason", topic.author_name) + end + + def test_respond_to? + topic = Topic.find(1) + assert_respond_to topic, "title" + assert_respond_to topic, "title?" + assert_respond_to topic, "title=" + assert_respond_to topic, :title + assert_respond_to topic, :title? + assert_respond_to topic, :title= + assert_respond_to topic, "author_name" + assert_respond_to topic, "attribute_names" + assert !topic.respond_to?("nothingness") + assert !topic.respond_to?(:nothingness) + end + + def test_array_content + topic = Topic.new + topic.content = %w( one two three ) + topic.save + + assert_equal(%w( one two three ), Topic.find(topic.id).content) + end + + def test_read_attributes_before_type_cast + category = Category.new({:name=>"Test categoty", :type => nil}) + category_attrs = {"name"=>"Test categoty", "type" => nil, "categorizations_count" => nil} + assert_equal category_attrs , category.attributes_before_type_cast + end + + if current_adapter?(:MysqlAdapter) + def test_read_attributes_before_type_cast_on_boolean + bool = Booleantest.create({ "value" => false }) + assert_equal "0", bool.reload.attributes_before_type_cast["value"] + end + end + + def test_read_attributes_before_type_cast_on_datetime + developer = Developer.find(:first) + # Oracle adapter returns Time before type cast + unless current_adapter?(:OracleAdapter) + assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"] + else + assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db) + + developer.created_at = "345643456" + assert_equal developer.created_at_before_type_cast, "345643456" + assert_equal developer.created_at, nil + + developer.created_at = "2010-03-21T21:23:32+01:00" + assert_equal developer.created_at_before_type_cast, "2010-03-21T21:23:32+01:00" + assert_equal developer.created_at, Time.parse("2010-03-21T21:23:32+01:00") + end + end + + def test_hash_content + topic = Topic.new + topic.content = { "one" => 1, "two" => 2 } + topic.save + + assert_equal 2, Topic.find(topic.id).content["two"] + + topic.content_will_change! + topic.content["three"] = 3 + topic.save + + assert_equal 3, Topic.find(topic.id).content["three"] + end + + def test_update_array_content + topic = Topic.new + topic.content = %w( one two three ) + + topic.content.push "four" + assert_equal(%w( one two three four ), topic.content) + + topic.save + + topic = Topic.find(topic.id) + topic.content << "five" + assert_equal(%w( one two three four five ), topic.content) + end + + def test_case_sensitive_attributes_hash + # DB2 is not case-sensitive + return true if current_adapter?(:DB2Adapter) + + assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes + end + + def test_hashes_not_mangled + new_topic = { :title => "New Topic" } + new_topic_values = { :title => "AnotherTopic" } + + topic = Topic.new(new_topic) + assert_equal new_topic[:title], topic.title + + topic.attributes= new_topic_values + assert_equal new_topic_values[:title], topic.title + end + + def test_create_through_factory + topic = Topic.create("title" => "New Topic") + topicReloaded = Topic.find(topic.id) + assert_equal(topic, topicReloaded) + end + + def test_write_attribute + topic = Topic.new + topic.send(:write_attribute, :title, "Still another topic") + assert_equal "Still another topic", topic.title + + topic.send(:write_attribute, "title", "Still another topic: part 2") + assert_equal "Still another topic: part 2", topic.title + end + + def test_read_attribute + topic = Topic.new + topic.title = "Don't change the topic" + assert_equal "Don't change the topic", topic.send(:read_attribute, "title") + assert_equal "Don't change the topic", topic["title"] + + assert_equal "Don't change the topic", topic.send(:read_attribute, :title) + assert_equal "Don't change the topic", topic[:title] + end + + def test_read_attribute_when_false + topic = topics(:first) + topic.approved = false + assert !topic.approved?, "approved should be false" + topic.approved = "false" + assert !topic.approved?, "approved should be false" + end + + def test_read_attribute_when_true + topic = topics(:first) + topic.approved = true + assert topic.approved?, "approved should be true" + topic.approved = "true" + assert topic.approved?, "approved should be true" + end + + def test_read_write_boolean_attribute + topic = Topic.new + # puts "" + # puts "New Topic" + # puts topic.inspect + topic.approved = "false" + # puts "Expecting false" + # puts topic.inspect + assert !topic.approved?, "approved should be false" + topic.approved = "false" + # puts "Expecting false" + # puts topic.inspect + assert !topic.approved?, "approved should be false" + topic.approved = "true" + # puts "Expecting true" + # puts topic.inspect + assert topic.approved?, "approved should be true" + topic.approved = "true" + # puts "Expecting true" + # puts topic.inspect + assert topic.approved?, "approved should be true" + # puts "" + end + + def test_query_attribute_string + [nil, "", " "].each do |value| + assert_equal false, Topic.new(:author_name => value).author_name? + end + + assert_equal true, Topic.new(:author_name => "Name").author_name? + end + + def test_query_attribute_number + [nil, 0, "0"].each do |value| + assert_equal false, Developer.new(:salary => value).salary? + end + + assert_equal true, Developer.new(:salary => 1).salary? + assert_equal true, Developer.new(:salary => "1").salary? + end + + def test_query_attribute_boolean + [nil, "", false, "false", "f", 0].each do |value| + assert_equal false, Topic.new(:approved => value).approved? + end + + [true, "true", "1", 1].each do |value| + assert_equal true, Topic.new(:approved => value).approved? + end + end + + def test_query_attribute_with_custom_fields + object = Company.find_by_sql(<<-SQL).first + SELECT c1.*, c2.ruby_type as string_value, c2.rating as int_value + FROM companies c1, companies c2 + WHERE c1.firm_id = c2.id + AND c1.id = 2 + SQL + + assert_equal "Firm", object.string_value + assert object.string_value? + + object.string_value = " " + assert !object.string_value? + + assert_equal 1, object.int_value.to_i + assert object.int_value? + + object.int_value = "0" + assert !object.int_value? + end + + def test_non_attribute_access_and_assignment + topic = Topic.new + assert !topic.respond_to?("mumbo") + assert_raise(NoMethodError) { topic.mumbo } + assert_raise(NoMethodError) { topic.mumbo = 5 } + end + def test_undeclared_attribute_method_does_not_affect_respond_to_and_method_missing topic = @target.new(:title => 'Budget') assert topic.respond_to?('title') diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index df6895f0d0..56ec4ca58c 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -52,254 +52,6 @@ class BasicsTest < ActiveRecord::TestCase assert Topic.table_exists? end - def test_set_attributes - topic = Topic.find(1) - topic.attributes = { "title" => "Budget", "author_name" => "Jason" } - topic.save - assert_equal("Budget", topic.title) - assert_equal("Jason", topic.author_name) - assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address) - end - - def test_set_attributes_without_hash - topic = Topic.new - assert_nothing_raised { topic.attributes = '' } - end - - def test_integers_as_nil - test = AutoId.create('value' => '') - assert_nil AutoId.find(test.id).value - end - - def test_set_attributes_with_block - topic = Topic.new do |t| - t.title = "Budget" - t.author_name = "Jason" - end - - assert_equal("Budget", topic.title) - assert_equal("Jason", topic.author_name) - end - - def test_respond_to? - topic = Topic.find(1) - assert_respond_to topic, "title" - assert_respond_to topic, "title?" - assert_respond_to topic, "title=" - assert_respond_to topic, :title - assert_respond_to topic, :title? - assert_respond_to topic, :title= - assert_respond_to topic, "author_name" - assert_respond_to topic, "attribute_names" - assert !topic.respond_to?("nothingness") - assert !topic.respond_to?(:nothingness) - end - - def test_array_content - topic = Topic.new - topic.content = %w( one two three ) - topic.save - - assert_equal(%w( one two three ), Topic.find(topic.id).content) - end - - def test_read_attributes_before_type_cast - category = Category.new({:name=>"Test categoty", :type => nil}) - category_attrs = {"name"=>"Test categoty", "type" => nil, "categorizations_count" => nil} - assert_equal category_attrs , category.attributes_before_type_cast - end - - if current_adapter?(:MysqlAdapter) - def test_read_attributes_before_type_cast_on_boolean - bool = Booleantest.create({ "value" => false }) - assert_equal "0", bool.reload.attributes_before_type_cast["value"] - end - end - - def test_read_attributes_before_type_cast_on_datetime - developer = Developer.find(:first) - # Oracle adapter returns Time before type cast - unless current_adapter?(:OracleAdapter) - assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"] - else - assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db) - - developer.created_at = "345643456" - assert_equal developer.created_at_before_type_cast, "345643456" - assert_equal developer.created_at, nil - - developer.created_at = "2010-03-21T21:23:32+01:00" - assert_equal developer.created_at_before_type_cast, "2010-03-21T21:23:32+01:00" - assert_equal developer.created_at, Time.parse("2010-03-21T21:23:32+01:00") - end - end - - def test_hash_content - topic = Topic.new - topic.content = { "one" => 1, "two" => 2 } - topic.save - - assert_equal 2, Topic.find(topic.id).content["two"] - - topic.content_will_change! - topic.content["three"] = 3 - topic.save - - assert_equal 3, Topic.find(topic.id).content["three"] - end - - def test_update_array_content - topic = Topic.new - topic.content = %w( one two three ) - - topic.content.push "four" - assert_equal(%w( one two three four ), topic.content) - - topic.save - - topic = Topic.find(topic.id) - topic.content << "five" - assert_equal(%w( one two three four five ), topic.content) - end - - def test_case_sensitive_attributes_hash - # DB2 is not case-sensitive - return true if current_adapter?(:DB2Adapter) - - assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes - end - - def test_hashes_not_mangled - new_topic = { :title => "New Topic" } - new_topic_values = { :title => "AnotherTopic" } - - topic = Topic.new(new_topic) - assert_equal new_topic[:title], topic.title - - topic.attributes= new_topic_values - assert_equal new_topic_values[:title], topic.title - end - - def test_create_through_factory - topic = Topic.create("title" => "New Topic") - topicReloaded = Topic.find(topic.id) - assert_equal(topic, topicReloaded) - end - - def test_write_attribute - topic = Topic.new - topic.send(:write_attribute, :title, "Still another topic") - assert_equal "Still another topic", topic.title - - topic.send(:write_attribute, "title", "Still another topic: part 2") - assert_equal "Still another topic: part 2", topic.title - end - - def test_read_attribute - topic = Topic.new - topic.title = "Don't change the topic" - assert_equal "Don't change the topic", topic.send(:read_attribute, "title") - assert_equal "Don't change the topic", topic["title"] - - assert_equal "Don't change the topic", topic.send(:read_attribute, :title) - assert_equal "Don't change the topic", topic[:title] - end - - def test_read_attribute_when_false - topic = topics(:first) - topic.approved = false - assert !topic.approved?, "approved should be false" - topic.approved = "false" - assert !topic.approved?, "approved should be false" - end - - def test_read_attribute_when_true - topic = topics(:first) - topic.approved = true - assert topic.approved?, "approved should be true" - topic.approved = "true" - assert topic.approved?, "approved should be true" - end - - def test_read_write_boolean_attribute - topic = Topic.new - # puts "" - # puts "New Topic" - # puts topic.inspect - topic.approved = "false" - # puts "Expecting false" - # puts topic.inspect - assert !topic.approved?, "approved should be false" - topic.approved = "false" - # puts "Expecting false" - # puts topic.inspect - assert !topic.approved?, "approved should be false" - topic.approved = "true" - # puts "Expecting true" - # puts topic.inspect - assert topic.approved?, "approved should be true" - topic.approved = "true" - # puts "Expecting true" - # puts topic.inspect - assert topic.approved?, "approved should be true" - # puts "" - end - - def test_query_attribute_string - [nil, "", " "].each do |value| - assert_equal false, Topic.new(:author_name => value).author_name? - end - - assert_equal true, Topic.new(:author_name => "Name").author_name? - end - - def test_query_attribute_number - [nil, 0, "0"].each do |value| - assert_equal false, Developer.new(:salary => value).salary? - end - - assert_equal true, Developer.new(:salary => 1).salary? - assert_equal true, Developer.new(:salary => "1").salary? - end - - def test_query_attribute_boolean - [nil, "", false, "false", "f", 0].each do |value| - assert_equal false, Topic.new(:approved => value).approved? - end - - [true, "true", "1", 1].each do |value| - assert_equal true, Topic.new(:approved => value).approved? - end - end - - def test_query_attribute_with_custom_fields - object = Company.find_by_sql(<<-SQL).first - SELECT c1.*, c2.ruby_type as string_value, c2.rating as int_value - FROM companies c1, companies c2 - WHERE c1.firm_id = c2.id - AND c1.id = 2 - SQL - - assert_equal "Firm", object.string_value - assert object.string_value? - - object.string_value = " " - assert !object.string_value? - - assert_equal 1, object.int_value.to_i - assert object.int_value? - - object.int_value = "0" - assert !object.int_value? - end - - def test_non_attribute_access_and_assignment - topic = Topic.new - assert !topic.respond_to?("mumbo") - assert_raise(NoMethodError) { topic.mumbo } - assert_raise(NoMethodError) { topic.mumbo = 5 } - end - def test_preserving_date_objects if current_adapter?(:SybaseAdapter) # Sybase ctlib does not (yet?) support the date type; use datetime instead. @@ -531,38 +283,6 @@ class BasicsTest < ActiveRecord::TestCase GUESSED_CLASSES.each(&:reset_table_name) end - def test_destroy_all - conditions = "author_name = 'Mary'" - topics_by_mary = Topic.all(:conditions => conditions, :order => 'id') - assert ! topics_by_mary.empty? - - assert_difference('Topic.count', -topics_by_mary.size) do - destroyed = Topic.destroy_all(conditions).sort_by(&:id) - assert_equal topics_by_mary, destroyed - assert destroyed.all? { |topic| topic.frozen? }, "destroyed topics should be frozen" - end - end - - def test_destroy_many - clients = Client.find([2, 3], :order => 'id') - - assert_difference('Client.count', -2) do - destroyed = Client.destroy([2, 3]).sort_by(&:id) - assert_equal clients, destroyed - assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen" - end - end - - def test_delete_many - original_count = Topic.count - Topic.delete(deleting = [1, 2]) - assert_equal original_count - deleting.size, Topic.count - end - - def test_boolean_attributes - assert ! Topic.find(1).approved? - assert Topic.find(2).approved? - end if current_adapter?(:MysqlAdapter) def test_update_all_with_order_and_limit @@ -570,63 +290,6 @@ class BasicsTest < ActiveRecord::TestCase end end - # Oracle UPDATE does not support ORDER BY - unless current_adapter?(:OracleAdapter) - def test_update_all_ignores_order_without_limit_from_association - author = authors(:david) - assert_nothing_raised do - assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all([ "body = ?", "bulk update!" ]) - end - end - - def test_update_all_with_order_and_limit_updates_subset_only - author = authors(:david) - assert_nothing_raised do - assert_equal 1, author.posts_sorted_by_id_limited.size - assert_equal 2, author.posts_sorted_by_id_limited.find(:all, :limit => 2).size - assert_equal 1, author.posts_sorted_by_id_limited.update_all([ "body = ?", "bulk update!" ]) - assert_equal "bulk update!", posts(:welcome).body - assert_not_equal "bulk update!", posts(:thinking).body - end - end - end - - def test_update_many - topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } } - updated = Topic.update(topic_data.keys, topic_data.values) - - assert_equal 2, updated.size - assert_equal "1 updated", Topic.find(1).content - assert_equal "2 updated", Topic.find(2).content - end - - def test_delete_all - assert Topic.count > 0 - - assert_equal Topic.count, Topic.delete_all - end - - def test_update_by_condition - Topic.update_all "content = 'bulk updated!'", ["approved = ?", true] - assert_equal "Have a nice day", Topic.find(1).content - assert_equal "bulk updated!", Topic.find(2).content - end - - def test_attribute_present - t = Topic.new - t.title = "hello there!" - t.written_on = Time.now - assert t.attribute_present?("title") - assert t.attribute_present?("written_on") - assert !t.attribute_present?("content") - end - - def test_attribute_keys_on_new_instance - t = Topic.new - assert_equal nil, t.title, "The topics table has a title column, so it should be nil" - assert_raise(NoMethodError) { t.title2 } - end - def test_null_fields assert_nil Topic.find(1).parent_id assert_nil Topic.create("title" => "Hey you").parent_id @@ -710,8 +373,6 @@ class BasicsTest < ActiveRecord::TestCase assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ] end - - def test_readonly_attributes assert_equal Set.new([ 'title' , 'comments_count' ]), ReadonlyTitlePost.readonly_attributes @@ -1313,49 +974,6 @@ class BasicsTest < ActiveRecord::TestCase end end - def test_increment_attribute - assert_equal 50, accounts(:signals37).credit_limit - accounts(:signals37).increment! :credit_limit - assert_equal 51, accounts(:signals37, :reload).credit_limit - - accounts(:signals37).increment(:credit_limit).increment!(:credit_limit) - assert_equal 53, accounts(:signals37, :reload).credit_limit - end - - def test_increment_nil_attribute - assert_nil topics(:first).parent_id - topics(:first).increment! :parent_id - assert_equal 1, topics(:first).parent_id - end - - def test_increment_attribute_by - assert_equal 50, accounts(:signals37).credit_limit - accounts(:signals37).increment! :credit_limit, 5 - assert_equal 55, accounts(:signals37, :reload).credit_limit - - accounts(:signals37).increment(:credit_limit, 1).increment!(:credit_limit, 3) - assert_equal 59, accounts(:signals37, :reload).credit_limit - end - - def test_decrement_attribute - assert_equal 50, accounts(:signals37).credit_limit - - accounts(:signals37).decrement!(:credit_limit) - assert_equal 49, accounts(:signals37, :reload).credit_limit - - accounts(:signals37).decrement(:credit_limit).decrement!(:credit_limit) - assert_equal 47, accounts(:signals37, :reload).credit_limit - end - - def test_decrement_attribute_by - assert_equal 50, accounts(:signals37).credit_limit - accounts(:signals37).decrement! :credit_limit, 5 - assert_equal 45, accounts(:signals37, :reload).credit_limit - - accounts(:signals37).decrement(:credit_limit, 1).decrement!(:credit_limit, 3) - assert_equal 41, accounts(:signals37, :reload).credit_limit - end - def test_toggle_attribute assert !topics(:first).approved? topics(:first).toggle!(:approved) @@ -1482,28 +1100,6 @@ class BasicsTest < ActiveRecord::TestCase assert_equal res6, res7 end - def test_clear_association_cache_stored - firm = Firm.find(1) - assert_kind_of Firm, firm - - firm.clear_association_cache - assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort - end - - def test_clear_association_cache_new_record - firm = Firm.new - client_stored = Client.find(3) - client_new = Client.new - client_new.name = "The Joneses" - clients = [ client_stored, client_new ] - - firm.clients << clients - assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set - - firm.clear_association_cache - assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set - end - def test_interpolate_sql assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo@bar') } assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar) baz') } @@ -1703,134 +1299,6 @@ class BasicsTest < ActiveRecord::TestCase assert_no_queries { assert true } end - def test_to_xml - xml = REXML::Document.new(topics(:first).to_xml(:indent => 0)) - bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema - written_on_in_current_timezone = topics(:first).written_on.xmlschema - last_read_in_current_timezone = topics(:first).last_read.xmlschema - - assert_equal "topic", xml.root.name - assert_equal "The First Topic" , xml.elements["//title"].text - assert_equal "David" , xml.elements["//author-name"].text - assert_match "Have a nice day", xml.elements["//content"].text - - assert_equal "1", xml.elements["//id"].text - assert_equal "integer" , xml.elements["//id"].attributes['type'] - - assert_equal "1", xml.elements["//replies-count"].text - assert_equal "integer" , xml.elements["//replies-count"].attributes['type'] - - assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text - assert_equal "datetime" , xml.elements["//written-on"].attributes['type'] - - assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text - - assert_equal nil, xml.elements["//parent-id"].text - assert_equal "integer", xml.elements["//parent-id"].attributes['type'] - assert_equal "true", xml.elements["//parent-id"].attributes['nil'] - - if current_adapter?(:SybaseAdapter) - assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text - assert_equal "datetime" , xml.elements["//last-read"].attributes['type'] - else - # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb) - assert_equal "2004-04-15", xml.elements["//last-read"].text - assert_equal "date" , xml.elements["//last-read"].attributes['type'] - end - - # Oracle and DB2 don't have true boolean or time-only fields - unless current_adapter?(:OracleAdapter, :DB2Adapter) - assert_equal "false", xml.elements["//approved"].text - assert_equal "boolean" , xml.elements["//approved"].attributes['type'] - - assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text - assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type'] - end - end - - def test_to_xml_skipping_attributes - xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count]) - assert_equal "<topic>", xml.first(7) - assert !xml.include?(%(<title>The First Topic</title>)) - assert xml.include?(%(<author-name>David</author-name>)) - - xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count]) - assert !xml.include?(%(<title>The First Topic</title>)) - assert !xml.include?(%(<author-name>David</author-name>)) - end - - def test_to_xml_including_has_many_association - xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count) - assert_equal "<topic>", xml.first(7) - assert xml.include?(%(<replies type="array"><reply>)) - assert xml.include?(%(<title>The Second Topic of the day</title>)) - end - - def test_array_to_xml_including_has_many_association - xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies) - assert xml.include?(%(<replies type="array"><reply>)) - end - - def test_array_to_xml_including_methods - xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ]) - assert xml.include?(%(<topic-id type="integer">#{topics(:first).topic_id}</topic-id>)), xml - assert xml.include?(%(<topic-id type="integer">#{topics(:second).topic_id}</topic-id>)), xml - end - - def test_array_to_xml_including_has_one_association - xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account) - assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true)) - assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true)) - end - - def test_array_to_xml_including_belongs_to_association - xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm) - assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true)) - assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true)) - assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true)) - end - - def test_to_xml_including_belongs_to_association - xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) - assert !xml.include?("<firm>") - - xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) - assert xml.include?("<firm>") - end - - def test_to_xml_including_multiple_associations - xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ]) - assert_equal "<firm>", xml.first(6) - assert xml.include?(%(<account>)) - assert xml.include?(%(<clients type="array"><client>)) - end - - def test_to_xml_including_multiple_associations_with_options - xml = companies(:first_firm).to_xml( - :indent => 0, :skip_instruct => true, - :include => { :clients => { :only => :name } } - ) - - assert_equal "<firm>", xml.first(6) - assert xml.include?(%(<client><name>Summit</name></client>)) - assert xml.include?(%(<clients type="array"><client>)) - end - - def test_to_xml_including_methods - xml = Company.new.to_xml(:methods => :arbitrary_method, :skip_instruct => true) - assert_equal "<company>", xml.first(9) - assert xml.include?(%(<arbitrary-method>I am Jack's profound disappointment</arbitrary-method>)) - end - - def test_to_xml_with_block - value = "Rockin' the block" - xml = Company.new.to_xml(:skip_instruct => true) do |_xml| - _xml.tag! "arbitrary-element", value - end - assert_equal "<company>", xml.first(9) - assert xml.include?(%(<arbitrary-element>#{value}</arbitrary-element>)) - end - def test_to_param_should_return_string assert_kind_of String, Client.find(:first).to_param end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index a107c1a474..4f3e43d77d 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -693,6 +693,14 @@ class FinderTest < ActiveRecord::TestCase assert_equal [], Topic.find_all_by_title("The First Topic!!") end + def test_find_all_by_one_attribute_which_is_a_symbol + topics = Topic.find_all_by_content("Have a nice day".to_sym) + assert_equal 2, topics.size + assert topics.include?(topics(:first)) + + assert_equal [], Topic.find_all_by_title("The First Topic!!") + end + def test_find_all_by_one_attribute_that_is_an_aggregate balance = customers(:david).balance assert_kind_of Money, balance diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 7c037b20c5..c42dda2ccb 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -478,4 +478,10 @@ class DynamicScopeTest < ActiveRecord::TestCase assert_equal Post.scoped_by_author_id(1).find(1), Post.find(1) assert_equal Post.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, Post.find(:first, :conditions => { :author_id => 1, :title => "Welcome to the weblog"}) end + + def test_dynamic_scope_should_create_methods_after_hitting_method_missing + assert Developer.methods.grep(/scoped_by_created_at/).blank? + Developer.scoped_by_created_at(nil) + assert Developer.methods.grep(/scoped_by_created_at/).present? + end end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 1cc3a337c3..d7666b19f6 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -19,6 +19,119 @@ class PersistencesTest < ActiveRecord::TestCase fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts, :minivans + # Oracle UPDATE does not support ORDER BY + unless current_adapter?(:OracleAdapter) + def test_update_all_ignores_order_without_limit_from_association + author = authors(:david) + assert_nothing_raised do + assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all([ "body = ?", "bulk update!" ]) + end + end + + def test_update_all_with_order_and_limit_updates_subset_only + author = authors(:david) + assert_nothing_raised do + assert_equal 1, author.posts_sorted_by_id_limited.size + assert_equal 2, author.posts_sorted_by_id_limited.find(:all, :limit => 2).size + assert_equal 1, author.posts_sorted_by_id_limited.update_all([ "body = ?", "bulk update!" ]) + assert_equal "bulk update!", posts(:welcome).body + assert_not_equal "bulk update!", posts(:thinking).body + end + end + end + + def test_update_many + topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } } + updated = Topic.update(topic_data.keys, topic_data.values) + + assert_equal 2, updated.size + assert_equal "1 updated", Topic.find(1).content + assert_equal "2 updated", Topic.find(2).content + end + + def test_delete_all + assert Topic.count > 0 + + assert_equal Topic.count, Topic.delete_all + end + + def test_update_by_condition + Topic.update_all "content = 'bulk updated!'", ["approved = ?", true] + assert_equal "Have a nice day", Topic.find(1).content + assert_equal "bulk updated!", Topic.find(2).content + end + + def test_increment_attribute + assert_equal 50, accounts(:signals37).credit_limit + accounts(:signals37).increment! :credit_limit + assert_equal 51, accounts(:signals37, :reload).credit_limit + + accounts(:signals37).increment(:credit_limit).increment!(:credit_limit) + assert_equal 53, accounts(:signals37, :reload).credit_limit + end + + def test_increment_nil_attribute + assert_nil topics(:first).parent_id + topics(:first).increment! :parent_id + assert_equal 1, topics(:first).parent_id + end + + def test_increment_attribute_by + assert_equal 50, accounts(:signals37).credit_limit + accounts(:signals37).increment! :credit_limit, 5 + assert_equal 55, accounts(:signals37, :reload).credit_limit + + accounts(:signals37).increment(:credit_limit, 1).increment!(:credit_limit, 3) + assert_equal 59, accounts(:signals37, :reload).credit_limit + end + + def test_destroy_all + conditions = "author_name = 'Mary'" + topics_by_mary = Topic.all(:conditions => conditions, :order => 'id') + assert ! topics_by_mary.empty? + + assert_difference('Topic.count', -topics_by_mary.size) do + destroyed = Topic.destroy_all(conditions).sort_by(&:id) + assert_equal topics_by_mary, destroyed + assert destroyed.all? { |topic| topic.frozen? }, "destroyed topics should be frozen" + end + end + + def test_destroy_many + clients = Client.find([2, 3], :order => 'id') + + assert_difference('Client.count', -2) do + destroyed = Client.destroy([2, 3]).sort_by(&:id) + assert_equal clients, destroyed + assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen" + end + end + + def test_delete_many + original_count = Topic.count + Topic.delete(deleting = [1, 2]) + assert_equal original_count - deleting.size, Topic.count + end + + def test_decrement_attribute + assert_equal 50, accounts(:signals37).credit_limit + + accounts(:signals37).decrement!(:credit_limit) + assert_equal 49, accounts(:signals37, :reload).credit_limit + + accounts(:signals37).decrement(:credit_limit).decrement!(:credit_limit) + assert_equal 47, accounts(:signals37, :reload).credit_limit + end + + def test_decrement_attribute_by + assert_equal 50, accounts(:signals37).credit_limit + accounts(:signals37).decrement! :credit_limit, 5 + assert_equal 45, accounts(:signals37, :reload).credit_limit + + accounts(:signals37).decrement(:credit_limit, 1).decrement!(:credit_limit, 3) + assert_equal 41, accounts(:signals37, :reload).credit_limit + end + def test_create topic = Topic.new topic.title = "New Topic" diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb index 8c385af97c..dab81530af 100644 --- a/activerecord/test/cases/serialization_test.rb +++ b/activerecord/test/cases/serialization_test.rb @@ -1,7 +1,13 @@ require "cases/helper" require 'models/contact' +require 'models/topic' +require 'models/reply' +require 'models/company' class SerializationTest < ActiveRecord::TestCase + + fixtures :topics, :companies, :accounts + FORMATS = [ :xml, :json ] def setup @@ -17,6 +23,134 @@ class SerializationTest < ActiveRecord::TestCase @contact = Contact.new(@contact_attributes) end + def test_to_xml + xml = REXML::Document.new(topics(:first).to_xml(:indent => 0)) + bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema + written_on_in_current_timezone = topics(:first).written_on.xmlschema + last_read_in_current_timezone = topics(:first).last_read.xmlschema + + assert_equal "topic", xml.root.name + assert_equal "The First Topic" , xml.elements["//title"].text + assert_equal "David" , xml.elements["//author-name"].text + assert_match "Have a nice day", xml.elements["//content"].text + + assert_equal "1", xml.elements["//id"].text + assert_equal "integer" , xml.elements["//id"].attributes['type'] + + assert_equal "1", xml.elements["//replies-count"].text + assert_equal "integer" , xml.elements["//replies-count"].attributes['type'] + + assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text + assert_equal "datetime" , xml.elements["//written-on"].attributes['type'] + + assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text + + assert_equal nil, xml.elements["//parent-id"].text + assert_equal "integer", xml.elements["//parent-id"].attributes['type'] + assert_equal "true", xml.elements["//parent-id"].attributes['nil'] + + if current_adapter?(:SybaseAdapter) + assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text + assert_equal "datetime" , xml.elements["//last-read"].attributes['type'] + else + # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb) + assert_equal "2004-04-15", xml.elements["//last-read"].text + assert_equal "date" , xml.elements["//last-read"].attributes['type'] + end + + # Oracle and DB2 don't have true boolean or time-only fields + unless current_adapter?(:OracleAdapter, :DB2Adapter) + assert_equal "false", xml.elements["//approved"].text + assert_equal "boolean" , xml.elements["//approved"].attributes['type'] + + assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text + assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type'] + end + end + + def test_to_xml_skipping_attributes + xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count]) + assert_equal "<topic>", xml.first(7) + assert !xml.include?(%(<title>The First Topic</title>)) + assert xml.include?(%(<author-name>David</author-name>)) + + xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count]) + assert !xml.include?(%(<title>The First Topic</title>)) + assert !xml.include?(%(<author-name>David</author-name>)) + end + + def test_to_xml_including_has_many_association + xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count) + assert_equal "<topic>", xml.first(7) + assert xml.include?(%(<replies type="array"><reply>)) + assert xml.include?(%(<title>The Second Topic of the day</title>)) + end + + def test_array_to_xml_including_has_many_association + xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies) + assert xml.include?(%(<replies type="array"><reply>)) + end + + def test_array_to_xml_including_methods + xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ]) + assert xml.include?(%(<topic-id type="integer">#{topics(:first).topic_id}</topic-id>)), xml + assert xml.include?(%(<topic-id type="integer">#{topics(:second).topic_id}</topic-id>)), xml + end + + def test_array_to_xml_including_has_one_association + xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account) + assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true)) + assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true)) + end + + def test_array_to_xml_including_belongs_to_association + xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm) + assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true)) + assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true)) + assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true)) + end + + def test_to_xml_including_belongs_to_association + xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) + assert !xml.include?("<firm>") + + xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) + assert xml.include?("<firm>") + end + + def test_to_xml_including_multiple_associations + xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ]) + assert_equal "<firm>", xml.first(6) + assert xml.include?(%(<account>)) + assert xml.include?(%(<clients type="array"><client>)) + end + + def test_to_xml_including_multiple_associations_with_options + xml = companies(:first_firm).to_xml( + :indent => 0, :skip_instruct => true, + :include => { :clients => { :only => :name } } + ) + + assert_equal "<firm>", xml.first(6) + assert xml.include?(%(<client><name>Summit</name></client>)) + assert xml.include?(%(<clients type="array"><client>)) + end + + def test_to_xml_including_methods + xml = Company.new.to_xml(:methods => :arbitrary_method, :skip_instruct => true) + assert_equal "<company>", xml.first(9) + assert xml.include?(%(<arbitrary-method>I am Jack's profound disappointment</arbitrary-method>)) + end + + def test_to_xml_with_block + value = "Rockin' the block" + xml = Company.new.to_xml(:skip_instruct => true) do |_xml| + _xml.tag! "arbitrary-element", value + end + assert_equal "<company>", xml.first(9) + assert xml.include?(%(<arbitrary-element>#{value}</arbitrary-element>)) + end + def test_serialize_should_be_reversible for format in FORMATS @serialized = Contact.new.send("to_#{format}") diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 5e0c5b6e98..7290c971f9 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -419,7 +419,10 @@ module ActiveSupport @_keyed_callbacks ||= {} @_keyed_callbacks[name] ||= begin str = send("_#{kind}_callbacks").compile(name, object) - class_eval "def #{name}() #{str} end", __FILE__, __LINE__ + class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def #{name}() #{str} end + protected :#{name} + RUBY_EVAL true end end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 2b80bd214f..1b93eac7ee 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -276,14 +276,22 @@ module ActiveSupport #:nodoc: end def depend_on(file_name, swallow_load_errors = false, message = "No such file to load -- %s.rb") - path = search_for_file(file_name) - require_or_load(path || file_name) - rescue LoadError => load_error - unless swallow_load_errors - if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1] - raise LoadError.new(message % file_name).copy_blame!(load_error) + #path = search_for_file(file_name) + require_or_load(file_name) + rescue LoadError + begin + if path = search_for_file(file_name) + require_or_load(path) + else + raise + end + rescue LoadError => load_error + unless swallow_load_errors + if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1] + raise LoadError.new(message % file_name).copy_blame!(load_error) + end + raise end - raise end end diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 19d7935211..e8506f5222 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -171,22 +171,29 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase end def test_advance - assert_equal DateTime.civil(2006,2,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 1) - assert_equal DateTime.civil(2005,6,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:months => 4) - assert_equal DateTime.civil(2005,3,21,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:weeks => 3) - assert_equal DateTime.civil(2005,3,5,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:days => 5) - assert_equal DateTime.civil(2012,9,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 7) - assert_equal DateTime.civil(2013,10,3,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :days => 5) + assert_equal DateTime.civil(2006,2,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 1) + assert_equal DateTime.civil(2005,6,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:months => 4) + assert_equal DateTime.civil(2005,3,21,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:weeks => 3) + assert_equal DateTime.civil(2005,3,5,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:days => 5) + assert_equal DateTime.civil(2012,9,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 7) + assert_equal DateTime.civil(2013,10,3,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :days => 5) assert_equal DateTime.civil(2013,10,17,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5) assert_equal DateTime.civil(2001,12,27,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => -3, :months => -2, :days => -1) - assert_equal DateTime.civil(2005,2,28,15,15,10), DateTime.civil(2004,2,29,15,15,10).advance(:years => 1) #leap day plus one year - assert_equal DateTime.civil(2005,2,28,20,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:hours => 5) - assert_equal DateTime.civil(2005,2,28,15,22,10), DateTime.civil(2005,2,28,15,15,10).advance(:minutes => 7) - assert_equal DateTime.civil(2005,2,28,15,15,19), DateTime.civil(2005,2,28,15,15,10).advance(:seconds => 9) - assert_equal DateTime.civil(2005,2,28,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(:hours => 5, :minutes => 7, :seconds => 9) - assert_equal DateTime.civil(2005,2,28,10,8,1), DateTime.civil(2005,2,28,15,15,10).advance(:hours => -5, :minutes => -7, :seconds => -9) + assert_equal DateTime.civil(2005,2,28,15,15,10), DateTime.civil(2004,2,29,15,15,10).advance(:years => 1) #leap day plus one year + assert_equal DateTime.civil(2005,2,28,20,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:hours => 5) + assert_equal DateTime.civil(2005,2,28,15,22,10), DateTime.civil(2005,2,28,15,15,10).advance(:minutes => 7) + assert_equal DateTime.civil(2005,2,28,15,15,19), DateTime.civil(2005,2,28,15,15,10).advance(:seconds => 9) + assert_equal DateTime.civil(2005,2,28,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(:hours => 5, :minutes => 7, :seconds => 9) + assert_equal DateTime.civil(2005,2,28,10,8,1), DateTime.civil(2005,2,28,15,15,10).advance(:hours => -5, :minutes => -7, :seconds => -9) assert_equal DateTime.civil(2013,10,17,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9) + end + def test_advanced_processes_first_the_date_deltas_and_then_the_time_deltas + # If the time deltas were processed first, the following datetimes would be advanced to 2010/04/01 instead. + assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23, 59, 59).advance(:months => 1, :seconds => 1) + assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23, 59).advance(:months => 1, :minutes => 1) + assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23).advance(:months => 1, :hours => 1) + assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 22, 58, 59).advance(:months => 1, :hours => 1, :minutes => 1, :seconds => 1) end def test_next_week diff --git a/railties/lib/rails/rack/log_tailer.rb b/railties/lib/rails/rack/log_tailer.rb index 3fa45156c3..011ac6cecc 100644 --- a/railties/lib/rails/rack/log_tailer.rb +++ b/railties/lib/rails/rack/log_tailer.rb @@ -6,7 +6,6 @@ module Rails path = Pathname.new(log || "#{File.expand_path(Rails.root)}/log/#{Rails.env}.log").cleanpath @cursor = ::File.size(path) - @last_checked = Time.now.to_f @file = ::File.open(path, 'r') end @@ -20,11 +19,9 @@ module Rails def tail! @file.seek @cursor - mod = @file.mtime.to_f - if mod > @last_checked + if !@file.eof? contents = @file.read - @last_checked = mod - @cursor += contents.size + @cursor = @file.tell $stdout.print contents end end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 21725a380c..08cfb585f9 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -114,7 +114,7 @@ class AppGeneratorTest < Rails::Generators::TestCase Rails.application.config.root = app_moved_root Rails.application.class.stubs(:name).returns("Myapp") - Rails.application.stubs(:instance_of?).returns(Rails::Application) + Rails.application.stubs(:is_a?).returns(Rails::Application) FileUtils.mv(app_root, app_moved_root) |