dm-master-slave-adapter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in dm-master-slave-adapter.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Chris Corbyn
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,199 @@
1
+ # DataMapper Master/Slave Adapter (for MySQL replication etc)
2
+
3
+ This DataMapper adapter provides a thin layer infront of at least two
4
+ real DataMaper adapters, splitting reads and writes between a 'master'
5
+ and a 'slave'.
6
+
7
+ The adapter comes in two parts:
8
+
9
+ 1. The MasterSlaveAdapter, which knows of only two 'real' adapters
10
+ 2. A ReaderPoolAdapter, which knows of any number of 'real' adapters
11
+ to use as readers. You can set the ReaderPoolAdapter as the reader
12
+ for the MasterSlaveAdapter.
13
+
14
+
15
+ ## Installation
16
+
17
+ Via rubygems:
18
+
19
+ gem install dm-master-slave-adapter
20
+
21
+
22
+ ## Usage
23
+
24
+ The adapter is configured, at a basic level in the following way:
25
+
26
+ DataMapper.setup(:default, {
27
+ :adapter => :master_slave,
28
+ :master => {
29
+ :adapter => :mysql,
30
+ :host => "master.db.site.com",
31
+ :username => "root",
32
+ :password => ""
33
+ },
34
+ :slave => {
35
+ :adapter => :mysql,
36
+ :host => "slave.db.site.com",
37
+ :username => "root",
38
+ :password => ""
39
+ }
40
+ })
41
+
42
+ Here we create a repository named :default, which uses MySQL adapters for the
43
+ master and the slave.
44
+
45
+ In YAML, this looks like this:
46
+
47
+ default:
48
+ adapter: master_slave
49
+ master:
50
+ adapter: mysql
51
+ host: "master.db.site.com"
52
+ username: root
53
+ password:
54
+ slave:
55
+ adapter: mysql
56
+ host: "slave.db.site.com"
57
+ username: root
58
+ password:
59
+
60
+ Both the master and the slave are named :default, but you cannot access them directly
61
+ with DataMapper.repository( ... ); you can only access the MasterSlaveAdapter.
62
+
63
+ It is possible to access both the master and the slave using accessors on the
64
+ MasterSlaveAdapter, however:
65
+
66
+ DataMapper.repository(:default).adapter.master
67
+ DataMapper.repository(:default).adapter.slave
68
+
69
+ ### Bind to master on first write
70
+
71
+ It is important to note one particular behaviour with this adapter. By design, after
72
+ the first write operation has occurred, all subsquent queries, including reads, will
73
+ be sent directly to the master. This is almost always the desirable behaviour, since
74
+ you will undoubtedly experience race conditions due to reader-lag if not.
75
+
76
+ You can force the binding to the master at any time, using:
77
+
78
+ DataMapper.repository(:default).adapter.bind_to_master
79
+
80
+ This is a state changing method and will remain in effect until you reset the binding
81
+ with:
82
+
83
+ DataMapper.repository(:default).adapter.reset_binding
84
+
85
+ In a web application, you'll typically want to reset the binding to master at the end
86
+ of each request, to ensure subsquent requests are not permanently bound to the master.
87
+
88
+ A Rack middleware is provided to do this automatically. The easiest way to use this in
89
+ a Rails application, is to mount it inside your ApplicationController:
90
+
91
+ class ApplicationController < ActionController::Base
92
+ use DataMapper::MasterSlaveAdapter::Binding, :default
93
+ end
94
+
95
+ You can use the middleware anywhere a Rack middleware can be used, however, but it must
96
+ be executed after DataMapper has been initialized.
97
+
98
+ Note that accessing the master directly, (again, by design) will not cause all subsquent
99
+ queries to be sent to the master in the same way implicit querying does. This is useful
100
+ when logic is isolated to a specific part of your application and you know other parts of
101
+ the application need not query the same storage backend. I personally do this for
102
+ session storage.
103
+
104
+ Lastly, you can force all queries to be implicitlty sent to the master in the context of
105
+ a block, simply by passing a block to #bind_to_master, like so:
106
+
107
+ DataMapper.repository(:default).adapter.bind_to_master do
108
+ ...
109
+ end
110
+
111
+ Once the block has completed, the adapter will be restored to its original state,
112
+ regardless of what writes may have occurred. Note that if the adapter was already
113
+ implictly bound to master before the block was invoked, this will have no effect.
114
+
115
+
116
+ ### Using the ReaderPoolAdapter
117
+
118
+ The ReaderPoolAdapter simply allows you to use more than one adapter as the 'slave' when
119
+ configuring the MasterSlaveAdapter. For every read query it receives, it picks a random
120
+ adapter from its pool.
121
+
122
+ It is configured like so:
123
+
124
+ DataMapper.setup(:default, {
125
+ :adapter => :master_slave,
126
+ :master => {
127
+ ...
128
+ },
129
+ :slave => {
130
+ :adapter => :reader_pool,
131
+ :pool => [
132
+ {
133
+ :adapter => :mysql
134
+ :host => "slave1.db.site.com",
135
+ :username => "root",
136
+ :password => ""
137
+ },
138
+ {
139
+ :adapter => :mysql
140
+ :host => "slave2.db.site.com",
141
+ :username => "root",
142
+ :password => ""
143
+ }
144
+ ]
145
+ }
146
+ })
147
+
148
+ In the above setup, we simply have two MySQL hosts specified as available
149
+ slaves to the MasterSlaveAdapter. In YAML, that looks like this:
150
+
151
+ default:
152
+ adapter: master_slave
153
+ master:
154
+ ...
155
+ slave:
156
+ adapter: reader_pool
157
+ pool:
158
+ - adapter: mysql
159
+ host: "slave1.db.site.com"
160
+ username: root
161
+ password:
162
+ - adapter: mysql
163
+ host: "slave2.db.site.com"
164
+ username: root
165
+ password:
166
+
167
+ ## Reporting Issues
168
+
169
+ Please file any issues in the issue tracker at GitHub:
170
+
171
+ - https://github.com/d11wtq/dm-master-slave-adapter/issues
172
+
173
+ ## Potential TODOs
174
+
175
+ - Raise an exception for #create, #update and #delete on the reader
176
+ - Enhanced logging to include the details of the adapter being used
177
+
178
+ ## Copyright and Licensing
179
+
180
+ Copyright (c) 2011 Chris Corbyn
181
+
182
+ Permission is hereby granted, free of charge, to any person obtaining
183
+ a copy of this software and associated documentation files (the
184
+ "Software"), to deal in the Software without restriction, including
185
+ without limitation the rights to use, copy, modify, merge, publish,
186
+ distribute, sublicense, and/or sell copies of the Software, and to
187
+ permit persons to whom the Software is furnished to do so, subject to
188
+ the following conditions:
189
+
190
+ The above copyright notice and this permission notice shall be
191
+ included in all copies or substantial portions of the Software.
192
+
193
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
194
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
195
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
196
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
197
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
198
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
199
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "data_mapper/master_slave_adapter/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "dm-master-slave-adapter"
7
+ s.version = DataMapper::MasterSlaveAdapter::VERSION
8
+ s.authors = ["Chris Corbyn"]
9
+ s.email = ["chris@w3style.co.uk"]
10
+ s.homepage = "https://github.com/d11wtq/dm-master-slave-adapter"
11
+ s.summary = %q{Master/Slave Adapter for DataMapper}
12
+ s.description = (<<-TEXT)
13
+ Provides the ability to use DataMapper in an environment where
14
+ database replication draws the need for using separate connections
15
+ for reading and writing data.
16
+
17
+ This adapter simply wraps two other "real" DataMapper adapters,
18
+ rather than providing any direct I/O logic
19
+ TEXT
20
+
21
+ s.rubyforge_project = "dm-master-slave-adapter"
22
+
23
+ s.files = `git ls-files`.split("\n")
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
26
+ s.require_paths = ["lib"]
27
+
28
+ s.add_development_dependency "rspec"
29
+ s.add_runtime_dependency "dm-core"
30
+ end
@@ -0,0 +1,76 @@
1
+ require 'forwardable'
2
+
3
+ module DataMapper
4
+ module Adapters
5
+
6
+ class MasterSlaveAdapter < AbstractAdapter
7
+ extend Forwardable
8
+
9
+ attr_reader :slave
10
+ attr_reader :master
11
+
12
+ def_delegators :reader, :read, :aggregate
13
+ def_delegators :writer, :create, :update, :delete
14
+
15
+ def initialize(name, options)
16
+ super
17
+
18
+ @slave = if @options[:slave].kind_of?(AbstractAdapter)
19
+ @options[:slave]
20
+ else
21
+ assert_kind_of 'options', @options[:slave], Hash
22
+ Adapters.new(name, @options[:slave])
23
+ end
24
+
25
+ @master = if @options[:master].kind_of?(AbstractAdapter)
26
+ @options[:master]
27
+ else
28
+ assert_kind_of 'options', @options[:master], Hash
29
+ Adapters.new(name, @options[:master])
30
+ end
31
+
32
+ @reader = @slave
33
+ end
34
+
35
+ def bind_to_master
36
+ original_reader, @reader = @reader, @master
37
+
38
+ if block_given?
39
+ begin
40
+ yield
41
+ ensure
42
+ @reader = original_reader
43
+ end
44
+ end
45
+
46
+ self
47
+ end
48
+
49
+ def bound_to_master?
50
+ @reader.equal?(@master)
51
+ end
52
+
53
+ def reset_binding
54
+ @reader = @slave
55
+ self
56
+ end
57
+
58
+ private
59
+
60
+ def reader
61
+ @reader
62
+ end
63
+
64
+ def writer
65
+ bind_to_master
66
+ @master
67
+ end
68
+
69
+ def method_missing(meth, *args, &block)
70
+ writer.send(meth, *args, &block)
71
+ end
72
+ end
73
+
74
+ const_added(:MasterSlaveAdapter)
75
+ end
76
+ end
@@ -0,0 +1,48 @@
1
+ require 'forwardable'
2
+
3
+ module DataMapper
4
+ module Adapters
5
+
6
+ class ReaderPoolAdapter < AbstractAdapter
7
+ extend Forwardable
8
+
9
+ attr_reader :pool
10
+
11
+ def_delegators :random_adapter, :create, :read, :update, :delete
12
+
13
+ def initialize(name, options)
14
+ super
15
+
16
+ assert_kind_of 'options', @options[:pool], Array
17
+
18
+ raise ArgumentError, "The are no adapters in the adapter pool" if @options[:pool].empty?
19
+
20
+ @pool = []
21
+ @options[:pool].each do |adapter_options|
22
+ adapter = if adapter_options.kind_of?(AbstractAdapter)
23
+ adapter_options
24
+ else
25
+ assert_kind_of 'pool_adapter_options', adapter_options, Hash
26
+ Adapters.new(name, adapter_options)
27
+ end
28
+
29
+ @pool.push(adapter)
30
+ end
31
+
32
+ @number_generator = Random.new
33
+ end
34
+
35
+ def method_missing(meth, *args, &block)
36
+ random_adapter.send(meth, *args, &block)
37
+ end
38
+
39
+ private
40
+
41
+ def random_adapter
42
+ @pool[@number_generator.rand(0...@pool.length)]
43
+ end
44
+ end
45
+
46
+ const_added(:ReaderPoolAdapter)
47
+ end
48
+ end
@@ -0,0 +1,30 @@
1
+ =begin
2
+ Use this Rack middleware after your DataMapper repositories have been set up.
3
+
4
+ It will ensure the binding to master is reset at the end of every request.
5
+
6
+ use DataMapper::MasterSlaveAdapter::Middleware::WriteUnbinding, :your_repository
7
+
8
+ If you are using Rails, it is possible to do this from inside of your ApplicationController.
9
+ =end
10
+ module DataMapper
11
+ module MasterSlaveAdapter
12
+ module Middleware
13
+
14
+ class WriteUnbinding
15
+ def initialize(app, name = :default)
16
+ @app = app
17
+ @name = name.to_sym
18
+ end
19
+
20
+ def call(env)
21
+ @app.call(env)
22
+ ensure
23
+ adapter = DataMapper.repository(@name).adapter
24
+ adapter.reset_binding if adapter.respond_to?(:reset_binding)
25
+ end
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ module DataMapper
2
+ module MasterSlaveAdapter
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ require "data_mapper/master_slave_adapter/version"
2
+ require "data_mapper/master_slave_adapter/middleware/write_unbinding"
3
+ require "data_mapper/adapters/master_slave_adapter"
4
+ require "data_mapper/adapters/reader_pool_adapter"
@@ -0,0 +1,135 @@
1
+ require 'spec_helper'
2
+
3
+ describe DataMapper::Adapters::MasterSlaveAdapter do
4
+ before(:each) do
5
+ @master = double(:kind_of? => true)
6
+ @slave = double(:kind_of? => true)
7
+ @args = stub()
8
+ @result = stub()
9
+
10
+ @adapter = DataMapper::Adapters::MasterSlaveAdapter.new(:test, {
11
+ :master => @master,
12
+ :slave => @slave
13
+ })
14
+ end
15
+
16
+ context "delegation" do
17
+ it "sends all reads to the slave" do
18
+ @slave.should_receive(:read).with(@args).and_return(@result)
19
+ @adapter.read(@args).should be(@result)
20
+ end
21
+
22
+ it "sends aggregate queries to the slave" do
23
+ @slave.should_receive(:aggregate).with(@args).and_return(@result)
24
+ @adapter.aggregate(@args).should be(@result)
25
+ end
26
+
27
+ it "sends create to the master" do
28
+ @master.should_receive(:create).with(@args).and_return(@result)
29
+ @adapter.create(@args).should be(@result)
30
+ end
31
+
32
+ it "sends update to the master" do
33
+ @master.should_receive(:update).with(@args).and_return(@result)
34
+ @adapter.update(@args).should be(@result)
35
+ end
36
+
37
+ it "sends destroy to the master" do
38
+ @master.should_receive(:destroy).with(@args).and_return(@result)
39
+ @adapter.destroy(@args).should be(@result)
40
+ end
41
+
42
+ it "sends any unknown method to the master" do
43
+ @master.should_receive(:prepare_statement).with(@args).and_return(@result)
44
+ @adapter.prepare_statement(@args).should be(@result)
45
+ end
46
+
47
+ it "provides direct access to the master" do
48
+ @adapter.master.should == @master
49
+ end
50
+
51
+ it "provides direct access to the slave" do
52
+ @adapter.slave.should == @slave
53
+ end
54
+ end
55
+
56
+ describe "state" do
57
+ it "allows binding reads to the master" do
58
+ @adapter.bind_to_master
59
+ @master.should_receive(:read).with(@args).and_return(@result)
60
+ @adapter.read(@args).should be(@result)
61
+ end
62
+
63
+ it "reports if it is bound to master" do
64
+ @adapter.bind_to_master
65
+ @adapter.should be_bound_to_master
66
+ end
67
+
68
+ it "binds all reads to the master after the first write" do
69
+ @master.should_receive(:update)
70
+ @master.should_receive(:read).with(@args).and_return(@result)
71
+ @adapter.update(stub())
72
+ @adapter.read(@args).should be(@result)
73
+ end
74
+
75
+ it "can be unbound from master" do
76
+ @adapter.bind_to_master
77
+ @adapter.reset_binding
78
+ @slave.should_receive(:read).with(@args).and_return(@result)
79
+ @adapter.read(@args).should be(@result)
80
+ end
81
+
82
+ it "does not remain bound to master when using the adapter directly" do
83
+ @master.stub(:execute => nil)
84
+ @adapter.master.execute(stub())
85
+ @adapter.should_not be_bound_to_master
86
+ end
87
+
88
+ it "can be bound to master in the context of a block" do
89
+ @master.should_receive(:read).with(@args).and_return(@result)
90
+ @adapter.bind_to_master do
91
+ @adapter.read(@args).should be(@result)
92
+ end
93
+ @adapter.should_not be_bound_to_master
94
+ end
95
+
96
+ it "does not unbind from master after binding for a block if it was already bound before the block" do
97
+ @adapter.bind_to_master
98
+ @master.should_receive(:read).with(@args).and_return(@result)
99
+ @adapter.bind_to_master do
100
+ @adapter.read(@args).should be(@result)
101
+ end
102
+ @adapter.should be_bound_to_master
103
+ end
104
+ end
105
+
106
+ context "configured with an options hash" do
107
+ it "delegates to DataMapper::Adapters to create a master and a slave of the same name" do
108
+ DataMapper::Adapters.should_receive(:new).with(:test, { :adapter => :test_slave }).and_return(@slave)
109
+ DataMapper::Adapters.should_receive(:new).with(:test, { :adapter => :test_master }).and_return(@master)
110
+
111
+ adapter = DataMapper::Adapters::MasterSlaveAdapter.new(:test, {
112
+ :master => { :adapter => :test_master },
113
+ :slave => { :adapter => :test_slave }
114
+ })
115
+
116
+ adapter.master.should be(@master)
117
+ adapter.slave.should be(@slave)
118
+ end
119
+ end
120
+
121
+ context "configured with already instantiated adapters" do
122
+ it "uses the provided adapters directly" do
123
+ @master.should_receive(:kind_of?).with(DataMapper::Adapters::AbstractAdapter).and_return(true)
124
+ @slave.should_receive(:kind_of?).with(DataMapper::Adapters::AbstractAdapter).and_return(true)
125
+
126
+ adapter = DataMapper::Adapters::MasterSlaveAdapter.new(:test, {
127
+ :master => @master,
128
+ :slave => @slave
129
+ })
130
+
131
+ adapter.master.should be(@master)
132
+ adapter.slave.should be(@slave)
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe DataMapper::Adapters::ReaderPoolAdapter do
4
+ before(:each) do
5
+ @random_number = double()
6
+ Random.stub(:new).and_return(@random_number)
7
+
8
+ @delegate_a = double(:delegate_a, :kind_of? => true)
9
+ @delegate_b = double(:delegate_b, :kind_of? => true)
10
+ @args = stub()
11
+ @result = stub()
12
+
13
+ @adapter = DataMapper::Adapters::ReaderPoolAdapter.new(:test, {
14
+ :pool => [@delegate_a, @delegate_b]
15
+ })
16
+ end
17
+
18
+ context "delegation" do
19
+ it "sends CRUD operations to a random adapter from the pool" do
20
+ @random_number.should_receive(:rand).exactly(4).times.and_return(1)
21
+ @delegate_b.should_receive(:create).with(@args).and_return(@result)
22
+ @delegate_b.should_receive(:read).with(@args).and_return(@result)
23
+ @delegate_b.should_receive(:update).with(@args).and_return(@result)
24
+ @delegate_b.should_receive(:delete).with(@args).and_return(@result)
25
+ @adapter.create(@args).should be(@result)
26
+ @adapter.read(@args).should be(@result)
27
+ @adapter.update(@args).should be(@result)
28
+ @adapter.delete(@args).should be(@result)
29
+ end
30
+
31
+ it "sends aggregate queries to a random adapter from the pool" do
32
+ @random_number.should_receive(:rand).once.and_return(0)
33
+ @delegate_a.should_receive(:aggregate).with(@args).and_return(@result)
34
+ @adapter.aggregate(@args).should be(@result)
35
+ end
36
+
37
+ it "sends unknown methods to a random adapter from the pool" do
38
+ @random_number.should_receive(:rand).once.and_return(0)
39
+ @delegate_a.should_receive(:select).with(@args).and_return(@result)
40
+ @adapter.select(@args).should be(@result)
41
+ end
42
+ end
43
+
44
+ context "configured with an options hash" do
45
+ it "delegates to DataMapper::Adapters to create a pool of readers of the same name" do
46
+ DataMapper::Adapters.should_receive(:new).with(:test, { :adapter => :test_adapter }).and_return(@delegate_a)
47
+
48
+ adapter = DataMapper::Adapters::ReaderPoolAdapter.new(:test, {
49
+ :pool => [{ :adapter => :test_adapter }]
50
+ })
51
+
52
+ adapter.pool.should include(@delegate_a)
53
+ end
54
+ end
55
+
56
+ context "configured with already instantiated adapters" do
57
+ it "uses the adapters directly" do
58
+ @delegate_a.should_receive(:kind_of?).with(DataMapper::Adapters::AbstractAdapter).and_return(true)
59
+ @delegate_b.should_receive(:kind_of?).with(DataMapper::Adapters::AbstractAdapter).and_return(true)
60
+
61
+ adapter = DataMapper::Adapters::ReaderPoolAdapter.new(:test, {
62
+ :pool => [@delegate_a, @delegate_b]
63
+ })
64
+
65
+ adapter.pool.should include(@delegate_a)
66
+ adapter.pool.should include(@delegate_b)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe DataMapper::MasterSlaveAdapter::Middleware::WriteUnbinding do
4
+ let(:app) { double(:call => nil) }
5
+ let(:env) { stub() }
6
+ let(:middleware) { DataMapper::MasterSlaveAdapter::Middleware::WriteUnbinding.new(app, :test) }
7
+ let(:adapter) { double(:reset_binding => nil) }
8
+
9
+ before(:each) do
10
+ DataMapper.should_receive(:repository).with(:test).and_return(stub(:adapter => adapter))
11
+ end
12
+
13
+ it "invokes the application" do
14
+ app.should_receive(:call).with(env)
15
+ middleware.call(env)
16
+ end
17
+
18
+ it "resets the adapter binding at the end of the request" do
19
+ adapter.should_receive(:reset_binding)
20
+ middleware.call(env)
21
+ end
22
+
23
+ it "ensures the binding is reset when an error occurs" do
24
+ app.stub(:call) { raise "An error" }
25
+ adapter.should_receive(:reset_binding)
26
+ middleware.call(env) rescue Exception
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ require 'bundler/setup'
2
+ require 'dm-core'
3
+ require 'dm-master-slave-adapter'
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dm-master-slave-adapter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Chris Corbyn
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-15 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &16062280 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *16062280
25
+ - !ruby/object:Gem::Dependency
26
+ name: dm-core
27
+ requirement: &16061600 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *16061600
36
+ description: ! " Provides the ability to use DataMapper in an environment where\n
37
+ \ database replication draws the need for using separate connections\n for
38
+ reading and writing data.\n\n This adapter simply wraps two other \"real\" DataMapper
39
+ adapters,\n rather than providing any direct I/O logic\n"
40
+ email:
41
+ - chris@w3style.co.uk
42
+ executables: []
43
+ extensions: []
44
+ extra_rdoc_files: []
45
+ files:
46
+ - .gitignore
47
+ - Gemfile
48
+ - LICENSE
49
+ - README.md
50
+ - Rakefile
51
+ - dm-master-slave-adapter.gemspec
52
+ - lib/data_mapper/adapters/master_slave_adapter.rb
53
+ - lib/data_mapper/adapters/reader_pool_adapter.rb
54
+ - lib/data_mapper/master_slave_adapter/middleware/write_unbinding.rb
55
+ - lib/data_mapper/master_slave_adapter/version.rb
56
+ - lib/dm-master-slave-adapter.rb
57
+ - spec/public/adapters/master_slave_adapter_spec.rb
58
+ - spec/public/adapters/reader_pool_adapter_spec.rb
59
+ - spec/public/middleware/write_unbinding_spec.rb
60
+ - spec/spec_helper.rb
61
+ homepage: https://github.com/d11wtq/dm-master-slave-adapter
62
+ licenses: []
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ! '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project: dm-master-slave-adapter
81
+ rubygems_version: 1.8.10
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: Master/Slave Adapter for DataMapper
85
+ test_files: []