tkellem 0.8.11 → 0.9.0.beta1

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.
@@ -1,20 +1,24 @@
1
1
  module Tkellem
2
2
 
3
- class Network < ActiveRecord::Base
4
- has_many :hosts, :dependent => :destroy
5
- accepts_nested_attributes_for :hosts
3
+ class Network < Sequel::Model
4
+ plugin :nested_attributes
5
+ plugin :validation_class_methods
6
+ plugin :serialization
6
7
 
7
- has_many :network_users, :dependent => :destroy
8
+ one_to_many :hosts, :dependent => :destroy
9
+ nested_attributes :hosts
10
+
11
+ one_to_many :network_users, :dependent => :destroy
8
12
  # networks either belong to a specific user, or they are public and any user
9
13
  # can join them.
10
- belongs_to :user
14
+ many_to_one :user
11
15
 
12
16
  validates_uniqueness_of :name, :scope => :user_id
13
17
 
14
- serialize :at_connect, Array
18
+ serialize_attributes :yaml, :at_connect
15
19
 
16
20
  def at_connect
17
- read_attribute(:at_connect) || []
21
+ super || []
18
22
  end
19
23
 
20
24
  def public?
@@ -1,17 +1,33 @@
1
1
  module Tkellem
2
2
 
3
- class NetworkUser < ActiveRecord::Base
4
- belongs_to :network
5
- belongs_to :user
3
+ class NetworkUser < Sequel::Model
4
+ plugin :serialization
6
5
 
7
- serialize :at_connect, Array
6
+ many_to_one :network
7
+ many_to_one :user
8
+
9
+ serialize_attributes :yaml, :at_connect
10
+
11
+ def at_connect
12
+ super || []
13
+ end
8
14
 
9
15
  def nick
10
- read_attribute(:nick) || user.name
16
+ super || user.name
11
17
  end
12
18
 
13
19
  def combined_at_connect
14
- network.at_connect + (at_connect || [])
20
+ network.at_connect + at_connect
21
+ end
22
+
23
+ def after_create
24
+ super
25
+ $tkellem_server.try(:after_create, self)
26
+ end
27
+
28
+ def after_destroy
29
+ super
30
+ $tkellem_server.try(:after_destroy, self)
15
31
  end
16
32
  end
17
33
 
@@ -1,13 +1,13 @@
1
1
  module Tkellem
2
2
 
3
- class Setting < ActiveRecord::Base
3
+ class Setting < Sequel::Model
4
4
  def self.get(setting_name)
5
- setting = first(:conditions => { :name => setting_name })
5
+ setting = where(:name => setting_name).first
6
6
  setting.try(:value)
7
7
  end
8
8
 
9
9
  def self.set(setting_name, new_value)
10
- setting = first(:conditions => { :name => setting_name })
10
+ setting = where(:name => setting_name).first
11
11
  setting.try(:update_attributes, :value => new_value.to_s, :unchanged => false)
12
12
  setting
13
13
  end
@@ -1,13 +1,20 @@
1
1
  module Tkellem
2
2
 
3
- class User < ActiveRecord::Base
4
- has_many :network_users, :dependent => :destroy
5
- has_many :networks, :dependent => :destroy
3
+ class User < Sequel::Model
4
+ plugin :validation_class_methods
5
+
6
+ one_to_many :network_users, :dependent => :destroy
7
+ one_to_many :networks, :dependent => :destroy
6
8
 
7
9
  validates_presence_of :username
8
10
  validates_uniqueness_of :username
9
11
  validates_presence_of :role, :in => %w(user admin)
10
12
 
13
+ def before_validation
14
+ self.role ||= 'user'
15
+ super
16
+ end
17
+
11
18
  # pluggable authentication -- add your own block, which takes |username, password|
12
19
  # parameters. Return a User object if authentication succeeded, or a
13
20
  # false/nil value if auth failed. You can create the user on-the-fly if
@@ -18,7 +25,7 @@ class User < ActiveRecord::Base
18
25
  # default database-based authentication
19
26
  # TODO: proper password hashing
20
27
  self.authentication_methods << proc do |username, password|
21
- user = find_by_username(username)
28
+ user = first(:username => username)
22
29
  user && user.valid_password?(password) && user
23
30
  end
24
31
 
@@ -31,7 +38,7 @@ class User < ActiveRecord::Base
31
38
  end
