whatup 0.3.4 → 0.3.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9a2ccfe87d331ab0f316dce065f81de75aef2f2d870a1f83ed467d05b52ebdc
4
- data.tar.gz: 990c58f0f6eaf19fe4032c33493d3f18ea7359318e182c15fb14e06390531b1a
3
+ metadata.gz: 4f618bbf5b44743049617a2a4af44444716b2f5caf2f6b698dfded679bd1eff5
4
+ data.tar.gz: 803106014c08fb0d54163d559f7568597c45726c74ba96654b6074d8a4ed27d1
5
5
  SHA512:
6
- metadata.gz: 6a7025e077088cfda1f83ac5587be14628e07deb4c91eb01a66e884baed2523f563b6dcb329d02e408f984ad5aa42d2bb76de8755d060a9aa2f9dd1f86c8d90b
7
- data.tar.gz: 8f0c1ae31f64922a223d23ffcd3e2d8a2555b6cb1e029505cb4389ff0bc0832e1cf406751486195792684fc7ecea654b4906aa08a5ae7ee649356dfa8745efca
6
+ metadata.gz: 3ad1f0ba64cc60f7962fad49e09fa7e926560b7d84c993e4b3550d5cdb41a0173b25f59e0302d54542b67ec9a275faeea04af8418a42d2796ee2ce16eba1d23d
7
+ data.tar.gz: 239e524850f5ca8c8c2591b23a7ed6fa7969ae6847c55c18e4e4e567e3849b5cec7cb8fe986c06eaf2ce8496b0d6217a3a698b99eec537df08b4e98f6c75b36c
@@ -32,7 +32,7 @@ Style/CommentedKeyword:
32
32
 
33
33
  # The default is a bit restrictive
34
34
  Metrics/AbcSize:
35
- Max: 30
35
+ Max: 40
36
36
 
37
37
  # The default is a bit restrictive
38
38
  Metrics/ClassLength:
@@ -43,12 +43,13 @@ Lint/AmbiguousRegexpLiteral:
43
43
  Enabled: false
44
44
 
45
45
  Metrics/BlockLength:
46
+ Max: 40
46
47
  Exclude:
47
48
  - 'whatup.gemspec'
48
49
  - 'spec/**/*_spec.rb'
49
50
 
50
51
  Metrics/MethodLength:
51
- Max: 20 # 10 is a bit too low
52
+ Max: 40 # 10 is a bit too low
52
53
 
53
54
  # Don't type unneccesary ()
54
55
  Style/MethodDefParentheses:
data/README.md CHANGED
@@ -28,8 +28,8 @@ brief instructions.
28
28
  $ whatup
29
29
 
30
30
  Commands:
31
+ whatup -v, --version # Output the version
31
32
  whatup client ... # Perform client commands
32
- whatup hello # Says hello
33
33
  whatup help [COMMAND] # Describe available commands or one specific command
34
34
  whatup server ... # Perform server commands
