Skip to content

Commit

Permalink
Version 0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Issei Naruta committed Apr 29, 2012
1 parent c1c9139 commit 3bf7624
Show file tree
Hide file tree
Showing 12 changed files with 274 additions and 66 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
*.swp
*.gem
18 changes: 18 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
GEM
remote: http://rubygems.org/
specs:
diff-lcs (1.1.3)
rspec (2.9.0)
rspec-core (~> 2.9.0)
rspec-expectations (~> 2.9.0)
rspec-mocks (~> 2.9.0)
rspec-core (2.9.0)
rspec-expectations (2.9.1)
diff-lcs (~> 1.1.3)
rspec-mocks (2.9.0)

PLATFORMS
ruby

DEPENDENCIES
rspec
103 changes: 103 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
## Welcome to Arproxy
Arproxy is a proxy between ActiveRecord and Database adapters. You can make a custom proxy what analyze and/or modify the SQLs before DB adapter executes them.

This comment has been minimized.

Copy link
@sorah

sorah Apr 29, 2012

Member
  • 文の終わりで改行を入れてもいいと思います (見た目上はひとつの <p> になるので問題はない)
  • "the SQLs" なんでしょうか。個人的には "SQLs" かなぁと思いました。

## Getting Started
Write your proxy like this:

```ruby
class QueryTracer < Arproxy::Base
def execute(sql, name=nil)
Rails.logger.debug sql
Rails.logger.debug caller(1).join("\n")
super(sql, name)
end
end
```

This comment has been minimized.

Copy link
@sorah

sorah Apr 29, 2012

Member

どこに書くのが適切なのか説明されてなくて少しわかりにくいですね。どこか適当な所に置いて require するとかなんでしょうか。

And write Arproxy configuration in Rails' config/initializers:

```ruby
Arproxy.configure do |config|
config.adapter = "mysql2" # A DB Apdapter name which is used in your database.yml
config.use QueryTracer
end
Arproxy.enable!
```

So you can see the backtrace of SQLs in the Rails' log.

This comment has been minimized.

Copy link
@sorah

sorah Apr 29, 2012

Member

個人的には "So" → "Then" かな


```ruby
# In your Rails code
MyTable.where(:id => id).limit(1) # => The SQL and the backtrace appear in the log
```

## Architecture
Without Arproxy:

```
+-------------------------+ +------------------+
| ActiveRecord::Base#find |--execute(sql, name)-->| Database Adapter |
+-------------------------+ +------------------+
```

With Arproxy:

```ruby
Arproxy.configure do |config|
config.adapter = "mysql2"
config.use MyProxy1
config.use MyProxy2
end
```

```
+-------------------------+ +----------+ +----------+ +------------------+
| ActiveRecord::Base#find |--execute(sql, name)-->| MyProxy1 |-->| MyProxy2 |-->| Database Adapter |
+-------------------------+ +----------+ +----------+ +------------------+
```

## Examples
### Slow Query Logger
```ruby
class SlowQueryLogger < Arproxy::Base
def initialize(slow_ms)
@slow_ms = slow_ms
end

def execute(sql, name=nil)
result = nil
ms = Benchmark.ms { result = super(sql, name) }
if ms >= @slow_ms
Rails.logger.info "Slow(#{ms.to_i}ms): #{sql}"
end
result
end
end

Arproxy.configure do |config|
config.use SlowQueryLogger, 1000
end
```

### Adding Comments to SQLs
```ruby
class CommentAdder < Arproxy::Base
def execute(sql, name=nil)
sql += " /*this_is_comment*/"
super(sql, name)
end
end
```

## Appendix
### What the `name' argument is
In the Rails' log you may see queries like this:
```
User Load (22.6ms) SELECT `users`.* FROM `users` WHERE `users`.`name` = 'Issei Naruta'
```
Then `"User Load"` is the `name`.

## License
Arproxy is released under the MIT license:
* www.opensource.org/licenses/MIT

11 changes: 11 additions & 0 deletions arproxy.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Gem::Specification.new do |spec|
spec.name = 'arproxy'
spec.version = '0.1.0'
spec.summary = 'Proxy between ActiveRecord and DB adapter'
spec.description = 'Arproxy is a proxy between ActiveRecord and database adapter'
spec.files = Dir.glob("lib/**/*.rb")
spec.author = 'Issei Naruta'
spec.email = 'naruta@cookpad.com'
spec.homepage = 'https://github.com/cookpad/arproxy'
spec.has_rdoc = false
end
45 changes: 28 additions & 17 deletions lib/arproxy.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
require "logger"

module Arproxy
autoload :Config
autoload :Proxy
autoload :Error
autoload :Config, "arproxy/config"
autoload :ProxyChain, "arproxy/proxy_chain"
autoload :Error, "arproxy/error"
autoload :Base, "arproxy/base"