32
39
 
33
40
  def username=(val)
34
- write_attribute(:username, val.try(:downcase))
41
+ super(val.try(:downcase))
35
42
  end
36
43
 
37
44
  def name
@@ -44,7 +51,7 @@ class User < ActiveRecord::Base
44
51
  end
45
52
 
46
53
  def password=(password)
47
- write_attribute(:password, password ? OpenSSL::Digest::SHA1.hexdigest(password) : nil)
54
+ super(password ? OpenSSL::Digest::SHA1.hexdigest(password) : nil)
48
55
  end
49
56
 
50
57
  def admin?
@@ -15,9 +15,11 @@ module Tkellem
15
15
  # different backlog implementation. Right now, it's always loaded though.
16
16
  class Backlog
17
17
  include Tkellem::EasyLogger
18
+ include Celluloid
19
+
20
+ cattr_accessor :replay_pool
18
21
 
19
22
  Bouncer.add_plugin(self)
20
- cattr_accessor :instances
21
23
 
22
24
  def self.get_instance(bouncer)
23
25
  bouncer.data(self)[:instance] ||= self.new(bouncer)
@@ -57,7 +59,7 @@ class Backlog
57
59
  # open stream in append-only mode
58
60
  return @streams[ctx] if @streams[ctx]
59
61
  stream = @streams[ctx] = File.open(stream_filename(ctx), 'ab')
60
- stream.seek(0, IO::SEEK_END)
62
+ stream.seek(0, ::IO::SEEK_END)
61
63
  @starting_pos[ctx] = stream.pos
62
64
  stream
63
65
  end
@@ -98,7 +100,7 @@ class Backlog
98
100
  ctx = msg.prefix.split(/[!~@]/, 2).first
99
101
  end
100
102
  stream = get_stream(ctx)
101
- stream.puts(Time.now.strftime("%d-%m-%Y %H:%M:%S < #{'* ' if msg.action?}#{msg.prefix}: #{msg.args.last}"))
103
+ stream.puts(Time.now.strftime("%d-%m-%Y %H:%M:%S") + " < #{'* ' if msg.action?}#{msg.prefix}: #{msg.args.last}")
102
104
  update_pos(ctx, stream.pos)
103
105
  end
104
106
  end
@@ -109,39 +111,53 @@ class Backlog
109
111
  return if msg.ctcp? && !msg.action?
110
112
  ctx = msg.args.first
111
113
  stream = get_stream(ctx)
112
- stream.puts(Time.now.strftime("%d-%m-%Y %H:%M:%S > #{'* ' if msg.action?}#{msg.args.last}"))
114
+ stream.puts(Time.now.strftime("%d-%m-%Y %H:%M:%S") + " > #{'* ' if msg.action?}#{msg.args.last}")
113
115
  update_pos(ctx, stream.pos)
114
116
  end
115
117
  end
116
118
 
117
119
  def send_backlog(conn, device)
118
120
  device.each do |ctx_name, pos|
119
- stream = File.open(stream_filename(ctx_name), 'rb')
120
- stream.seek(pos)
121
-
122
- while line = stream.gets
123
- timestamp, msg = parse_line(line, ctx_name)
124
- next unless msg
125
- if msg.prefix
126
- # to user
121
+ filename = stream_filename(ctx_name)
122
+ Backlog.replay_pool.async(:replay, filename, pos, @bouncer, conn, ctx_name)
123
+ device[ctx_name] = get_stream(ctx_name).pos
124
+ end
125
+ end
126
+ end
127
+
128
+ class BacklogReplay
129
+ include Celluloid
130
+
131
+ def replay(filename, pos, bouncer, conn, ctx_name)
132
+ stream = File.open(filename, 'rb')
133
+ stream.seek(pos)
134
+
135
+ while line = stream.gets
136
+ timestamp, msg = parse_line(line, ctx_name)
137
+ next unless msg
138
+ privmsg = msg.args.first[0] != '#'[0]
139
+ if msg.prefix
140
+ # to this user
141
+ if privmsg
142
+ msg.args[0] = bouncer.nick
127
143
  else
