From b7f1735feaef93abc2130d55b0bc575221587539 Mon Sep 17 00:00:00 2001 From: Guillermo Iguaran Date: Fri, 9 Sep 2011 12:31:11 -0500 Subject: Add missing require in base_test.rb, fixes isolated test --- activerecord/test/cases/base_test.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activerecord') diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 63879259af..cb92f79e0e 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -22,6 +22,7 @@ require 'models/person' require 'models/edge' require 'models/joke' require 'models/bulb' +require 'models/bird' require 'rexml/document' require 'active_support/core_ext/exception' require 'bcrypt' -- cgit v1.2.3 From fb73be84695d700f11673ba92f29dfeef3659a37 Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Sat, 10 Sep 2011 00:11:35 +0530 Subject: Not used variables removed. Warnings removed. --- activerecord/test/cases/relations_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index b491d63047..0f50ac9a2b 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -938,7 +938,7 @@ class RelationTest < ActiveRecord::TestCase def test_first_or_create_bang_with_invalid_block assert_raise(ActiveRecord::RecordInvalid) do - parrot = Bird.where(:color => 'green').first_or_create! { |bird| bird.pirate_id = 1 } + Bird.where(:color => 'green').first_or_create! { |bird| bird.pirate_id = 1 } end end -- cgit v1.2.3 From 4bcacc80667a548664b5ca09d85074c6c383748e Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sun, 11 Sep 2011 17:05:16 -0700 Subject: Merge pull request #2936 from joelmoss/migration_status db:migrate:status not looking at all migration paths --- activerecord/lib/active_record/railties/databases.rake | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 13c41350fb..b3316fd1a2 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -203,11 +203,13 @@ db_namespace = namespace :db do end db_list = ActiveRecord::Base.connection.select_values("SELECT version FROM #{ActiveRecord::Migrator.schema_migrations_table_name}") file_list = [] - Dir.foreach(File.join(Rails.root, 'db', 'migrate')) do |file| - # only files matching "20091231235959_some_name.rb" pattern - if match_data = /^(\d{14})_(.+)\.rb$/.match(file) - status = db_list.delete(match_data[1]) ? 'up' : 'down' - file_list << [status, match_data[1], match_data[2].humanize] + ActiveRecord::Migrator.migrations_paths.each do |path| + Dir.foreach(path) do |file| + # only files matching "20091231235959_some_name.rb" pattern + if match_data = /^(\d{14})_(.+)\.rb$/.match(file) + status = db_list.delete(match_data[1]) ? 'up' : 'down' + file_list << [status, match_data[1], match_data[2].humanize] + end end end db_list.map! do |version| -- cgit v1.2.3 From 8d59e0b2633c26f9de8942a2d676afe39b0ee3f8 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 4 Sep 2011 22:05:53 +0100 Subject: Alias id= if necessary, rather than relying on method_missing --- activerecord/lib/active_record/attribute_methods/write.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index c77a3ac145..5621b44c8c 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -17,6 +17,10 @@ module ActiveRecord write_attribute(attr_name, new_value) end end + + if attr_name == primary_key && attr_name != "id" + generated_attribute_methods.module_eval("alias :id= :'#{primary_key}='") + end end end -- cgit v1.2.3 From 99bd6b53da9555450afb1e050324007868e0768c Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Fri, 9 Sep 2011 09:08:27 +0100 Subject: Add deprecation for doing `attribute_method_suffix ''` --- activerecord/lib/active_record/attribute_methods/read.rb | 2 -- 1 file changed, 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 9a50a20fbc..4174e4da09 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -6,8 +6,6 @@ module ActiveRecord ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date] included do - attribute_method_suffix "" - cattr_accessor :attribute_types_cached_by_default, :instance_writer => false self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT -- cgit v1.2.3 From 3386a089eff05a8a990ca56909c9a427f4f2fe25 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sat, 10 Sep 2011 09:36:27 +0100 Subject: Fix warnings. Make sure we don't redefine an already-defined attribute method. --- activerecord/lib/active_record/attribute_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index d0687ed0b6..1937ac4272 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -36,7 +36,7 @@ module ActiveRecord @@_defined_activerecord_methods ||= defined_activerecord_methods raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name) - @_defined_class_methods.include?(method_name) + @_defined_class_methods.include?(method_name) || generated_attribute_methods.method_defined?(method_name) end def defined_activerecord_methods -- cgit v1.2.3 From 8667d3aeb64dd8dba463ace364534326411bb46c Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sat, 10 Sep 2011 09:41:42 +0100 Subject: Make protected method public so we avoid method_missing. --- activerecord/test/models/topic.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index 6440dbe8ab..fe424e61b2 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -78,11 +78,12 @@ class Topic < ActiveRecord::Base after_initialize :set_email_address + def approved=(val) + @custom_approved = val + write_attribute(:approved, val) + end + protected - def approved=(val) - @custom_approved = val - write_attribute(:approved, val) - end def default_written_on self.written_on = Time.now unless attribute_present?("written_on") -- cgit v1.2.3 From 50d395f96ea05da1e02459688e94bff5872c307b Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sat, 10 Sep 2011 21:10:01 +0100 Subject: Raise error when using write_attribute with a non-existent attribute. Previously we would just silently write the attribute. This can lead to subtle bugs (for example, see the change in AutosaveAssociation where a through association would wrongly gain an attribute. Also, ensuring that we never gain any new attributes after initialization will allow me to reduce our dependence on method_missing. --- .../lib/active_record/attribute_methods/write.rb | 10 +++++++--- activerecord/lib/active_record/autosave_association.rb | 5 ++++- activerecord/lib/active_record/persistence.rb | 2 +- activerecord/lib/active_record/timestamp.rb | 4 +++- activerecord/test/cases/persistence_test.rb | 17 +++++++++++------ activerecord/test/cases/serialization_test.rb | 13 +++++++------ activerecord/test/models/contact.rb | 13 +++++++------ activerecord/test/schema/schema.rb | 8 +++++++- 8 files changed, 47 insertions(+), 25 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 5621b44c8c..e9cdb130db 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -28,12 +28,16 @@ module ActiveRecord # for fixnum and float columns are turned into +nil+. def write_attribute(attr_name, value) attr_name = attr_name.to_s - attr_name = self.class.primary_key if attr_name == 'id' + attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key @attributes_cache.delete(attr_name) - if (column = column_for_attribute(attr_name)) && column.number? + column = column_for_attribute(attr_name) + + if column && column.number? @attributes[attr_name] = convert_number_column_value(value) - else + elsif column || @attributes.has_key?(attr_name) @attributes[attr_name] = value + else + raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'" end end alias_method :raw_write_attribute, :write_attribute diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 085fdba639..056170d82a 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -370,7 +370,10 @@ module ActiveRecord else key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id if autosave != false && (new_record? || record.new_record? || record[reflection.foreign_key] != key || autosave) - record[reflection.foreign_key] = key + unless reflection.through_reflection + record[reflection.foreign_key] = key + end + saved = record.save(:validate => !autosave) raise ActiveRecord::Rollback if !saved && autosave saved diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 2dac9ea0fb..5e65e46a7d 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -314,7 +314,7 @@ module ActiveRecord new_id = self.class.unscoped.insert attributes_values - self.id ||= new_id + self.id ||= new_id if self.class.primary_key IdentityMap.add(self) if IdentityMap.enabled? @new_record = false diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index cccac6ffd7..4d5e469a7f 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -48,7 +48,9 @@ module ActiveRecord current_time = current_time_from_proper_timezone all_timestamp_attributes.each do |column| - write_attribute(column.to_s, current_time) if respond_to?(column) && self.send(column).nil? + if respond_to?(column) && respond_to?("#{column}=") && self.send(column).nil? + write_attribute(column.to_s, current_time) + end end end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 9cd07fa8a5..adfd8e83a1 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -202,9 +202,12 @@ class PersistencesTest < ActiveRecord::TestCase end def test_create_columns_not_equal_attributes - topic = Topic.new - topic.title = 'Another New Topic' - topic.send :write_attribute, 'does_not_exist', 'test' + topic = Topic.allocate.init_with( + 'attributes' => { + 'title' => 'Another New Topic', + 'does_not_exist' => 'test' + } + ) assert_nothing_raised { topic.save } end @@ -249,9 +252,11 @@ class PersistencesTest < ActiveRecord::TestCase topic.title = "Still another topic" topic.save - topicReloaded = Topic.find(topic.id) - topicReloaded.title = "A New Topic" - topicReloaded.send :write_attribute, 'does_not_exist', 'test' + topicReloaded = Topic.allocate + topicReloaded.init_with( + 'attributes' => topic.attributes.merge('does_not_exist' => 'test') + ) + topicReloaded.title = 'A New Topic' assert_nothing_raised { topicReloaded.save } end diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb index 382d659289..61b04b3e37 100644 --- a/activerecord/test/cases/serialization_test.rb +++ b/activerecord/test/cases/serialization_test.rb @@ -7,12 +7,13 @@ class SerializationTest < ActiveRecord::TestCase def setup @contact_attributes = { - :name => 'aaron stack', - :age => 25, - :avatar => 'binarydata', - :created_at => Time.utc(2006, 8, 1), - :awesome => false, - :preferences => { :gem => 'ruby' } + :name => 'aaron stack', + :age => 25, + :avatar => 'binarydata', + :created_at => Time.utc(2006, 8, 1), + :awesome => false, + :preferences => { :gem => 'ruby' }, + :alternative_id => nil } end diff --git a/activerecord/test/models/contact.rb b/activerecord/test/models/contact.rb index e081eee661..3d15c7fbed 100644 --- a/activerecord/test/models/contact.rb +++ b/activerecord/test/models/contact.rb @@ -11,12 +11,13 @@ class Contact < ActiveRecord::Base connection.merge_column('contacts', name, sql_type, options) end - column :name, :string - column :age, :integer - column :avatar, :binary - column :created_at, :datetime - column :awesome, :boolean - column :preferences, :string + column :name, :string + column :age, :integer + column :avatar, :binary + column :created_at, :datetime + column :awesome, :boolean + column :preferences, :string + column :alternative_id, :integer serialize :preferences diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 64e0452100..9d5ad16a3c 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -47,6 +47,7 @@ ActiveRecord::Schema.define do create_table :audit_logs, :force => true do |t| t.column :message, :string, :null=>false t.column :developer_id, :integer, :null=>false + t.integer :unvalidated_developer_id end create_table :authors, :force => true do |t| @@ -156,6 +157,7 @@ ActiveRecord::Schema.define do t.string :type t.integer :taggings_count, :default => 0 t.integer :children_count, :default => 0 + t.integer :parent_id end create_table :companies, :force => true do |t| @@ -461,6 +463,7 @@ ActiveRecord::Schema.define do create_table :pirates, :force => true do |t| t.column :catchphrase, :string t.column :parrot_id, :integer + t.integer :non_validated_parrot_id t.column :created_on, :datetime t.column :updated_on, :datetime end @@ -529,6 +532,7 @@ ActiveRecord::Schema.define do create_table :ships, :force => true do |t| t.string :name t.integer :pirate_id + t.integer :update_only_pirate_id t.datetime :created_at t.datetime :created_on t.datetime :updated_at @@ -663,7 +667,9 @@ ActiveRecord::Schema.define do t.string :description t.integer :man_id t.integer :polymorphic_man_id - t.string :polymorphic_man_type + t.string :polymorphic_man_type + t.integer :horrible_polymorphic_man_id + t.string :horrible_polymorphic_man_type end create_table :interests, :force => true do |t| -- cgit v1.2.3 From eecfa84a9065da2b1bd1e02f37e8653a2825c624 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 11 Sep 2011 14:48:40 +0100 Subject: Always generate attribute methods on the base class. This fixes a situation I encountered where a subclass would cache the name of a generated attribute method in @_defined_class_methods. Then, when the superclass has it's attribute methods undefined, the subclass would always have to dispatch through method_missing, because the presence of the attribute in @_defined_class_methods would mean that it is never generated again, even if undefine_attribute_methods is called on the subclass. There various other confusing edge cases like this. STI classes share columns, so let's just keep all the attribute method generation state isolated to the base class. --- .../lib/active_record/attribute_methods.rb | 23 +++++++++++++++++----- activerecord/lib/active_record/base.rb | 2 +- .../test/cases/attribute_methods/read_test.rb | 1 + activerecord/test/cases/attribute_methods_test.rb | 16 +++++++++++++++ 4 files changed, 36 insertions(+), 6 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 1937ac4272..a8cb89fb65 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -11,17 +11,30 @@ module ActiveRecord # accessors, mutators and query methods. def define_attribute_methods return if attribute_methods_generated? - super(column_names) - @attribute_methods_generated = true + + if base_class == self + super(column_names) + @attribute_methods_generated = true + else + base_class.define_attribute_methods + end end def attribute_methods_generated? - @attribute_methods_generated ||= false + if base_class == self + @attribute_methods_generated ||= false + else + base_class.attribute_methods_generated? + end end def undefine_attribute_methods(*args) - super - @attribute_methods_generated = false + if base_class == self + super + @attribute_methods_generated = false + else + base_class.undefine_attribute_methods(*args) + end end # Checks whether the method is defined in the model or any of its subclasses diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 2979ad1cb3..92f80c6eaa 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1332,7 +1332,7 @@ MSG # Returns the class descending directly from ActiveRecord::Base or an # abstract class, if any, in the inheritance hierarchy. def class_of_active_record_descendant(klass) - if klass.superclass == Base || klass.superclass.abstract_class? + if klass == Base || klass.superclass == Base || klass.superclass.abstract_class? klass elsif klass.superclass.nil? raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord" diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb index 3641031d12..e03ed33591 100644 --- a/activerecord/test/cases/attribute_methods/read_test.rb +++ b/activerecord/test/cases/attribute_methods/read_test.rb @@ -35,6 +35,7 @@ module ActiveRecord end def self.serialized_attributes; {}; end + def self.base_class; self; end end end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index dbf5a1ba76..9815c4ba87 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -659,6 +659,22 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal %w(preferences), Contact.serialized_attributes.keys end + def test_instance_method_should_be_defined_on_the_base_class + subklass = Class.new(Topic) + + Topic.define_attribute_methods + + instance = subklass.new + instance.id = 5 + assert_equal 5, instance.id + assert subklass.method_defined?(:id), "subklass is missing id method" + + Topic.undefine_attribute_methods + + assert_equal 5, instance.id + assert subklass.method_defined?(:id), "subklass is missing id method" + end + private def cached_columns @cached_columns ||= (time_related_columns_on_topic + serialized_columns_on_topic).map(&:name) -- cgit v1.2.3 From cf115d2f8ef48764e095aa453f729b60705088f1 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 11 Sep 2011 16:07:24 +0100 Subject: Reset column info when messing with columns. We are subclassing Session here, but messing with the columns will affect the attribute methods defined on the Session superclass, and therefore other tests, unless we properly isolate it by resetting column info before and after the test run. --- activerecord/test/cases/session_store/session_test.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'activerecord') diff --git a/activerecord/test/cases/session_store/session_test.rb b/activerecord/test/cases/session_store/session_test.rb index 669c0b7b4d..258cee7aba 100644 --- a/activerecord/test/cases/session_store/session_test.rb +++ b/activerecord/test/cases/session_store/session_test.rb @@ -36,6 +36,7 @@ module ActiveRecord end def test_find_by_sess_id_compat + Session.reset_column_information klass = Class.new(Session) do def self.session_id_column 'sessid' @@ -53,6 +54,7 @@ module ActiveRecord assert_equal session.sessid, found.session_id ensure klass.drop_table! + Session.reset_column_information end def test_find_by_session_id -- cgit v1.2.3 From ac687ed651773fccecbc22cd6d8b07d5439ceb76 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 12 Sep 2011 22:12:12 +0100 Subject: Let Ruby deal with method visibility. Check respond_to_without_attributes? in method_missing. If there is any method that responds (even private), let super handle it and raise NoMethodError if necessary. --- activerecord/lib/active_record/attribute_methods.rb | 17 ++++++++++------- activerecord/test/cases/attribute_methods_test.rb | 6 +++--- 2 files changed, 13 insertions(+), 10 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index a8cb89fb65..8d7eb4a48d 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -61,14 +61,17 @@ module ActiveRecord end end - def method_missing(method_id, *args, &block) - # If we haven't generated any methods yet, generate them, then - # see if we've created the method we're looking for. - if !self.class.attribute_methods_generated? + # If we haven't generated any methods yet, generate them, then + # see if we've created the method we're looking for. + def method_missing(method, *args, &block) + unless self.class.attribute_methods_generated? self.class.define_attribute_methods - method_name = method_id.to_s - guard_private_attribute_method!(method_name, args) - send(method_id, *args, &block) + + if respond_to_without_attributes?(method) + send(method, *args, &block) + else + super + end else super end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 9815c4ba87..5ae3713e73 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -608,7 +608,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = @target.new(:title => "The pros and cons of programming naked.") assert !topic.respond_to?(:title) exception = assert_raise(NoMethodError) { topic.title } - assert_match %r(^Attempt to call private method), exception.message + assert exception.message.include?("private method") assert_equal "I'm private", topic.send(:title) end @@ -618,7 +618,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = @target.new assert !topic.respond_to?(:title=) exception = assert_raise(NoMethodError) { topic.title = "Pants"} - assert_match %r(^Attempt to call private method), exception.message + assert exception.message.include?("private method") topic.send(:title=, "Very large pants") end @@ -628,7 +628,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = @target.new(:title => "Isaac Newton's pants") assert !topic.respond_to?(:title?) exception = assert_raise(NoMethodError) { topic.title? } - assert_match %r(^Attempt to call private method), exception.message + assert exception.message.include?("private method") assert topic.send(:title?) end -- cgit v1.2.3 From 1a421ceccb25b15c87e97567fff573c941aa3ab3 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 12 Sep 2011 23:58:37 +0100 Subject: Deprecate using method_missing for attributes that are columns. This shouldn't ever happen unless people are doing something particularly weird, but adding a deprecation in case there are bugs not caught by our tests. --- activerecord/lib/active_record/attribute_methods.rb | 15 +++++++++++++++ activerecord/test/cases/attribute_methods_test.rb | 15 +++++++++++++++ 2 files changed, 30 insertions(+) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 8d7eb4a48d..dc6dc2e63a 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/enumerable' +require 'active_support/deprecation' module ActiveRecord # = Active Record Attribute Methods @@ -77,6 +78,20 @@ module ActiveRecord end end + def attribute_missing(match, *args, &block) + if self.class.columns_hash[match.attr_name] + ActiveSupport::Deprecation.warn( + "The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \ + "dispatched through method_missing. This shouldn't happen, because `#{match.attr_name}' " \ + "is a column of the table. If this error has happened through normal usage of Active " \ + "Record (rather than through your own code or external libraries), please report it as " \ + "a bug." + ) + end + + super + end + def respond_to?(name, include_private = false) self.class.define_attribute_methods unless self.class.attribute_methods_generated? super diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 5ae3713e73..e324a252dd 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -675,6 +675,21 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert subklass.method_defined?(:id), "subklass is missing id method" end + def test_dispatching_column_attributes_through_method_missing_deprecated + Topic.define_attribute_methods + + topic = Topic.new(:id => 5) + topic.id = 5 + + topic.method(:id).owner.send(:remove_method, :id) + + assert_deprecated do + assert_equal 5, topic.id + end + ensure + Topic.undefine_attribute_methods + end + private def cached_columns @cached_columns ||= (time_related_columns_on_topic + serialized_columns_on_topic).map(&:name) -- cgit v1.2.3 From 11870117c6d9231b79e8125218728423e9dff207 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 13 Sep 2011 19:09:01 +0100 Subject: Rename first_or_new to first_or_initialize. For consistency with find_or_initialize_by. Also remove first_or_build alias. --- activerecord/CHANGELOG | 8 +++++--- activerecord/lib/active_record/base.rb | 2 +- activerecord/lib/active_record/relation.rb | 3 +-- activerecord/test/cases/base_test.rb | 12 ++---------- activerecord/test/cases/relations_test.rb | 18 ++++++------------ 5 files changed, 15 insertions(+), 28 deletions(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index e82906186e..563260dfd0 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -4,10 +4,12 @@ Wed Sep 7 15:25:02 2011 Aaron Patterson keys are per process id. * lib/active_record/connection_adapters/sqlite_adapter.rb: ditto -* Add first_or_create, first_or_create!, first_or_build and first_or_new methods to Active Record. This is a better approach over the old find_or_create_by dynamic methods because it's clearer which arguments are used to find the record and which are used to create it: +* Add first_or_create, first_or_create!, first_or_initialize methods to Active Record. This is a + better approach over the old find_or_create_by dynamic methods because it's clearer which + arguments are used to find the record and which are used to create it: + + User.where(:first_name => "Scarlett").first_or_create!(:last_name => "Johansson") - User.where(:first_name => "Scarlett").first_or_create!(:last_name => "Johansson", :hot => true) - [Andrés Mejía] * Support bulk change_table in mysql2 adapter, as well as the mysql one. [Jon Leighton] diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 92f80c6eaa..558b341c06 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -442,7 +442,7 @@ module ActiveRecord #:nodoc: class << self # Class methods delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped - delegate :first_or_create, :first_or_create!, :first_or_new, :first_or_build, :to => :scoped + delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :scoped delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped delegate :find_each, :find_in_batches, :to => :scoped delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index d3f1347e03..ecefaa633c 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -132,10 +132,9 @@ module ActiveRecord # Like first_or_create but calls new instead of create. # # Expects arguments in the same format as Base.new. - def first_or_new(attributes = nil, options = {}, &block) + def first_or_initialize(attributes = nil, options = {}, &block) first || new(attributes, options, &block) end - alias :first_or_build :first_or_new def respond_to?(method, include_private = false) arel.respond_to?(method, include_private) || diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index cb92f79e0e..12c1cfb30e 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -294,16 +294,8 @@ class BasicsTest < ActiveRecord::TestCase assert_equal parrot, the_same_parrot end - def test_first_or_new - parrot = Bird.first_or_new(:color => 'green', :name => 'parrot') - assert_kind_of Bird, parrot - assert !parrot.persisted? - assert parrot.new_record? - assert parrot.valid? - end - - def test_first_or_build - parrot = Bird.first_or_build(:color => 'green', :name => 'parrot') + def test_first_or_initialize + parrot = Bird.first_or_initialize(:color => 'green', :name => 'parrot') assert_kind_of Bird, parrot assert !parrot.persisted? assert parrot.new_record? diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 0f50ac9a2b..95408a5f29 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -956,8 +956,8 @@ class RelationTest < ActiveRecord::TestCase assert_raises(ActiveRecord::RecordInvalid) { Bird.where(:color => 'green').first_or_create!([ {:name => 'parrot'}, {:pirate_id => 1} ]) } end - def test_first_or_new - parrot = Bird.where(:color => 'green').first_or_new(:name => 'parrot') + def test_first_or_initialize + parrot = Bird.where(:color => 'green').first_or_initialize(:name => 'parrot') assert_kind_of Bird, parrot assert !parrot.persisted? assert parrot.valid? @@ -966,8 +966,8 @@ class RelationTest < ActiveRecord::TestCase assert_equal 'green', parrot.color end - def test_first_or_new_with_no_parameters - parrot = Bird.where(:color => 'green').first_or_new + def test_first_or_initialize_with_no_parameters + parrot = Bird.where(:color => 'green').first_or_initialize assert_kind_of Bird, parrot assert !parrot.persisted? assert !parrot.valid? @@ -975,8 +975,8 @@ class RelationTest < ActiveRecord::TestCase assert_equal 'green', parrot.color end - def test_first_or_new_with_block - parrot = Bird.where(:color => 'green').first_or_new { |bird| bird.name = 'parrot' } + def test_first_or_initialize_with_block + parrot = Bird.where(:color => 'green').first_or_initialize { |bird| bird.name = 'parrot' } assert_kind_of Bird, parrot assert !parrot.persisted? assert parrot.valid? @@ -985,12 +985,6 @@ class RelationTest < ActiveRecord::TestCase assert_equal 'parrot', parrot.name end - def test_first_or_build_is_alias_for_first_or_new - birds = Bird.scoped - assert birds.respond_to?(:first_or_build) - assert_equal birds.method(:first_or_new), birds.method(:first_or_build) - end - def test_explicit_create_scope hens = Bird.where(:name => 'hen') assert_equal 'hen', hens.new.name -- cgit v1.2.3 From 3777d4217b58bbb20a7301521f5451b5f55af78d Mon Sep 17 00:00:00 2001 From: Erik Behrends Date: Tue, 13 Sep 2011 22:42:39 +0300 Subject: [:class_name] option in belongs_to should mention belongs_to and not has_one --- activerecord/lib/active_record/associations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 8d755b6848..9e7d609d19 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1329,7 +1329,7 @@ module ActiveRecord # # [:class_name] # Specify the class name of the association. Use it only if that name can't be inferred - # from the association name. So has_one :author will by default be linked to the Author class, but + # from the association name. So belongs_to :author will by default be linked to the Author class, but # if the real class name is Person, you'll have to specify it with this option. # [:conditions] # Specify the conditions that the associated object must meet in order to be included as a +WHERE+ -- cgit v1.2.3 From 55da28dd2fa734de256a13fb09469eaa3ab15599 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 13 Sep 2011 23:23:29 +0100 Subject: We don't need to build a set for DangerousAttributeError. We can just use method_defined? and private_method_defined? --- activerecord/lib/active_record/attribute_methods.rb | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index dc6dc2e63a..0b074da0a5 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -42,23 +42,29 @@ module ActiveRecord # that also derive from Active Record. Raises DangerousAttributeError if the # method is defined by Active Record though. def instance_method_already_implemented?(method_name) + if dangerous_attribute_method?(method_name) + raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" + end + method_name = method_name.to_s index = ancestors.index(ActiveRecord::Base) || ancestors.length @_defined_class_methods ||= ancestors.first(index).map { |m| m.instance_methods(false) | m.private_instance_methods(false) }.flatten.map {|m| m.to_s }.to_set - @@_defined_activerecord_methods ||= defined_activerecord_methods - raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name) @_defined_class_methods.include?(method_name) || generated_attribute_methods.method_defined?(method_name) end - def defined_activerecord_methods + # A method name is 'dangerous' if it is already defined by Active Record, but + # not by any ancestors. (So 'puts' is not dangerous but 'save' is.) + def dangerous_attribute_method?(method_name) active_record = ActiveRecord::Base - super_klass = ActiveRecord::Base.superclass - methods = (active_record.instance_methods - super_klass.instance_methods) + - (active_record.private_instance_methods - super_klass.private_instance_methods) - methods.map {|m| m.to_s }.to_set + superclass = ActiveRecord::Base.superclass + + (active_record.method_defined?(method_name) || + active_record.private_method_defined?(method_name)) && + !superclass.method_defined?(method_name) && + !superclass.private_method_defined?(method_name) end end -- cgit v1.2.3 From 3b8a7cf2a7b2ffce671ca7f655de736c1054edbc Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 13 Sep 2011 23:52:38 +0100 Subject: Stop trying to be clever about when to define attribute methods. There is no meaningful performance penalty in defining attribute methods, since it only happens once. There is also no reason *not* to define them, since they get thrown in an included module, so they will not 'overwrite' anything. In fact, this is desirable, since it allows us to call super. --- .../lib/active_record/attribute_methods.rb | 11 +--------- activerecord/test/cases/attribute_methods_test.rb | 24 ---------------------- 2 files changed, 1 insertion(+), 34 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 0b074da0a5..d7bfaa5655 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -38,21 +38,12 @@ module ActiveRecord end end - # Checks whether the method is defined in the model or any of its subclasses - # that also derive from Active Record. Raises DangerousAttributeError if the - # method is defined by Active Record though. def instance_method_already_implemented?(method_name) if dangerous_attribute_method?(method_name) raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" end - method_name = method_name.to_s - index = ancestors.index(ActiveRecord::Base) || ancestors.length - @_defined_class_methods ||= ancestors.first(index).map { |m| - m.instance_methods(false) | m.private_instance_methods(false) - }.flatten.map {|m| m.to_s }.to_set - - @_defined_class_methods.include?(method_name) || generated_attribute_methods.method_defined?(method_name) + super end # A method name is 'dangerous' if it is already defined by Active Record, but diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index e324a252dd..b1b41fed0d 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -431,30 +431,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert topic.is_test? end - def test_kernel_methods_not_implemented_in_activerecord - %w(test name display y).each do |method| - assert !ActiveRecord::Base.instance_method_already_implemented?(method), "##{method} is defined" - end - end - - def test_defined_kernel_methods_implemented_in_model - %w(test name display y).each do |method| - klass = Class.new ActiveRecord::Base - klass.class_eval "def #{method}() 'defined #{method}' end" - assert klass.instance_method_already_implemented?(method), "##{method} is not defined" - end - end - - def test_defined_kernel_methods_implemented_in_model_abstract_subclass - %w(test name display y).each do |method| - abstract = Class.new ActiveRecord::Base - abstract.class_eval "def #{method}() 'defined #{method}' end" - abstract.abstract_class = true - klass = Class.new abstract - assert klass.instance_method_already_implemented?(method), "##{method} is not defined" - end - end - def test_raises_dangerous_attribute_error_when_defining_activerecord_method_in_model %w(save create_or_update).each do |method| klass = Class.new ActiveRecord::Base -- cgit v1.2.3 From 681c4dbb0222ac403de8ea0bfcf8ad77c5430585 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Wed, 14 Sep 2011 09:43:31 +1000 Subject: Add documentation for the extending method in ActiveRecord:QueryMethods --- .../lib/active_record/relation/query_methods.rb | 36 ++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 7eda9ad8e8..8dc59583c1 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -147,6 +147,42 @@ module ActiveRecord relation end + # Used to extend a scope with additional methods, either through + # a module or through a block provided. + # + # The object returned is a relation, which can be further extended. + # + # === Using a module + # + # module Pagination + # def page(number) + # # pagination code goes here + # end + # end + # + # scope = Model.scoped.extending(Pagination) + # scope.page(params[:page]) + # + # This can also take a list of modules also: + # + # scope = Model.scoped.extending(Pagination, SomethingElse) + # + # === Using a block + # + # scope = Model.scoped.extending do + # def page(number) + # # pagination code goes here + # end + # end + # scope.page(params[:page]) + # + # You can also use a block and a module list: + # + # scope = Model.scoped.extending(Pagination) do + # def per_page(number) + # # pagination code goes here + # end + # end def extending(*modules) modules << Module.new(&Proc.new) if block_given? -- cgit v1.2.3 From 823e16f57cb64c8557aa8e06b07e361d625a6e2e Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Wed, 14 Sep 2011 22:46:12 +0530 Subject: update 3.1 release date in changelogs --- activerecord/CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 563260dfd0..a54526dd41 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -36,7 +36,7 @@ a URI that specifies the connection configuration. For example: [Prem Sichanugrist] -*Rails 3.1.0 (unreleased)* +*Rails 3.1.0 (August 30, 2011)* * Add a proxy_association method to association proxies, which can be called by association extensions to access information about the association. This replaces proxy_owner etc with -- cgit v1.2.3 From dcd70773fc66f84555cf55de0c0c2b8cd8ae8cc1 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Wed, 14 Sep 2011 23:15:18 +0530 Subject: minor edit --- activerecord/lib/active_record/relation/query_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 8dc59583c1..a11b7a3864 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -163,7 +163,7 @@ module ActiveRecord # scope = Model.scoped.extending(Pagination) # scope.page(params[:page]) # - # This can also take a list of modules also: + # You can also pass a list of modules: # # scope = Model.scoped.extending(Pagination, SomethingElse) # -- cgit v1.2.3