35
35
  ```
@@ -11,9 +11,15 @@ module Whatup
11
11
  module CLI
12
12
  # Top-level command class
13
13
  class CLI < Thor
14
- desc 'hello', 'Says hello'
15
- def hello
16
- say "Hello!\n", :cyan
14
+ map %w[-v --version] => :version
15
+ option :version,
16
+ aliases: '-v',
17
+ type: :boolean,
18
+ desc: 'Show version',
19
+ default: true
20
+ desc '-v, --version', 'Output the version'
21
+ def version
22
+ say Whatup::VERSION
17
23
  end
18
24
 
19
25
  # Subcommands are defined below, but are implemented in `commands/`
@@ -10,6 +10,7 @@ module Whatup
10
10
  extend ThorInteractive
11
11
 
12
12
  Room = Whatup::Server::Room
13
+ Message = Whatup::Server::Message
13
14
  Client = Whatup::Server::Client
14
15
 
15
16
  desc 'msg [NAME]', 'Send a message to [NAME]'
@@ -29,17 +30,14 @@ module Whatup
29
30
  say "That user doesn't exist!"
30
31
  end
31
32
 
32
- desc 'list', 'List your received messages'
33
+ desc 'list', 'List your direct messages'
33
34
  def list
34
35
  say 'Your direct messages:'
35
- msgs = current_user.received_messages.map do |msg|
36
- <<~MSG
37
- From: #{msg.sender.name}
38
-
39
- #{msg.content}
40
- MSG
41
- end.join('-' * 10)
42
- say msgs
36
+ say \
37
+ Message.where(sender: current_user)
38
+ .or(Message.where(recipient: current_user))
39
+ .map(&:to_s)
40
+ .join('-' * 10)
43
41
  end
44
42
  end
45
43
  end
@@ -51,7 +51,7 @@ module Whatup
51
51
  end
52
52
 
53
53
  desc 'room [NAME]', 'Create and enter chatroom [NAME]'
54
- def room name # rubocop:disable Metrics/AbcSize
54
+ def room name
55
55
  if room = Room.find_by(name: name)
56
56
  current_user.puts <<~MSG
57
57
  Entering #{room.name}... enjoy your stay!
@@ -84,23 +84,6 @@ module Whatup
84
84
  MSG
85
85
  end
86
86
 
87
- desc 'dm [NAME]', 'Send a direct message to [NAME]'
88
- def dm name
89
- if recepient = Client.find_by(name: name)
90
- say <<~MSG
91
- Sending a direct message to #{name}...
92
-
93
- The message can span multiple lines.
94
-
95
- Type `.exit` when you're ready to send it.
96
- MSG
97
- current_user.composing_dm = recepient
98
- return
99
- end
100
-
101
- say "That user doesn't exist!"
102
- end
103
-
104
87
  desc 'exit', 'Closes your connection with the server'
105
88
  def exit
106
89
  current_user.exit!
@@ -8,12 +8,21 @@ module Whatup
8
8
  module CLI
9
9
  # Server commands
10
10
  class Server < Thor
11
- option :port, type: :numeric, default: 9_001
11
+ option :port,
12
+ type: :numeric,
13
+ default: 9_001,
14
+ desc: 'The port to run the server on'
15
+ option :verbose,
16
+ aliases: '-v',
17
+ type: :boolean,
18
+ desc: 'Enable verbose output',
19
+ default: false
12
20
  desc 'start', 'Starts a server instance'
13
21
  long_desc <<~DESC
14
22
  Starts a server instance running locally on the specified port.
15
23
  DESC
16
24
  def start
25
+ ENV['WHATUP_LOG_LEVEL'] = 'DEBUG' if options[:verbose]
17
26
  Whatup::Server::Server.new(port: options[:port]).start
18
27
  end
19
28
  end
@@ -2,35 +2,43 @@
2
2
 
3
3
  require 'whatup'
4
4
  require 'whatup/server/redirection'
5
+ require 'whatup/server/logger'
5
6
 
6
7
  module Whatup
7
8
  module Server
8
9
  module DbInit
9
10
  extend Redirection
11
+ extend WhatupLogger
10
12
 
11
13
  class << self
12
- # Sets up our database, deleting all existing data.
13
14
  def setup_db!
15
+ log.debug { 'Setting up database ...' }
16
+
14
17
  db = "#{Dir.home}/.whatup.db"
15
- SQLite3::Database.new(db) unless File.exist?(db)
18
+
19
+ if File.exist?(db)
20
+ log.debug { "Using existing database `#{db}" }
21
+ else
22
+ log.debug { "Creating new database `#{db}" }
23
+ SQLite3::Database.new(db)
24
+ end
16
25
 
17
26
  ActiveRecord::Base.establish_connection adapter: 'sqlite3',
18
27
  database: db
19
28
 
20
- ActiveRecord::Base.connection.execute <<~SQL
29
+ truncate_sql = <<~SQL
21
30
  DROP TABLE IF EXISTS clients_rooms;