128
- # from user, add prefix
129
- if msg.args.first[0] == '#'[0]
130
- # it's a room, we can just replay
131
- msg.prefix = @bouncer.nick
132
- else
133
- # a one-on-one chat -- every client i've seen doesn't know how to
134
- # display messages from themselves here, so we fake it by just
135
- # adding an arrow and pretending the other user said it. shame.
136
- msg.prefix = msg.args.first
137
- msg.args[0] = @bouncer.nick
138
- msg.args[-1] = "-> #{msg.args.last}"
139
- end
144
+ # do nothing, it's good to send
145
+ end
146
+ else
147
+ # from this user, maybe add prefix
148
+ if privmsg
149
+ # a one-on-one chat -- every client i've seen doesn't know how to
150
+ # display messages from themselves here, so we fake it by just
151
+ # adding an arrow and pretending the other user said it. shame.
152
+ msg.prefix = msg.args.first
153
+ msg.args[0] = bouncer.nick
154
+ msg.args[-1] = "-> #{msg.args.last}"
155
+ else
156
+ # it's a room, we can just replay
157
+ msg.prefix = bouncer.nick
140
158
  end
141
- conn.send_msg(msg.with_timestamp(timestamp))
142
159
  end
143
-
144
- device[ctx_name] = get_stream(ctx_name).pos
160
+ conn.send_msg(msg.with_timestamp(timestamp))
145
161
  end
146
162
  end
147
163
 
@@ -166,4 +182,6 @@ class Backlog
166
182
  end
167
183
  end
168
184
 
185
+ Backlog.replay_pool = BacklogReplay.pool(size: Celluloid.cores * 2)
186
+
169
187
  end
@@ -1,26 +1,30 @@
1
- require 'eventmachine'
1
+ require 'celluloid/io'
2
2
 
3
3
  require 'tkellem/tkellem_bot'
4
4
 
5
5
  module Tkellem
6
6
 
7
7
  # listens on the unix domain socket and executes admin commands
8
- module SocketServer
9
- include EM::Protocols::LineText2
8
+ # TODO: rename this class
9
+ class SocketServer
10
+ include Celluloid::IO
10
11
  include Tkellem::EasyLogger
12
+ include Tkellem::CelluloidTools::LineReader
11
13
 
12
14
  def log_name
13
15
  "admin"
14
16
  end
15
17
 
16
- def post_init
17
- set_delimiter "\n"
18
+ def initialize(socket)
19
+ @socket = socket
20
+ @delimiter = "\n"
21
+ run!
18
22
  end
19
23
 
20
24
  def receive_line(line)
21
25
  trace "admin socket: #{line}"
22
- TkellemBot.run_command(line, nil, nil) do |line|
23
- send_data("#{line}\n")
26
+ TkellemBot.run_command(line, nil, nil) do |outline|
27
+ send_data("#{outline}\n")
24
28
  end
25
29
  send_data("\0\n")
26
30
  rescue => e
@@ -28,6 +32,10 @@ module SocketServer
28
32
  e.backtrace.each { |l| send_data("#{l}\n") }
29
33
  send_data("\0\n")
30
34
  end
35
+
36
+ def send_data(dat)
37
+ @socket.write(dat)
38
+ end
31
39
  end
32
40
 
33
41
  end
@@ -156,7 +156,7 @@ class TkellemBot
156
156
  end
157
157
 
158
158
  def modify
159
- instance = model.first(:conditions => find_attributes)
159
+ instance = model.first(find_attributes)
160
160
  new_record = false
161
161
  if instance
162
162
  instance.attributes = attributes
@@ -181,7 +181,7 @@ class TkellemBot
181
181
  end
182
182
 
183
183
  def remove
184
- instance = model.first(:conditions => find_attributes)
184
+ instance = model.first(find_attributes)
185
185
  if instance
186
186
  instance.destroy
187
187
  respond "Removed #{show(instance)}"
@@ -258,7 +258,7 @@ class TkellemBot
258
258
 
259
259
  if opts['username']
260
260
  if Command.admin_user?(user)
261
- user = User.first(:conditions => { :username => opts['username'] })
261
+ user = User.where(:username => opts['username']).first
262
262
  else
263
263
  raise Command::ArgumentError, "Only admins can change other passwords"
264
264
  end
@@ -303,7 +303,7 @@ class TkellemBot
303
303
 
304
304
  def execute
305
305
  if opts['network'].present? # only settable by admins
306
- target = Network.first(:conditions => ["name = ? AND user_id IS NULL", opts['network'].downcase])
306
+ target = Network.where(:user_id => nil, :name => opts['network'].downcase).first
307
307
  else
