Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Access an instance's connection via its class, rather than via #connection #9371

Merged
merged 1 commit into from

6 participants

@benmoss

Had a problem today where we had a belongs_to :connection on our model. Hadn't occurred to me that this might be a problem, but when we ran our tests we were getting an obscure undefined method substitute_at for nil:NilClass exception whenever an instance of this class was being destroyed.

Discovered the root cause was this method, which was attempting to call connection on the instance, which in turn delegates to the class. This seems like a pretty safe change to make, I can't think of any possible repercussions. I wasn't sure if this warranted a regression test, but I'd be happy to provide one if it's required.

@senny
Owner

not sure about the test either but we need a CHANGELOG entry for sure. I'm leaning towards creating a test.

@benmoss

Added a test and a CHANGELOG entry

activerecord/test/cases/persistence_test.rb
@@ -315,6 +315,11 @@ def test_destroy
assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
end
+ def test_destroy_doesnt_rely_on_connection_instance_method
+ warehouse = WarehouseThing.find(1)
+ assert_nothing_raised(NoMethodError) { warehouse.destroy }
@senny Owner
senny added a note

maybe we should assert here that #connection returns "a connection" and supply a description in the assertion message why this is relevant. Otherwise the connection method could be easily removed from the model and we could run back into regressions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@benmoss

I experimented with removing the connection instance method from core and these were the only other places in the codebase that seemed to rely on it:

https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb#L22
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb#L29
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb#L44
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/locking/optimistic.rb#L89
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/locking/optimistic.rb#L120

It seems like it only makes sense to try to make the earlier change if I change all these others as well, otherwise it'll only be a matter of time until they likely bite me or someone else. Do you think this is worthwhile? It still seems like it's going to be hard to protect against others introducing new code that relies on it, the only sure-fire way to remove it as a 'reserved method' would be to rename it or remove it entirely, but both of those seem like too big of changes to the AR interface.

@rafaelfranca

I think you can change all these places, but we can't protect for introducing new code without removing connection from the instance. We can't do this without a deprecation cycle.

@jonleighton @tenderlove WDYT about removing connection method from instance?

@jonleighton
Collaborator

I am in favour. I've encountered this problem myself.

@rafaelfranca

@benmoss mind to update your pull request deprecating connection at instance level and removing all the use in the Rails codebase?

@benmoss

Sure, awesome!

@steveklabnik
Collaborator

Neat.

@benmoss

Any word on this?

@jonleighton
Collaborator

@benmoss I'll merge this, but it needs a rebase. Please comment when you have rebased as github doesn't send a notification when there's a code update.

@benmoss

Rebased!

activerecord/CHANGELOG.md
@@ -1717,5 +1717,8 @@
*Aaron Patterson*
+* `connection` is deprecated as an instance method.
+
+ *Ben Moss*

Can you please move this to the top of the changelog, right after the "unreleased" title?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
activerecord/test/cases/fixtures_test.rb
@@ -477,11 +477,6 @@ class CustomConnectionFixturesTest < ActiveRecord::TestCase
fixtures :courses
self.use_transactional_fixtures = false
- def test_connection
- assert_kind_of Course, courses(:ruby)
- assert_equal Course.connection, courses(:ruby).connection
- end

It'd be better to change the test to ensure it's deprecated instead:

def test_connection_instance_method_deprecation
  assert_deprecated { courses(:ruby).connection }
end

Or something like that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@benmoss

Fixed! Sorry about that.

@carlosantoniodasilva

@benmoss no problem, looks good to me now. Thanks.

@jonleighton all yours :)

@jonleighton
Collaborator

@benmoss could you add the reasoning for the change to your commit message / the changelog? thanks

@benmoss

Done

@benmoss benmoss Deprecate #connection in favour of accessing it via the class
This allows end-users to have a `connection` method on their models
without clashing with ActiveRecord internals.
992d87d
@jonleighton jonleighton merged commit 15970ef into rails:master
@yahonda yahonda referenced this pull request from a commit in yahonda/oracle-enhanced
@yahonda yahonda Deprecate #connection in favour of accessing it via the class 8fed415
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 9, 2013
  1. @benmoss

    Deprecate #connection in favour of accessing it via the class

    benmoss authored
    This allows end-users to have a `connection` method on their models
    without clashing with ActiveRecord internals.
This page is out of date. Refresh to see the latest.
View
6 activerecord/CHANGELOG.md
@@ -1,5 +1,11 @@
## Rails 4.0.0 (unreleased) ##
+* `connection` is deprecated as an instance method.
+ This allows end-users to have a `connection` method on their models
+ without clashing with ActiveRecord internals.
+
+ *Ben Moss*
+
* When copying migrations, preserve their magic comments and content encoding.
*OZAWA Sakuro*
View
6 activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -26,7 +26,7 @@ def insert_record(record, validate = true, raise = false)
join_table[reflection.association_foreign_key] => record.id
)
- owner.connection.insert stmt
+ owner.class.connection.insert stmt
end
record
@@ -41,7 +41,7 @@ def count_records
def delete_records(records, method)
if sql = options[:delete_sql]
records = load_target if records == :all
- records.each { |record| owner.connection.delete(interpolate(sql, record)) }
+ records.each { |record| owner.class.connection.delete(interpolate(sql, record)) }
else
relation = join_table
condition = relation[reflection.foreign_key].eq(owner.id)
@@ -53,7 +53,7 @@ def delete_records(records, method)
)
end
- owner.connection.delete(relation.where(condition).compile_delete)
+ owner.class.connection.delete(relation.where(condition).compile_delete)
end
end
View
1  activerecord/lib/active_record/core.rb
@@ -324,6 +324,7 @@ def readonly!
# also be used to "borrow" the connection to do database work that isn't
# easily done without going straight to SQL.
def connection
+ ActiveSupport::Deprecation.warn("#connection is deprecated in favour of accessing it via the class")
self.class.connection
end
View
4 activerecord/lib/active_record/locking/optimistic.rb
@@ -86,7 +86,7 @@ def update_record(attribute_names = @attributes.keys) #:nodoc:
)
).arel.compile_update(arel_attributes_with_values_for_update(attribute_names))
- affected_rows = connection.update stmt
+ affected_rows = self.class.connection.update stmt
unless affected_rows == 1
raise ActiveRecord::StaleObjectError.new(self, "update")
@@ -117,7 +117,7 @@ def relation_for_destroy
if locking_enabled?
column_name = self.class.locking_column
column = self.class.columns_hash[column_name]
- substitute = connection.substitute_at(column, relation.bind_values.length)
+ substitute = self.class.connection.substitute_at(column, relation.bind_values.length)
relation = relation.where(self.class.arel_table[column_name].eq(substitute))
relation.bind_values << [column, self[column_name].to_i]
View
2  activerecord/lib/active_record/persistence.rb
@@ -410,7 +410,7 @@ def destroy_row
def relation_for_destroy
pk = self.class.primary_key
column = self.class.columns_hash[pk]
- substitute = connection.substitute_at(column, 0)
+ substitute = self.class.connection.substitute_at(column, 0)
relation = self.class.unscoped.where(
self.class.arel_table[pk].eq(substitute))
View
5 activerecord/test/cases/fixtures_test.rb
@@ -477,9 +477,8 @@ class CustomConnectionFixturesTest < ActiveRecord::TestCase
fixtures :courses
self.use_transactional_fixtures = false
- def test_connection
- assert_kind_of Course, courses(:ruby)
- assert_equal Course.connection, courses(:ruby).connection
+ def test_connection_instance_method_deprecation
+ assert_deprecated { courses(:ruby).connection }
end
def test_leaky_destroy
View
8 activerecord/test/cases/transaction_callbacks_test.rb
@@ -182,9 +182,9 @@ def test_only_call_after_rollback_on_create_after_transaction_rollsback_for_new_
end
def test_call_after_rollback_when_commit_fails
- @first.connection.class.send(:alias_method, :real_method_commit_db_transaction, :commit_db_transaction)
+ @first.class.connection.class.send(:alias_method, :real_method_commit_db_transaction, :commit_db_transaction)
begin
- @first.connection.class.class_eval do
+ @first.class.connection.class.class_eval do
def commit_db_transaction; raise "boom!"; end
end
@@ -194,8 +194,8 @@ def commit_db_transaction; raise "boom!"; end
assert !@first.save rescue nil
assert_equal [:after_rollback], @first.history
ensure
- @first.connection.class.send(:remove_method, :commit_db_transaction)
- @first.connection.class.send(:alias_method, :commit_db_transaction, :real_method_commit_db_transaction)
+ @first.class.connection.class.send(:remove_method, :commit_db_transaction)
+ @first.class.connection.class.send(:alias_method, :commit_db_transaction, :real_method_commit_db_transaction)
end
end
Something went wrong with that request. Please try again.