22
31
  DROP TABLE IF EXISTS clients;
23
32
  DROP TABLE IF EXISTS messages;
24
33
  DROP TABLE IF EXISTS rooms;
25
34
  SQL
35
+ log.debug { "Truncating existing data ...\n#{truncate_sql}" }
36
+ ActiveRecord::Base.connection.execute truncate_sql
26
37
 
27
- if Whatup.testing?
28
- # We silence output here, so that tests don't get cluttered
29
- redirect(stdout: StringIO.new) { create_tables! }
30
- return
38
+ StringIO.new.tap do |io|
39
+ redirect(stdout: io) { create_tables! }
40
+ log.debug { "Creating tables ...\n#{io&.string}" }
31
41
  end
32
-
33
- create_tables!
34
42
  end
35
43
 
36
44
  private
@@ -40,14 +48,17 @@ module Whatup
40
48
  create_table :clients, force: true do |t|
41
49
  t.string :name
42
50
  t.references :room
51
+ t.timestamps
43
52
  end
44
53
  create_table :messages, force: true do |t|
45
54
  t.string :content
46
55
  t.references :sender
47
56
  t.references :recipient
57
+ t.timestamps
48
58
  end
49
59
  create_table :rooms, force: true do |t|
50
60
  t.string :name
61
+ t.timestamps
51
62
  end
52
63
  end
53
64
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+ require 'logger'
5
+
6
+ require 'colorize'
7
+
8
+ class Logger
9
+ class Formatter
10
+ def call severity, time, _progname, msg
11
+ color = case severity
12
+ when 'DEBUG' then :light_magenta
13
+ when 'INFO' then :light_green
14
+ when 'WARN' then :light_cyan
15
+ when 'ERROR' then :light_red
16
+ when 'FATAL', 'UNKNOWN' then :red
17
+ end
18
+
19
+ "[#{severity.ljust(5, ' ').colorize color}][#{time}]: #{msg}\n"
20
+ end
21
+ end
22
+ end
23
+
24
+ module WhatupLogger
25
+ # Access a logger, to stdout (for now).
26
+ #
27
+ # Uses logging level ENV['WHATUP_LOG_LEVEL'], which can be WARN, INFO, etc
28
+ def log
29
+ Logger.new(STDOUT).tap do |logger|
30
+ if Logger.constants.map(&:to_s).include? ENV['WHATUP_LOG_LEVEL']
31
+ logger.level = Logger.const_get ENV['WHATUP_LOG_LEVEL']
32
+ else
33
+ logger.level = Logger::INFO
34
+ logger.level = 9001 if Whatup.testing?
35
+ end
36
+ end
37
+ end
38
+ end
@@ -21,7 +21,7 @@ module Whatup
21
21
  end
22
22
 
23
23
  def gets
24
- socket.gets
24
+ socket&.gets
25
25
  end
26
26
 
27
27
  def input!
@@ -55,6 +55,10 @@ module Whatup
55
55
  @deleted = true
56
56
  destroy!
57
57
  end
58
+
59
+ def to_s
60
+ name
61
+ end
58
62
  end
59
63
  end
60
64
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'tzinfo'
4
+
3
5
  require 'whatup/server/models/application_record'
4
6
 
5
7
  module Whatup
@@ -7,6 +9,20 @@ module Whatup
7
9
  class Message < ApplicationRecord
8
10
  belongs_to :recipient, class_name: 'Client'
9
11
  belongs_to :sender, class_name: 'Client', foreign_key: 'sender_id'
12
+
13
+ TZ = TZInfo::Timezone.get 'America/Detroit' # Central time
14
+
15
+ def to_s
16
+ <<~MSG.gsub '.exit', ''
17
+ ------------------------------------------------------------
18
+ From: #{sender.name}
19
+ To: #{recipient.name}
20
+ Date: #{TZ.utc_to_local(created_at).to_s :db}
21
+
22
+ #{content&.chomp}
23
+ ------------------------------------------------------------
24
+ MSG
25
+ end
10
26
  end