308
308
  target = network_user
309
309
  end
@@ -339,7 +339,7 @@ class TkellemBot
339
339
  admin_option('public', '--public', "Create new public network. Once created, public/private status can't be modified.")
340
340
 
341
341
  def list
342
- public_networks = Network.all(:conditions => 'user_id IS NULL')
342
+ public_networks = Network.where(:user_id => nil).all
343
343
  user_networks = user.try(:reload).try(:networks) || []
344
344
  if user_networks.present? && public_networks.present?
345
345
  r "Public networks are prefixed with [P], user-specific networks with [U]."
@@ -355,15 +355,14 @@ class TkellemBot
355
355
  end
356
356
 
357
357
  def execute
358
- # TODO: this got gross
359
358
  if args.empty? && !opts['remove']
360
359
  list
361
360
  return
362
361
  end
363
362
 
364
363
  if opts['network'].present?
365
- target = Network.first(:conditions => ["name = ? AND user_id = ?", opts['network'].downcase, user.try(:id)])
366
- target ||= Network.first(:conditions => ["name = ? AND user_id IS NULL", opts['network'].downcase]) if self.class.admin_user?(user)
364
+ target = Network.where(:name => opts['network'].downcase, :user_id => user.try(:id)).first
365
+ target ||= Network.where(:user_id => nil, :name => opts['network'].downcase).first if self.class.admin_user?(user)
367
366
  else
368
367
  target = network_user.try(:network)
369
368
  if target && target.public? && !self.class.admin_user?(user)
@@ -379,7 +378,7 @@ class TkellemBot
379
378
  raise(Command::ArgumentError, "No network found") unless target
380
379
  raise(Command::ArgumentError, "You must explicitly specify the network to remove") unless opts['network']
381
380
  if uri
382
- target.hosts.first(:conditions => addr_args).try(:destroy)
381
+ target.hosts.where(addr_args).first.try(:destroy)
383
382
  respond " #{show(target)}"
384
383
  else
385
384
  target.destroy
@@ -397,16 +396,9 @@ class TkellemBot
397
396
  end
398
397
  end
399
398
 
400
- target.attributes = { :hosts_attributes => [addr_args] }
401
- target.save
402
- if target.errors.any?
403
- respond "Error:"
404
- target.errors.full_messages.each { |m| respond " #{m}" }
405
- respond " #{show(target)}"
406
- else
407
- respond("updated:")
408
- respond " #{show(target)}"
409
- end
399
+ Host.create(addr_args.merge(network: target))
400
+ respond("updated:")
401
+ respond " #{show(target)}"
410
402
  end
411
403
  end
412
404
  end
@@ -1,46 +1,62 @@
1
- require 'eventmachine'
2
- require 'active_record'
1
+ require 'active_support/core_ext'
2
+ require 'celluloid'
3
+ require 'sequel'
3
4
 
4
- require 'tkellem/bouncer_connection'
5
5
  require 'tkellem/bouncer'
6
-
7
- require 'tkellem/models/host'
8
- require 'tkellem/models/listen_address'
9
- require 'tkellem/models/network'
10
- require 'tkellem/models/network_user'
11
- require 'tkellem/models/setting'
12
- require 'tkellem/models/user'
6
+ require 'tkellem/bouncer_connection'
7
+ require 'tkellem/celluloid_tools'
13
8
 
14
9
  require 'tkellem/plugins/backlog'
15
- require 'tkellem/plugins/push_service'
10
+ #require 'tkellem/plugins/push_service'
16
11
 
17
12
  module Tkellem
18
13
 
19
14
  class TkellemServer
15
+ include Celluloid
20
16
  include Tkellem::EasyLogger
21
17
 
22
- attr_reader :bouncers
18
+ attr_reader :bouncers, :options
19
+
20
+ def self.initialize_database(path)
21
+ Sequel.extension :migration
22
+ db = Sequel.connect({
23
+ :adapter => 'sqlite',
24
+ :database => path,
25
+ })
26
+ migrations_path = File.expand_path("../migrations", __FILE__)
27
+ Sequel::Migrator.apply(db, migrations_path)
28
+
29
+ Sequel::Model.raise_on_save_failure = true
30
+ # Can't load the models until we've connected to the database and migrated
31
+ require 'tkellem/models/host'
32
+ require 'tkellem/models/listen_address'
33
+ require 'tkellem/models/network'
34
+ require 'tkellem/models/network_user'
35
+ require 'tkellem/models/setting'
36
+ require 'tkellem/models/user'
37
+
38
+ db
39
+ end
23
40
 