module_function
def configure
Expand All @@ -13,28 +14,38 @@ def configure
end

def enable!
@proxy = Proxy.new @config
@proxy.enable!
@proxy_chain = ProxyChain.new @config

@config.adapter_class.class_eval do
def execute_with_arproxy(sql, name=nil)
::Arproxy.proxy_chain.connection = self
::Arproxy.proxy_chain.head.execute sql, name
end
alias_method :execute_without_arproxy, :execute
alias_method :execute, :execute_with_arproxy
::Arproxy.logger.debug("Arproxy: Enabled")
end
end

def disable!
@proxy.disable! if @proxy
@config.adapter_class.class_eval do
alias_method :execute, :execute_without_arproxy
::Arproxy.logger.debug("Arproxy: Disabled")
end
@proxy_chain = nil
@config = nil
end

def logger
@logger ||= ::Logger.new(STDOUT)
@logger ||= begin
@config && @config.logger ||
defined?(::Rails) && ::Rails.logger ||
::Logger.new(STDOUT)
end
end

def chain_head
@proxy.chain_head
def proxy_chain
@proxy_chain
end
end

__END__
Arproxy.configure do |config|
config.adapter = "mysql2"
config.use Hoge
config.use Moge
end

Arproxy.enable!
9 changes: 9 additions & 0 deletions lib/arproxy/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Arproxy
class Base
attr_accessor :proxy_chain, :next_proxy

def execute(sql, name=nil)
next_proxy.execute sql, name
end
end
end
10 changes: 7 additions & 3 deletions lib/arproxy/chain_tail.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
module Arproxy
class ChainTail
def execute(connection, sql, name=nil)
connection.execute_without_arproxy(sql, name)
class ChainTail < Base
def initialize(proxy_chain)
self.proxy_chain = proxy_chain
end

def execute(sql, name=nil)
self.proxy_chain.connection.execute_without_arproxy sql, name
end
end
end
8 changes: 7 additions & 1 deletion lib/arproxy/config.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module Arproxy
class Config
attr_accessor :adapter
attr_accessor :adapter, :logger
attr_reader :proxies

def initialize
Expand All @@ -11,5 +11,11 @@ def use(proxy_class, *options)
::Arproxy.logger.debug("Arproxy: Mounting #{proxy_class.inspect} (#{options.inspect})")
@proxies << [proxy_class, options]
end

def adapter_class
raise Arproxy::Error, "config.adapter must be set" unless @adapter
camelized_adapter_name = @adapter.split("_").map(&:capitalize).join
eval "::ActiveRecord::ConnectionAdapters::#{camelized_adapter_name}Adapter"
end
end
end
45 changes: 0 additions & 45 deletions lib/arproxy/proxy.rb

This file was deleted.

26 changes: 26 additions & 0 deletions lib/arproxy/proxy_chain.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module Arproxy
autoload :ChainTail, "arproxy/chain_tail"

class ProxyChain
attr_reader :head, :tail
attr_accessor :connection

def initialize(config)
@config = config
setup_proxy_chain(@config)
end

def setup_proxy_chain(config)
@tail = ChainTail.new self
@head = config.proxies.reverse.inject(@tail) do |next_proxy, proxy_config|
cls, options = proxy_config
proxy = cls.new *options
proxy.proxy_chain = self
proxy.next_proxy = next_proxy
proxy
end
end
private :setup_proxy_chain

end
end
62 changes: 62 additions & 0 deletions spec/arproxy_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
require "spec_helper"

describe Arproxy do
class ProxyA < Arproxy::Base
def execute(sql, name)
super "#{sql}_A", "#{name}_A"
end
end

class ProxyB < Arproxy::Base
def initialize(opt=nil)
@opt = opt
end

def execute(sql, name)
super "#{sql}_B#{@opt}", "#{name}_B#{@opt}"
end
end

module ::ActiveRecord
module ConnectionAdapters
class DummyAdapter
def execute(sql, name = nil)
{:sql => sql, :name => name}
end
end
end
end

let(:connection) { ::ActiveRecord::ConnectionAdapters::DummyAdapter.new }
subject { connection.execute "SQL", "NAME" }
after(:each) do
Arproxy.disable!
end

context "with 2 proxies" do
before do
Arproxy.configure do |config|
config.adapter = "dummy"
config.use ProxyA
config.use ProxyB
end
Arproxy.enable!
end

it { should == {:sql => "SQL_A_B", :name => "NAME_A_B"} }
end

context "with 2 proxies which have an option" do
before do
Arproxy.configure do |config|
config.adapter = "dummy"
config.use ProxyA
config.use ProxyB, 1
end
Arproxy.enable!
end

it { should == {:sql => "SQL_A_B1", :name => "NAME_A_B1"} }
end

end
2 changes: 2 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require File.expand_path("../../lib/arproxy", __FILE__)

0 comments on commit 3bf7624

Please sign in to comment.