11
27
  end
12
28
  end
@@ -9,6 +9,7 @@ require 'active_record'
9
9
  require 'active_support/core_ext/object/blank'
10
10
 
11
11
  require 'whatup/server/db_init'
12
+ require 'whatup/server/logger'
12
13
  require 'whatup/server/redirection'
13
14
  require 'whatup/server/models/client'
14
15
  require 'whatup/server/models/message'
@@ -18,9 +19,9 @@ require 'whatup/cli/commands/interactive/interactive'
18
19
  module Whatup
19
20
  module Server
20
21
  class Server # rubocop:disable Metrics/ClassLength
21
- include Thor::Shell
22
22
  include DbInit
23
23
  include Redirection
24
+ include WhatupLogger
24
25
 
25
26
  Client = Whatup::Server::Client
26
27
 
@@ -49,7 +50,7 @@ module Whatup
49
50
  # The server continuously loops, and handle each new client in a separate
50
51
  # thread.
51
52
  def start
52
- say "Starting a server with PID:#{@pid} @ #{@address} ... \n", :green
53
+ log.info { "Starting a server with PID:#{@pid} @ #{@address} ... \n" }
53
54
 
54
55
  exit_if_pid_exists!
55
56
  connect_to_socket!
@@ -57,7 +58,10 @@ module Whatup
57
58
 
58
59
  # Listen for connections, then accept each in a separate thread
59
60
  loop do
60
- Thread.new(@socket.accept) { |client| handle_client client }
61
+ Thread.new(@socket.accept) do |client|
62
+ log.info { "Accepted new client: #{client.inspect}" }
63
+ handle_client client
64
+ end
61
65
  end
62
66
  rescue SignalException # In case of ^c
63
67
  kill
@@ -76,9 +80,9 @@ module Whatup
76
80
  #
77
81
  # @return [Whatup::Server::Room] The created room
78
82
  def new_room! clients: [], name:
79
- room = Room.create! name: name, clients: clients
80
- @rooms << room
81
- room
83
+ Room.create!(name: name, clients: clients).tap do |room|
84
+ @rooms << room
85
+ end
82
86
  end
83
87
 
84
88
  private
@@ -87,15 +91,22 @@ module Whatup
87
91
  #
88
92
  # @param client [Whatup::Server::Client] The client
89
93
  #
90
- # rubocop:disable Metrics/MethodLength
91
- def handle_client client
94
+ def handle_client client # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/LineLength
92
95
  client = create_new_client_if_not_existing! client
93
96
 
94
97
  # Loop forever to maintain the connection
95
98
  loop do
96
99
  @clients.reject! &:deleted
97
100
 
98
- Thread.current.exit if client.deleted
101
+ if client.deleted
102
+ log.debug do
103
+ <<~OUT
104
+ Client `#{client.name}` has been deleted.
105
+ #{'Killing'.colorize :red} this thread."
106
+ OUT
107
+ end
108
+ Thread.current.exit
109
+ end
99
110
 
100
111
  if client.composing_dm?
101
112
  handle_dm client
@@ -106,28 +117,31 @@ module Whatup
106
117
  # Wait until we get a valid command. This takes as long as the client
107
118
  # takes.
108
119
  msg = client.input! unless Whatup::CLI::Interactive.command?(msg)
120
+ log.info { "#{client.name.colorize :light_blue}> #{msg}" }
109
121
 
110
- puts "#{client.name}> #{msg}"
111
-
112
- begin
113
- # Send the output to the client
114
- redirect stdin: client.socket, stdout: client.socket do
122
+ # Send the output to the client
123
+ redirect stdin: client.socket, stdout: client.socket do
124
+ begin
115
125
  # Invoke the cli using the provided commands and options.
116
126
  run_thor_command! client: client, msg: msg