24
- def initialize
41
+ def initialize(options)
42
+ Celluloid.logger = Tkellem::EasyLogger.logger
43
+ @options = options
25
44
  @listeners = {}
26
- @bouncers = {}
45
+ @bouncers = CelluloidTools::BackoffSupervisor.new_link({})
27
46
  $tkellem_server = self
28
47
 
29
- unless ActiveRecord::Base.connected?
30
- ActiveRecord::Base.establish_connection({
31
- :adapter => 'sqlite3',
32
- :database => File.expand_path("~/.tkellem/tkellem.sqlite3"),
33
- })
34
- ActiveRecord::Migrator.migrate(File.expand_path("../migrations", __FILE__), nil)
35
- end
36
-
37
- ListenAddress.all.each { |a| listen(a) }
38
- NetworkUser.find_each { |nu| add_bouncer(Bouncer.new(nu)) }
39
- Observer.forward_to << self
48
+ @db = self.class.initialize_database(db_file)
40
49
  end
41
50
 
42
- def stop
43
- Observer.forward_to.delete(self)
51
+ def run
52
+ start_unix_server
53
+ ListenAddress.all { |a| listen(a) }
54
+ NetworkUser.all { |nu| add_bouncer(nu) }
55
+
56
+ begin
57
+ loop { sleep 5 }
58
+ rescue Interrupt
59
+ end
44
60
  end
45
61
 
46
62
  # callbacks for AR observer events
@@ -49,7 +65,7 @@ class TkellemServer
49
65
  when ListenAddress
50
66
  listen(obj)
51
67
  when NetworkUser
52
- add_bouncer(Bouncer.new(obj))
68
+ add_bouncer(obj)
53
69
  end
54
70
  end
55
71
 
@@ -57,68 +73,75 @@ class TkellemServer
57
73
  case obj
58
74
  when ListenAddress
59
75
  stop_listening(obj)
60
- # TODO: remove bouncer on NetworkUser.destroy
61
76
  end
62
77
  end
63
78
 
64
- def listen(listen_address)
65
- address = listen_address.address
66
- port = listen_address.port
67
- ssl = listen_address.ssl
79
+ def start_unix_server
80
+ # This file relies on the models being loaded
81
+ # TODO: this is gross
82
+ require 'tkellem/socket_server'
83
+ CelluloidTools::UnixListener.start(socket_file) do |socket|
84
+ SocketServer.new(socket)
85
+ end
86
+ end
68
87
 
88
+ def listen(listen_address)
69
89
  info "Listening on #{listen_address}"
70
90
 
71
- @listeners[listen_address.id] = EM.start_server(listen_address.address,
72
- listen_address.port,
73
- BouncerConnection,
74
- self,
75
- listen_address.ssl)
91
+ if listen_address.ssl
92
+ server_class = CelluloidTools::SSLListener
93
+ else
94
+ server_class = CelluloidTools::TCPListener
95
+ end
96
+
97
+ listener = server_class.start(listen_address.address,
98
+ listen_address.port) do |socket|
99
+ BouncerConnection.new(self, socket).async.run
100
+ end
101
+
102
+ @listeners[listen_address.id] = listener
76
103
  end
77
104
 
78
105
  def stop_listening(listen_address)
79
106
  listener = @listeners[listen_address.id]
80
107
  return unless listener
81
- EM.stop_server(listener)
108
+ listener.terminate
82
109
  info "No longer listening on #{listen_address}"
83
110
  end
84
111
 
85
- def add_bouncer(bouncer)
86
- key = [bouncer.user.id, bouncer.network.name]
87
- raise("bouncer already exists: #{key}") if @bouncers.include?(key)
88
- @bouncers[key] = bouncer
112
+ def add_bouncer(network_user)
113
+ unless network_user.user && network_user.network
114
+ info "Terminating orphan network user #{network_user}"
115
+ network_user.destroy
116
+ return
117
+ end
118
+ key = [network_user.user_id, network_user.network.name]
119
+ raise("bouncer already exists: #{key}") if @bouncers.registry.key?(key)
120
+ @bouncers.supervise_as(key, Bouncer, network_user)
89
121
  end
