monga 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.
Files changed (42) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +9 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +110 -0
  5. data/Rakefile +13 -0
  6. data/lib/monga/client.rb +8 -0
  7. data/lib/monga/clients/client.rb +24 -0
  8. data/lib/monga/clients/master_slave_client.rb +5 -0
  9. data/lib/monga/clients/replica_set_client.rb +101 -0
  10. data/lib/monga/collection.rb +108 -0
  11. data/lib/monga/connection.rb +23 -0
  12. data/lib/monga/connection_pool.rb +38 -0
  13. data/lib/monga/connections/em_connection.rb +152 -0
  14. data/lib/monga/connections/primary.rb +46 -0
  15. data/lib/monga/connections/secondary.rb +13 -0
  16. data/lib/monga/cursor.rb +175 -0
  17. data/lib/monga/database.rb +97 -0
  18. data/lib/monga/exceptions.rb +9 -0
  19. data/lib/monga/miner.rb +72 -0
  20. data/lib/monga/request.rb +122 -0
  21. data/lib/monga/requests/delete.rb +23 -0
  22. data/lib/monga/requests/get_more.rb +19 -0
  23. data/lib/monga/requests/insert.rb +29 -0
  24. data/lib/monga/requests/kill_cursors.rb +25 -0
  25. data/lib/monga/requests/query.rb +49 -0
  26. data/lib/monga/requests/update.rb +25 -0
  27. data/lib/monga/response.rb +11 -0
  28. data/lib/monga.rb +30 -0
  29. data/monga.gemspec +26 -0
  30. data/spec/helpers/mongodb.rb +59 -0
  31. data/spec/helpers/truncate.rb +15 -0
  32. data/spec/monga/collection_spec.rb +448 -0
  33. data/spec/monga/connection_pool_spec.rb +50 -0
  34. data/spec/monga/connection_spec.rb +64 -0
  35. data/spec/monga/cursor_spec.rb +186 -0
  36. data/spec/monga/database_spec.rb +67 -0
  37. data/spec/monga/replica_set_client_spec.rb +46 -0
  38. data/spec/monga/requests/delete_spec.rb +0 -0
  39. data/spec/monga/requests/insert_spec.rb +0 -0
  40. data/spec/monga/requests/query_spec.rb +28 -0
  41. data/spec/spec_helper.rb +29 -0
  42. metadata +185 -0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in monga.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem "minitest"