127
+ rescue RuntimeError,
128
+ ArgumentError,
129
+ Thor::InvocationError,
130
+ Thor::UndefinedCommandError => e
131
+ log.info do
132
+ "#{client.name.colorize :red}> #{e.class}: #{e.message}"
133
+ end
134
+ client.puts case e.class.to_s
135
+ when 'RuntimeError'
136
+ 'Invalid input or unknown command'
137
+ else
138
+ e.message
139
+ end
117
140
  end
118
- rescue RuntimeError,
119
- Thor::InvocationError,
120
- Thor::UndefinedCommandError => e
121
- puts e.message
122
- client.puts 'Invalid input or unknown command'
123
- rescue ArgumentError => e
124
- puts e.message
125
- client.puts e.message
126
141
  end
127
142
  msg = nil
128
143
  end
129
144
  end
130
- # rubocop:enable Metrics/MethodLength
131
145
 
132
146
  # Handles inputing direct messages
133
147
  #
@@ -137,18 +151,20 @@ module Whatup
137
151
  msg = StringIO.new
138
152
  loop do
139
153
  input = client.input!
140
- puts "#{client.name}> #{input}"
154
+ log.info { "#{client.name.colorize :light_blue}> #{input}" }
141
155
  msg.puts input
142
156
  if input == '.exit'
143
157
  client.puts "Finished dm to `#{client.composing_dm.name}`."
144
158
  break
145
159
  end
146
160
  end
147
- client.composing_dm
148
- .received_messages << Message.new(
149
- sender: client,
150
- content: msg.string
151
- )
161
+ Message.create!(sender: client, content: msg.string).tap do |m|
162
+ client.composing_dm.received_messages << m
163
+ log.debug do
164
+ "Created new message (id = #{m.id}) from `#{client}` to" \
165
+ "`#{client.composing_dm}`"
166
+ end
167
+ end
152
168
  client.composing_dm = nil
153
169
  end
154
170
 
@@ -163,7 +179,7 @@ module Whatup
163
179
  .select do |c|
164
180
  client.room.clients.pluck(:id).include? c.id
165
181
  end
166
- puts "#{client.name}> #{input}"
182
+ log.info { "#{client.name.colorize :light_blue}> #{input}" }
167
183
  if input == '.exit'
168
184
  client.puts "Exited `#{client.room.name}`."
169
185
  audience.each { |c| c.puts "#{client.name}> LEFT" }
@@ -185,7 +201,18 @@ module Whatup
185
201
  #
186
202
  # @return [Whatup::Server::Client] The created client
187
203
  def create_new_client_if_not_existing! client
188
- name = client.gets.chomp
204
+ log.debug { 'Creating new client' }
205
+
206
+ name = client.gets&.chomp
207
+
208
+ if name.nil?
209
+ log.debug do
210
+ 'New client (currently unknown) has left. ' \
211
+ "#{'Killing'.colorize :red} this thread."
212
+ end
213
+ Thread.current.exit
214
+ end
215
+
189
216
  rand_num = SecureRandom.random_number(100).to_s.rjust 3, '0'
190
217
  name = name == '' ? "ANON-#{rand_num}" : name
191
218
 
@@ -193,6 +220,10 @@ module Whatup
193
220
  client.puts 'That name is taken! Goodbye.'
194
221
  client.puts 'END'
195
222
  client.close
223
+ log.debug do
224
+ "Existing name `#{name}` entered. " \
225
+ "#{'Killing'.colorize :red} this thread"
226
+ end
196
227
  Thread.current.exit
197
228
  end
198
229
 
@@ -200,16 +231,17 @@ module Whatup
200
231
  name: name,
201
232
  socket: client
202
233
  )
234
+ log.info { "Created new client `#{name}` ..." }
203
235
 
204
- puts "#{client.name} just showed up!"
205
- client.puts <<~MSG
206
- Hello, #{client.name}!
236
+ client.tap do |c|
237
+ c.puts <<~MSG
238
+ Hello, #{client.name}!
207
239
 