90
122
 
91
123
  def find_bouncer(user, network_name)
92
124
  key = [user.id, network_name]
93
- bouncer = @bouncers[key]
125
+ bouncer = @bouncers.registry[key]
94
126
  if !bouncer
95
127
  # find the public network with this name, and attempt to auto-add this user to it
96
- network = Network.first(:conditions => { :user_id => nil, :name => network_name })
128
+ network = Network.first({ :user_id => nil, :name => network_name })
97
129
  if network
98
- nu = NetworkUser.create!(:user => user, :network => network)
130
+ NetworkUser.create(:user => user, :network => network)
99
131
  # AR callback should create the bouncer in sync
100
- bouncer = @bouncers[key]
132
+ bouncer = @bouncers.registry[key]
101
133
  end
102
134
  end
103
135
  bouncer
104
136
  end
105
137
 
106
- class Observer < ActiveRecord::Observer
107
- observe 'Tkellem::ListenAddress', 'Tkellem::NetworkUser'
108
- cattr_accessor :forward_to
109
- self.forward_to = []
110
-
111
- def after_create(obj)
112
- forward_to.each { |f| f.after_create(obj) }
113
- end
114
-
115
- def after_destroy(obj)
116
- forward_to.each { |f| f.after_destroy(obj) }
117
- end
138
+ def socket_file
139
+ File.join(options[:path], 'tkellem.socket')
118
140
  end
119
141
 
120
- ActiveRecord::Base.observers = Observer
121
- ActiveRecord::Base.instantiate_observers
142
+ def db_file
143
+ File.join(options[:path], 'tkellem.sqlite3')
144
+ end
122
145
  end
123
146
 
124
147
  end
@@ -1,3 +1,3 @@
1
1
  module Tkellem
2
- VERSION = "0.8.11"
2
+ VERSION = "0.9.0.beta1"
3
3
  end
data/lib/tkellem.rb CHANGED
@@ -21,7 +21,7 @@ module Tkellem
21
21
  @trace = val
22
22
  end
23
23
  def self.trace
24
- @trace || @trace = false
24
+ @trace || @trace = true
25
25
  end
26
26
 
27
27
  def log_name
@@ -32,15 +32,6 @@ module Tkellem
32
32
  puts("TRACE: #{log_name}: #{msg}") if EasyLogger.trace
33
33
  end
34
34
 
35
- def failsafe(event)
36
- yield
37
- rescue => e
38
- # if the failsafe rescue fails, we're in a really bad state and should probably just die
39
- self.error "exception while handling #{event}"
40
- self.error e.to_s
41
- (e.backtrace || []).each { |line| self.error line }
42
- end
43
-
44
35
  ::Logger::Severity.constants.each do |level|
45
36
  next if level == "UNKNOWN"
46
37
  module_eval(<<-EVAL, __FILE__, __LINE__)
data/spec/spec_helper.rb CHANGED
@@ -5,30 +5,17 @@ require 'tkellem'
5
5
  require 'rspec'
6
6
 
7
7
  Tkellem::EasyLogger.logger = Logger.new("test.log")
8
- ActiveRecord::Base.logger = Tkellem::EasyLogger.logger
9
8
 
10
- ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
11
- ActiveRecord::Migration.verbose = false
12
- ActiveRecord::Migrator.migrate(File.expand_path("../../lib/tkellem/migrations", __FILE__), nil)
9
+ TestDB = Tkellem::TkellemServer.initialize_database(':memory:')
13
10
 
14
11
  RSpec.configure do |config|
15
- config.before(:each) do
16
- ActiveRecord::Base.connection.increment_open_transactions
17
- ActiveRecord::Base.connection.begin_db_transaction
18
- end
19
-
20
- config.after(:each) do
21
- ActiveRecord::Base.connection.rollback_db_transaction
22
- ActiveRecord::Base.connection.decrement_open_transactions
12
+ config.around(:each) do |block|
13
+ TestDB.transaction(:rollback => :always) do
14
+ block.run()
15
+ end
23
16
  end
24
17
 
25
18
  def m(line)
26
19
  IrcMessage.parse(line)
27
20
  end
28
-
29
- def em(mod)
30
- c = Class.new
31
- c.send(:include, mod)
32
- c
33
- end
34
21
  end