8
+ gem "minitest-reporters"
9
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Petr Yanovich
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # Monga
2
+
3
+ [MongoDB](http://www.mongodb.org/) Ruby Client on [EventMachine](https://github.com/eventmachine/eventmachine).
4
+
5
+ This client is under development. You can try [em-mongo](https://github.com/bcg/em-mongo).
6
+
7
+ Client supports MongoDB 2.4. Some features won't work in lower versions.
8
+
9
+ ## To Do List
10
+
11
+ * [ ] Write a Wiki
12
+ * [ ] Write comments
13
+ * [ ] Grammar improvement ;)
14
+
15
+ ### Clients
16
+ * [x] Client (Single instance connection)
17
+ * [IN PROCESS] ReplicaSetClient
18
+ * [ ] MasterSlaveClient
19
+ * [ ] ReadPref
20
+ * [ ] Sharding Support
21
+
22
+ ### Connection
23
+ * [x] Connection
24
+ * [x] Autoreconnect
25
+ * [x] Connection Pool
26
+
27
+ ### Protocol
28
+ * [x] OP_QUERY
29
+ * [x] OP_GET_MORE
30
+ * [x] OP_KILL_CURSORS
31
+ * [x] OP_INSERT
32
+ * [x] OP_UPDATE
33
+ * [x] OP_DELETE
34
+ * [x] OP_REPLY
35
+
36
+ ### Database
37
+ * [x] create_collection
38
+ * [x] drop_collection
39
+ * [x] get_last_error
40
+ * [x] drop_indexes
41
+ * [x] get_indexes
42
+ * Authentication
43
+ * [ ] login
44
+ * [ ] logout
45
+ * [ ] add_user
46
+ * [ ] check maxBsonSize / validate
47
+ * [x] cmd
48
+ * [x] eval
49
+ * [ ] where
50
+
51
+ ### Collection
52
+ * QUERY_OP
53
+ * [x] find
54
+ * [x] find_one (first)
55
+ * [ ] sorting
56
+ * INSERT_OP
57
+ * [x] insert (single)
58
+ * [x] insert (batch)
59
+ * [x] safe_insert
60
+ * FLAGS
61
+ * [x] continue_on_error
62
+ * UPDATE_OP
63
+ * [x] update
64
+ * [x] safe_update
65
+ * FLAGS
66
+ * [x] upsert
67
+ * [x] multi_update
68
+ * DELETE_OP
69
+ * [x] delete
70
+ * [x] safe_delete
71
+ * FLAGS
72
+ * [x] single_remove
73
+ * INDEXES
74
+ * [x] ensure_index
75
+ * [x] drop_index
76
+ * [x] drop_indexes
77
+ * [x] get_indexes
78
+ * [x] count
79
+ * [x] all
80
+ * [x] cursor
81
+ * [ ] DBRef
82
+
83
+ ### Cursor
84
+ * [x] limit
85
+ * [x] skip
86
+ * [x] batch_size
87
+ * [x] get_more
88
+ * [x] next_document
89
+ * [x] next_batch
90
+ * [x] each_doc
91
+ * [x] kill
92
+ * [x] mark_to_kill
93
+ * [x] batch_kill
94
+ * [x] explain
95
+ * [x] hint
96
+ * Flags
97
+ * [x] tailable_cursor
98
+ * [x] slave_ok
99
+ * [x] no_cursor_timeout
100
+ * [x] await_data
101
+ * [x] exhaust
102
+ * [x] partial
103
+
104
+ # ISSUES handled with
105
+
106
+ Some commands, such as `db.getLastError`, `db.count` and other `db.commands` requires `numberToReturn` in OP_QUERY to be setted as `-1`. Also this commands should return a response. If nothing returned it should be interpreted as an Exception. Also, in contrast to the other queries it could return `errmsg` which should be interpreted as an Exception too. Other query methods could return `err` response.
107
+
108
+ To create index you can't call any `db.ensureIndex` command but you should insert a document into `sytem.indexes` collection manually. To get list of indexes you should fetch all documents from this collection. But to drop index you should call specific `db.dropIndexes` command.
109
+
110
+ `multi_update` flag works only with `$` commands (i.e. `$set: { title: "blahblah" }`)
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ task :default => :spec
5
+ Rake::TestTask.new(:spec) do |t|
6
+ t.libs << 'spec'
7
+ if spec = ENV['spec']
8
+ t.pattern = "spec/**/#{spec}*_spec.rb"
9
+ else
10
+ t.pattern = 'spec/**/*_spec.rb'
11
+ end
12
+ t.verbose = false
13
+ end
@@ -0,0 +1,8 @@
1
+ require File.expand_path("../clients/client", __FILE__)
2
+ require File.expand_path("../clients/replica_set_client", __FILE__)
3
+ require File.expand_path("../clients/master_slave_client", __FILE__)
4
+ module Monga
5
+ Client = Monga::Clients::Client
6
+ ReplicaSetClient = Monga::Clients::ReplicaSetClient
7
+ MasterSlaveClient = Monga::Clients::MasterSlaveClient
8
+ end
@@ -0,0 +1,24 @@
1
+ module Monga::Clients
2
+ class Client
3
+ extend Forwardable
4
+
5
+ def_delegators :@connection_pool, :aquire_connection, :send_command, :primary?, :connected?
6
+
7
+ attr_reader :connection_pool
8
+
9
+ def initialize(opts={})
10
+ opts[:pool_size] ||= 1
11
+ @connection_pool = Monga::ConnectionPool.new(opts)
12
+ end
13
+
14
+ def [](db_name)
15
+ Monga::Database.new(self, db_name)
16
+ end
17
+
18
+ def find_primary!
19
+ @connection_pool.connections.each do |conn|
20
+ conn.is_master?(self)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ module Monga::Clients
2
+ class MasterSlaveClient
3
+
4
+ end
5
+ end
@@ -0,0 +1,101 @@
1
+ # How it works
2
+ # Replica Set tries to establish connections to all passed servers.
3
+ # Till no connection established it queues al queries inside it's
4
+
5
+ module Monga::Clients
6
+ class ReplicaSetClient
7
+ class ProxyConnection
8
+ include EM::Deferrable
9
+ def initialize(client)
10
+ @client = client
11
+ end
12
+
13
+ def send_command(msg, request_id=nil, &cb)
14
+ callback do
15
+ connection = @client.aquire_connection
16
+ connection.send_command(msg, request_id, &cb)
17
+ end
18
+ end
19
+ end
20
+
21
+ include EM::Deferrable
22
+
23
+ attr_reader :servers, :clients
24
+
25
+ def initialize(opts = {})
26
+ @read_pref = opts.delete(:read_pref) || :primary
27
+ @servers = opts.delete(:servers)
28
+ raise ArgumentError, "servers option is not passed or empty" if @servers.empty?
29
+
30
+ @clients = @servers.map do |server|
31
+ Monga::Client.new(server.merge(opts))
32
+ end
33
+
34
+ @proxy_connection = ProxyConnection.new(self)
35
+ end
36
+
37
+ def [](db_name)
38
+ Monga::Database.new(self, db_name)
39
+ end
40
+
41
+ def aquire_connection
42
+ server ||= case @read_pref
43
+ when :primary
44
+ primary
45
+ when :secondary
46
+ secondary
47
+ when :primary_preferred
48
+ primary || secondary
49
+ when :secondary_preferred
50
+ secondary || primary
51
+ when :nearest
52
+ fail "unimplemented read_pref mode"
53
+ else
54
+ fail "read_pref is undefined"
55
+ end
56
+
57
+
58
+ if server
59
+ if @deferred_status != :succeeded
60
+ set_deferred_status :succeeded
61
+ @proxy_connection.set_deferred_status :succeeded
62
+ end
63
+ else
64
+ if @deferred_status == :succeeded
65
+ set_deferred_status nil
66
+ @proxy_connection.set_deferred_status nil
67
+ end
68
+ end
69
+
70
+ server || @proxy_connection
71
+ end
72
+
73
+ def primary
74
+ prim = @clients.detect{ |c| c.primary? && c.connected? }
75
+ unless prim
76
+ find_primary!
77
+ end
78
+ prim
79
+ end
80
+
81
+ def secondary
82
+ @clients.select{ |c| !c.primary? && c.connected? }.sample
83
+ end
84
+
85
+ def find_primary!
86
+ unless @pending_primary
87
+ @pending_primary = true
88
+ @clients.each{ |c| c.find_primary! }
89
+ EM.add_timer(0.1) do
90
+ if primary
91
+ aquire_connection
92
+ @pending_primary = false
93
+ else
94
+ @pending_primary = false
95
+ find_primary!
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,108 @@
1
+ module Monga
2
+ class Collection
3
+ attr_reader :name, :db
4
+
5
+ def initialize(db, name)
6
+ @db = db
7
+ @name = name
8
+ end
9
+
10
+ def query(query = {}, fields = {}, opts = {})
11
+ options = {}
12
+ options[:query] = query
13
+ options[:fields] = fields
14
+ options.merge! opts
15
+ Monga::Miner.new(db, name, options)
16
+ end
17
+ alias :find :query
18
+
19
+ def find_one(query = {}, fields = {}, opts = {})
20
+ options = {}
21
+ options[:query] = query
22
+ options[:fields] = fields
23
+ options.merge! opts
24
+
25
+ Monga::Response.surround do |resp|
26
+ req = Monga::Miner.new(db, name, options).limit(1)
27
+ req.callback{ |data| resp.succeed data.first }
28
+ req.errback{ |err| resp.fail err }
29
+ end
30
+ end
31
+ alias :first :find_one
32
+
33
+ def insert(documents, opts = {})
34
+ options = {}
35
+ options[:documents] = documents
36
+ options.merge!(opts)
37
+ Monga::Requests::Insert.new(@db, @name, options).perform
38
+ end
39
+
40
+ def update(query = {}, update = {}, flags = {})
41
+ options = {}
42
+ options[:query] = query
43
+ options[:update] = update
44
+ options.merge!(flags)
45
+ Monga::Requests::Update.new(@db, @name, options).perform
46
+ end
47
+
48
+ def delete(query = {}, opts = {})
49
+ options = {}
50
+ options[:query] = query
51
+ options.merge!(opts)
52
+ Monga::Requests::Delete.new(@db, @name, options).perform
53
+ end
54
+ alias :remove :delete
55
+
56
+ def ensure_index(keys, opts={})
57
+ doc = {}
58
+ doc.merge!(opts)
59
+ # Read docs about naming
60
+ doc[:name] ||= keys.to_a.flatten * "_"
61
+ doc[:key] = keys
62
+ doc[:ns] = "#{db.name}.#{name}"
63
+ Monga::Requests::Insert.new(@db, "system.indexes", {documents: doc}).perform
64
+ end
65
+
66
+ def drop_index(indexes)
67
+ @db.drop_indexes(@name, indexes)
68
+ end
69
+
70
+ def drop_indexes
71
+ @db.drop_indexes(@name, "*")
72
+ end
73
+
74
+ def get_indexes
75
+ Monga::Miner.new(@db, "system.indexes")
76
+ end
77
+
78
+ def drop
79
+ @db.drop_collection(@name)
80
+ end
81
+
82
+ def count
83
+ @db.count(@name)
84
+ end
85
+
86
+ # Safe methods
87
+ [:update, :insert, :delete, :remove, :ensure_index].each do |meth|
88
+ class_eval <<-EOS
89
+ def safe_#{meth}(*args)
90
+ safe do
91
+ #{meth}(*args)
92
+ end
93
+ end
94
+ EOS
95
+ end
96
+
97
+ def safe
98
+ response = Monga::Response.new
99
+ request_id = yield
100
+ req = @db.get_last_error
101
+ req.callback do |data|
102
+ response.succeed(request_id)
103
+ end
104
+ req.errback{ |err| response.fail(err) }
105
+ response
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,23 @@
1
+ require File.expand_path("../connections/em_connection", __FILE__)
2
+ require File.expand_path("../connections/primary", __FILE__)
3
+ require File.expand_path("../connections/secondary", __FILE__)
4
+
5
+ module Monga
6
+ class Connection
7
+ extend Forwardable
8
+
9
+ def_delegators :@connection, :connected?, :reconnect, :responses, :send_command, :master?, :is_master?, :host, :port
10
+
11
+ CONNECTION_TYPES = {
12
+ default: Monga::Connections::EMConnection,
13
+ primary: Monga::Connections::Primary,
14
+ secondary: Monga::Connections::Secondary,
15
+ }
16
+
17
+ def initialize(opts={})
18
+ conn_type = opts.delete(:connection_type) || :default
19
+ conn_class = CONNECTION_TYPES[conn_type]
20
+ @connection = conn_class.connect(opts)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,38 @@
1
+ module Monga
2
+ class ConnectionPool < Monga::Connection
3
+ extend Forwardable
4
+
5
+ def_delegators :aquire_connection, :send_command
6
+
7
+ attr_reader :connections
8
+
9
+ def initialize(opts={})
10
+ @connections = []
11
+ pool_size = opts.delete :pool_size
12
+
13
+ pool_size.times do
14
+ @connections << Monga::Connection.new(opts)
15
+ end
16
+ end
17
+
18
+ def aquire_connection
19
+ connected = @connections.select(&:connected?)
20
+ if connected.any?
21
+ min = connected.min_by{ |c| c.responses.size }.responses.size
22
+ conns = connected.select{ |c| c.responses.size == min }
23
+ conns.sample
24
+ else
25
+ @connections.sample
26
+ end
27
+ end
28
+
29
+ def primary?
30
+ conn = aquire_connection
31
+ conn ? conn.master? : false
32
+ end
33
+
34
+ def connected?
35
+ @connections.any?(&:connected?)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,152 @@
1
+ module Monga::Connections
2
+ class EMConnection < EM::Connection
3
+ include EM::Deferrable
4
+
5
+ class Buffer
6
+ include Enumerable
7
+
8
+ attr_reader :buffer
9
+
10
+ def initialize
11
+ @buffer = ""
12
+ @positon = 0
13
+ end
14
+
15
+ def append(data)
16
+ @buffer += data
17
+ end
18
+
19
+ def each
20
+ while true
21
+ size = @buffer.size
22
+ if size > Monga::HEADER_SIZE
23
+ msg_length = @buffer[0, 4].unpack("L").first
24
+ if msg_length && size >= msg_length
25
+ data = @buffer.slice!(0, size)
26
+ yield data.unpack("LLLLLQLLa*")
27
+ else
28
+ break
29
+ end
30
+ else
31
+ break
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ attr_reader :responses, :host, :port
38
+
39
+ def initialize(opts = {})
40
+ @host = opts[:host]
41
+ @port = opts[:port]
42
+ @reactor_running = true
43
+ @responses = {}
44
+ end
45
+
46
+ def self.connect(opts = {})
47
+ host = opts[:host] ||= Monga::DEFAULT_HOST
48
+ port = opts[:port] ||= Monga::DEFAULT_PORT
49
+
50
+ EM.connect(host, port, self, opts)
51
+ end
52
+
53
+ def send_command(msg, request_id=nil, &cb)
54
+ reconnect unless @connected
55
+
56
+ callback do
57
+ send_data msg
58
+ end
59
+
60
+ @responses[request_id] = cb if cb
61
+ end
62
+
63
+ def receive_data(data)
64
+ @buffer.append(data)
65
+ @buffer.each do |message|
66
+ request_id = message[2]
67
+ cb = @responses.delete request_id
68
+ cb.call(message) if cb
69
+ end
70
+ end
71
+
72
+ def connection_completed
73
+ Monga.logger.debug("Connection is established #{@host}:#{@port}")
74
+
75
+ EM.add_shutdown_hook do
76
+ close
77
+ end
78
+
79
+ unless @reactor_running
80
+ EM.add_periodic_timer(Monga::Cursor::CLOSE_TIMEOUT){ Monga::Cursor.batch_kill(self) }
81
+ end
82
+
83
+ @connected = true
84
+ @pending_for_reconnect = false
85
+ @buffer = Buffer.new
86
+ @reactor_running = true
87
+
88
+ succeed
89
+ end
90
+
91
+ def reconnect
92
+ unless @connected && @pending_for_reconnect
93
+ if @reactor_running
94
+ super(@host, @port)
95
+ else
96
+ EM.schedule{ super(@host, @port) }
97
+ end
98
+ @pending_for_reconnect = true
99
+ end
100
+ end
101
+
102
+ def force_reconnect(host, port)
103
+ @connected = false
104
+ @host = host
105
+ @port = port
106
+ end
107
+
108
+ def connected?
109
+ EM.schedule { reconnect } unless @reactor_running
110
+ @connected || false
111
+ end
112
+
113
+ def unbind
114
+ Monga.logger.debug("Lost connection #{@host}:#{@port}")
115
+
116
+ @responses.each{ |k, cb| cb.call(Monga::Exceptions::LostConnection.new("Mongo has lost connection"))}
117
+ @connected = false
118
+ @primary = false
119
+ @pending_for_reconnect = false
120
+ set_deferred_status(nil)
121
+
122
+ if @reactor_running
123
+ EM.add_timer(0.1){ reconnect }
124
+ end
125
+ end
126
+
127
+ def close
128
+ Monga.logger.debug("EventMachine is stopped, closing connection")
129
+ @reactor_running = false
130
+ end
131
+
132
+ def master?
133
+ @primary || false
134
+ end
135
+
136
+ def is_master?(client)
137
+ db = client["admin"]
138
+ req = Monga::Requests::Query.new(db, "$cmd", query: {"isMaster" => 1}, limit: 1)
139
+ command = req.command
140
+ request_id = req.request_id
141
+ @responses[request_id] = proc do |data|
142
+ resp = req.parse_response(data)
143
+ if Exception === resp
144
+ @primary = false
145
+ else
146
+ @primary = resp.last.first["ismaster"]
147
+ end
148
+ end
149
+ send_data command
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,46 @@
1
+ module Monga::Connections
2
+ class Primary < Monga::Connections::EMConnection
3
+ def initialize(opts)
4
+ @client = opts.delete :client
5
+ @ser
6
+ super
7
+ end
8
+
9
+ def connection_completed
10
+ check_master do |master|
11
+ if master
12
+ super
13
+ else
14
+ reconnect
15
+ end
16
+ end
17
+ end
18
+
19
+ def check_master
20
+ db = @client["admin"]
21
+ request = Monga::Request.new(db, "$cmd", query: { "isMaster" => 1 })
22
+ request_id = request.request_id
23
+ @responses[request_id] = proc do |data|
24
+ request.parse_response(data)
25
+ if Exception === data
26
+ Monga.logger.debug("Error on connecting Primary to #{@host}:#{@port}, #{data.class}, #{data.message}")
27
+ @host, @port = @client.next_addr(@host, @port)
28
+ yield false
29
+ else
30
+ doc = data.last.first
31
+ if doc["ismaster"]
32
+ Monga.logger.debug("Primary has connected to #{@host}:#{@port}")
33
+ @client.inform(:primary, @host, @port)
34
+ yield true
35
+ else
36
+ Monga.logger.debug("#{@host}:#{@port} is not a primary")
37
+ @host, @port = @client.select_addr(doc)
38
+ yield false
39
+ end
40
+ end
41
+ end
42
+ command = request.command
43
+ send_data(command)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,13 @@
1
+ module Monga::Connections
2
+ class Secondary < Monga::Connections::EMConnection
3
+ def initialize(opts)
4
+ @client = opts.delete :client
5
+ super
6
+ end
7
+
8
+ def connection_completed
9
+ @client.inform(:secondary, @host, @port)
10
+ super()
11
+ end
12
+ end
13
+ end