-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rails 4 #5
Rails 4 #5
Conversation
cc @eac @staugaard |
# | ||
class ActiveRecordDecorator < SimpleDelegator | ||
def name | ||
if __getobj__.respond_to? :connection_pool_name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this object is never used except to decorate ActiveRecord::Base, so it doesn't seem like this check is needed, yeah?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's String when we call connection_handler.establish_connection method
with explicit connection_pool_name, e.g. here:
https://github.com/zendesk/active_record_shards/blob/master/lib/active_record_shards/connection_switcher.rb#L185
In other words:
if __getobj__.is_a? String
# assume it's connection_pool_name already
__getobj__
else
# assume it's ActiveRecord::Base object
__getobj__.connection_pool_name
end
I guess, ActiveRecordDecorator is a poor name and documentation is not 100% accurate, I'll change it ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ActiveRecordDecorator
object should quack only with name
method, so sth like OpenStruct might be another possible option
if args[0].respond_to? :connection_pool_name
args[0] = OpenStruct.new(:name => args[0].connection_pool_name)
elsif args[0].is_a? String
args[0] = OpenStruct.new(:name => args[0])
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it relies on fact, that ConnectionHandler calls only name
method on klass|owner
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how it may look like with OpenStruct: https://gist.github.com/lukkry/6370855
We can remove ActiveRecordDecorator entirely then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I prefer the OpenStruct approach. It's more clear that "hey, we're just doing this to fool some methods we don't want to override".
travis needs to be configured to actually build the appraisal |
Overall these changes look good, although I will say I found the decorator stuff to be pretty hard to read at first. |
Agreed, it's not super obvious. Actually, I've started with 2 separate files, each for a different rails version: connection_pool_rails3.rb with 2 overridden methods and connection_pool_rails4.rb with 5 overridden methods, and then I moved to Decorator . imo it's not perfect but still better than maintaining different files for different rails version and overriding lots of AR internals |
else | ||
args[0].connection_pool_name | ||
end | ||
args[0] = OpenStruct.new(:name => name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since OpenStruct has some performance issues (http://ruby-doc.org/, http://jamesgolick.com/), I must ask: could this be done with a Struct instead, i.e.
args[0] = Struct.new(:name).new(name)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
didn't know that - thanks! ;) https://github.com/lukkry/active_record_shards/commit/500a6b9a922dd519c4f057503095b54036a3e75a
I've just found out that some of the methods like: |
Above errors are not Rails 4 related, See: #6 |
alias_method_chain :establish_connection, :connection_pool_name | ||
end | ||
module ActiveRecordShards | ||
ConnectionPoolNameDecorator = Struct.new(:name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found a place where superclass
method is called on this object: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L576
but imo we don't expect this code to be executed, pool_for(klass)
always returns a pool. Otherwise we probably should go back to SimpleDelegator
Can someone confirm it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think superclass
will be called. Can we just inject this into the Struct
definition too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ping @lukkry
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@osheroff, I'm not sure if I get what do you want to inject?
I think that superclass
is never called because pool_for
method ultimately fetches data from @owner_to_pool variable and if our connection pool names are not there, there is nothing what we can do about it but raise ConnectionNotEstablished. It looks like Rails 4 assumes that there is always a key for ActiveRecord::Base in @owner_to_pool
Loading development environment (Rails 4.0.0)
2.0.0-p247 :001 > ActiveRecord::Base.connection_handler.send(:owner_to_pool).keys
=> ["ActiveRecord::Base"]
2.0.0-p247 :002 > Account.all
Account Load (0.1ms) SELECT "accounts".* FROM "accounts"
=> #<ActiveRecord::Relation []>
2.0.0-p247 :003 > ActiveRecord::Base.connection_handler.remove_connection(ActiveRecord::Base)
=> {:adapter=>"sqlite3", :database=>"/Users/lukkry/projects/mango_test/db/development.sqlite3", :pool=>5, :timeout=>5000}
2.0.0-p247 :004 > Account.all
ActiveRecord::ConnectionNotEstablished: ActiveRecord::ConnectionNotEstablished
from /Users/lukkry/.rvm/gems/ruby-2.0.0-p247/gems/activerecord-4.0.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:546:in `retrieve_connection'
from /Users/lukkry/.rvm/gems/ruby-2.0.0-p247/gems/activerecord-4.0.0/lib/active_record/connection_handling.rb:79:in `retrieve_connection'
from /Users/lukkry/.rvm/gems/ruby-2.0.0-p247/gems/activerecord-4.0.0/lib/active_record/connection_handling.rb:53:in `connection'
from /Users/lukkry/.rvm/gems/ruby-2.0.0-p247/gems/activerecord-4.0.0/lib/active_record/querying.rb:36:in `find_by_sql'
from /Users/lukkry/.rvm/gems/ruby-2.0.0-p247/gems/activerecord-4.0.0/lib/active_record/relation.rb:585:in `exec_queries'
from /Users/lukkry/.rvm/gems/ruby-2.0.0-p247/gems/activerecord-4.0.0/lib/active_record/relation.rb:471:in `load'
from /Users/lukkry/.rvm/gems/ruby-2.0.0-p247/gems/activerecord-4.0.0/lib/active_record/relation.rb:220:in `to_a'
from /Users/lukkry/.rvm/gems/ruby-2.0.0-p247/gems/activerecord-4.0.0/lib/active_record/relation.rb:573:in `inspect'
from /Users/lukkry/.rvm/gems/ruby-2.0.0-p247/gems/railties-4.0.0/lib/rails/commands/console.rb:90:in `start'
from /Users/lukkry/.rvm/gems/ruby-2.0.0-p247/gems/railties-4.0.0/lib/rails/commands/console.rb:9:in `start'
from /Users/lukkry/.rvm/gems/ruby-2.0.0-p247/gems/railties-4.0.0/lib/rails/commands.rb:64:in `<top (required)>'
from bin/rails:4:in `require'
from bin/rails:4:in `<main>'
so it should be safe to assume that there is always a key for our connection pool name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I run tests on master and this line is never executed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re: superclass/rails3 yeah I guess so ;>
it wraps self.class
into ConnectionPoolNameDecorator
all the time and finally fails with:
<"undefined method `connection_pool_name' for ActiveRecordShards::ConnectionPoolNameDecorator:Class">
Don't have any reasonable solution yet.
I'm using this test (it's probably good only for rails 3 & 4):
it "raises an exception if a connection is not found" do
ActiveRecord::Base.on_shard(0) do
ActiveRecord::Base.connection_handler.remove_connection(Ticket)
assert_raises(ActiveRecord::ConnectionNotEstablished) do
ActiveRecord::Base.connection_handler.retrieve_connection_pool(Ticket)
assert_using_database('ars_test_shard0', Ticket)
end
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one possible solution is to override retrive_connection_pool
methods, but this is sth what I want to avoid from the beginning ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@osheroff I don't see what role sockets play here. As far as I understand pool_from_any_process_for
, it uses only copied memory from a parent process. Is it how you see it or I'm missing sth obvious here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In unix, when a child is forked off from the parent, it inherits its
parent's file descriptors (and thus sockets), and it's very bad to
re-use these in the child process. The rails code is probably ensuring
that children don't re-use their parents' sockets, and I wanted to make
sure that it works as expected.
On Mon, Sep 30, 2013 at 02:07:44PM -0700, Lukasz Krystkowiak wrote:
- end
-end-ActiveRecord::Base.singleton_class.class_eval do
- def establish_connection_with_connection_pool_name(spec = nil)
- case spec
- when ActiveRecord::Base::ConnectionSpecification
connection_handler.establish_connection(connection_pool_name, spec)
- else
establish_connection_without_connection_pool_name(spec)
- end
- end
- alias_method_chain :establish_connection, :connection_pool_name
-end
+module ActiveRecordShards- ConnectionPoolNameDecorator = Struct.new(:name)
@osheroff I don't see what role sockets play here. As far as I understand
pool_from_any_process_for
, it uses only copied memory from a parent process. Is it how you see it or I'm missing sth obvious here?
Reply to this email directly or view it on GitHub:
https://github.com/zendesk/active_record_shards/pull/5/files#r6671278
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re: superclass/rails3 I've overriden retrieve_connection_pool
;( and removed unneeded code (with superclass)
Before
# Rails 4
def retrieve_connection_pool(klass)
class_to_pool[klass.name] ||= begin
until pool = pool_for(klass)
klass = klass.superclass
break unless klass <= Base
end
class_to_pool[klass.name] = pool
end
end
# Rails 3 & 2
def retrieve_connection_pool(klass)
pool = @connection_pools[klass.name]
return pool if pool
return nil if ActiveRecord::Base == klass
retrieve_connection_pool klass.superclass
end
After
ActiveRecord::ConnectionAdapters::ConnectionHandler.class_eval do
if ActiveRecord::VERSION::MAJOR >= 4
def retrieve_connection_pool(klass)
class_to_pool[klass.connection_pool_name] ||= pool_for(klass)
end
else
def retrieve_connection_pool(klass)
(@class_to_pool || @connection_pools)[klass.connection_pool_name]
end
end
end
this is ready for re-review |
# Rails version). | ||
# | ||
# It takes the first argument, ActiveRecord::Base object or | ||
# String (connection_pool_name), converts it in OpenStruct |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can remove "Open" from that comment :-)
hey @lukkry, I didn't see that you had added new commits. Think this is ready to merge (or at least pre-release)? |
I squashed the commits, I think it's ready to try it as pre-release ;) |
@osheroff should I go ahead and merge it? |
I went ahead and released 3.0.0.beta1 from this branch; try that in a real project or two before merge? I was in flight with testing it on classic when security issues got in the way, can resume next week.... |
ok, cool, I'll test it against classic as well |
@@ -2,6 +2,8 @@ require 'bundler/setup' | |||
require "appraisal" | |||
require "bump/tasks" | |||
|
|||
Bundler::GemHelper.install_tasks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
require 'bundler/gem_tasks'
does the same
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is it better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not much, but I like it better, a little shorter / just a require vs a
method call
On Mon, Jan 20, 2014 at 8:56 AM, Lukasz Krystkowiak <
notifications@github.com> wrote:
In Rakefile:
@@ -2,6 +2,8 @@ require 'bundler/setup'
require "appraisal"
require "bump/tasks"+Bundler::GemHelper.install_tasks
why is it better?
—
Reply to this email directly or view it on GitHubhttps://github.com//pull/5/files#r9010711
.
Conflicts: Gemfile active_record_shards.gemspec lib/active_record_shards/default_slave_patches.rb test/schema.rb
is this ready to go now ? I want to play with rails 4 :) |
The thing that was, and is blocking this is someone testing it with classic on acceptance. |
can do! |
@lukkry there should be no need to use your own fork |
@grosser I pushed it all to rails4_support branch in this repo, https://github.com/zendesk/active_record_shards/tree/rails4_support, so we can work with it, instead of the fork. Also, it would be cool to merge #10 first, and rebase everything on top of it ;) |
Tried in on tddium + acceptance and seems to work nicely. On Tue, Jan 21, 2014 at 12:43 PM, Lukasz Krystkowiak <
|
done, try to rebase! |
I'm closing this in favor of #12 |
This PR adds support for Rails 4.
I try to highlight the biggest changes and describe how I approached them, it's likely going to be a long PR message, so bear with me ;)
ConnectionSpecification
lives in a different namespace, soActiveRecordShards::ConnectionSpecification
was introduced.ConnectionHandler
was slightly redesigned andklass.name
is called more often now (it's a key in@class_to_pool
=> all the methods where it's used are currently overrided andklass.connection_pool_name
is used instead). If we want to stick with the current approach we'll have to override many more methods fromConnectionHandler
. Therefore, I createdActiveRecordDecorator
to avoid it. This class decoratesActiveRecord::Base
object and returns connection pool name instead of its name. It allows us to overrideConnectionHandler
methods without changing or copying any logic:with_scope
is depracated =>scoping
is used insteadLet me know what you think, appreciate any feedback! ;)