208
- Welcome to whatup.
240
+ Welcome to whatup.
209
241
 
210
- To get started, type `help`.
211
- MSG
212
- client
242
+ To get started, type `help`.
243
+ MSG
244
+ end
213
245
  end
214
246
 
215
247
  # Initialize a new cli class using the initial command and options,
@@ -233,9 +265,11 @@ module Whatup
233
265
 
234
266
  # Kills the server if a PID for this app exists
235
267
  def exit_if_pid_exists!
268
+ log.debug { "Checking if `#{@pid}` exists ..." }
269
+
236
270
  return unless running?
237
271
 
238
- say <<~EXIT, :cyan
272
+ log.info <<~EXIT
239
273
  A server appears to already be running!
240
274
  Check `#{@pid_file}`.
241
275
  EXIT
@@ -246,14 +280,18 @@ module Whatup
246
280
  # Connect a new socket for this server to start listening on the specified
247
281
  # address and port.
248
282
  def connect_to_socket!
283
+ log.info do
284
+ "#{'Opening'.colorize :blue} TCP socket at `#{@ip}:#{@port}`"
285
+ end
249
286
  @socket = TCPServer.open @ip, @port
250
287
  rescue Errno::EADDRINUSE
251
- puts 'Address already in use!'
288
+ log.error "Address `#{@ip}:#{@port}` is already in use!"
252
289
  kill
253
290
  end
254
291
 
255
292
  # Write this process's PID to the PID file
256
293
  def write_pid!
294
+ log.debug { "Writing PID to `#{@pid}`" }
257
295
  File.open(@pid_file, 'w') { |f| f.puts Process.pid }
258
296
  end
259
297
 
@@ -264,8 +302,12 @@ module Whatup
264
302
 
265
303
  # Kills the server and removes the PID file
266
304
  def kill
267
- say "Killing the server with PID:#{Process.pid} ...", :red
305
+ log.info do
306
+ "#{'Killing'.colorize :red} the server with " \
307
+ "PID:#{Process.pid} ..."
308
+ end
268
309
  FileUtils.rm_rf @pid_file
310
+ log.debug { "Removed `#{@pid_file}`." }
269
311
  exit
270
312
  end
271
313
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Whatup
4
- VERSION = '0.3.4'
4
+ VERSION = '0.3.5'
5
5
  end
@@ -67,7 +67,9 @@ Gem::Specification.new do |spec|
67
67
  'activesupport' => '~> 5.2',
68
68
  'thor' => '~> 0.20.3',
69
69
  'sqlite3' => '~> 1.4',
70
- 'activerecord' => '~> 5.2'
70
+ 'activerecord' => '~> 5.2',
71
+ 'tzinfo' => '~> 1.2',
72
+ 'colorize' => '~> 0.8.1'
71
73
  }.each do |gem, version|
72
74
  spec.add_dependency gem, version
73
75
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: whatup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Delk
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-04-12 00:00:00.000000000 Z
11
+ date: 2019-04-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -178,6 +178,34 @@ dependencies:
178
178
  - - "~>"
179
179
  - !ruby/object:Gem::Version
180
180
  version: '5.2'
181
+ - !ruby/object:Gem::Dependency
182
+ name: tzinfo
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '1.2'
188
+ type: :runtime
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '1.2'
195
+ - !ruby/object:Gem::Dependency
196
+ name: colorize
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: 0.8.1
202
+ type: :runtime
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: 0.8.1
181
209
  description: |
182
210
  whatup is a simple server-based instant messaging application using TCP
183
211
  sockets.
@@ -221,6 +249,7 @@ files:
221
249
  - lib/whatup/cli/thor_interactive.rb
222
250
  - lib/whatup/client/client.rb
223
251
  - lib/whatup/server/db_init.rb
252
+ - lib/whatup/server/logger.rb
224
253
  - lib/whatup/server/models/application_record.rb
225
254
  - lib/whatup/server/models/client.rb
226
255
  - lib/whatup/server/models/message.rb