dm-master-slave-adapter 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []