torquebox-backstage 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +6 -1
- data/Gemfile +3 -2
- data/Gemfile.lock +22 -20
- data/README.md +3 -1
- data/Rakefile +2 -2
- data/TODO +1 -1
- data/TORQUEBOX_VERSION +1 -1
- data/VERSION +1 -1
- data/backstage.rb +12 -6
- data/config/torquebox.yml +1 -0
- data/lib/apps/models/app.rb +17 -0
- data/lib/apps/routes.rb +29 -1
- data/lib/destinations/models/destination.rb +12 -2
- data/lib/destinations/routes.rb +13 -1
- data/lib/has_mbean.rb +6 -1
- data/lib/helpers.rb +92 -15
- data/lib/logs.rb +18 -0
- data/lib/logs/models/log.rb +81 -0
- data/lib/logs/routes.rb +18 -0
- data/lib/message_processors/models/message_processor.rb +10 -0
- data/lib/resource_helpers.rb +4 -2
- data/lib/runtimes/models/job.rb +10 -0
- data/lib/services/models/service.rb +10 -0
- data/spec/api_spec.rb +2 -2
- data/spec/app_spec.rb +59 -0
- data/spec/data/jbosslogs/boot.log +177 -0
- data/spec/data/jbosslogs/error.log +177 -0
- data/spec/data/railsapp/log/development.log +80 -0
- data/spec/data/railsapp/log/production.log +0 -0
- data/spec/log_spec.rb +53 -0
- data/spec/spec_helper.rb +11 -2
- data/views/apps/index.haml +2 -1
- data/views/apps/show.haml +1 -0
- data/views/css/style.sass +39 -1
- data/views/dashboard/index.haml +96 -0
- data/views/layout.haml +4 -2
- data/views/logs/index.haml +14 -0
- data/views/logs/show.haml +15 -0
- metadata +52 -27
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,11 @@
|
|
1
|
+
* 0.3.0 - 2011-04-29
|
2
|
+
* you can now tail application and jboss logs
|
3
|
+
* added a dashboard
|
4
|
+
* updated to use the TorqueBox 1.0.0.Final gems
|
5
|
+
|
1
6
|
* 0.2.1 - 2011-04-27
|
2
7
|
* The deployment descriptor name was changed from backstage-knob.yml to torquebox-backstage-knob.yml
|
3
|
-
|
8
|
+
|
4
9
|
* 0.2.0 - 2011-04-26
|
5
10
|
* if the queue isn't available via jndi, just log an error instead of raising
|
6
11
|
* updated to use the TorqueBox 1.0.0.CR2 gems
|
data/Gemfile
CHANGED
@@ -3,9 +3,10 @@ source :rubygems
|
|
3
3
|
gem "sinatra", "1.1.2"
|
4
4
|
gem "rack-flash"
|
5
5
|
gem 'haml', '~>3.0'
|
6
|
-
gem
|
6
|
+
gem 'sass', '~>3.0'
|
7
|
+
gem "tobias-jmx", '0.8'
|
7
8
|
gem 'json'
|
8
|
-
gem 'torquebox', '1.0.0
|
9
|
+
gem 'torquebox', '1.0.0'
|
9
10
|
gem 'tobias-sinatra-url-for'
|
10
11
|
gem 'rack-accept'
|
11
12
|
|
data/Gemfile.lock
CHANGED
@@ -3,12 +3,11 @@ GEM
|
|
3
3
|
specs:
|
4
4
|
diff-lcs (1.1.2)
|
5
5
|
git (1.2.5)
|
6
|
-
haml (3.
|
6
|
+
haml (3.1.1)
|
7
7
|
jeweler (1.5.2)
|
8
8
|
bundler (~> 1.0.0)
|
9
9
|
git (>= 1.2.5)
|
10
10
|
rake
|
11
|
-
jmx (0.7)
|
12
11
|
json (1.5.1-java)
|
13
12
|
rack (1.2.2)
|
14
13
|
rack-accept (0.4.3)
|
@@ -26,28 +25,30 @@ GEM
|
|
26
25
|
rspec-expectations (2.5.0)
|
27
26
|
diff-lcs (~> 1.1.2)
|
28
27
|
rspec-mocks (2.5.0)
|
28
|
+
sass (3.1.1)
|
29
29
|
sinatra (1.1.2)
|
30
30
|
rack (~> 1.1)
|
31
31
|
tilt (~> 1.2)
|
32
32
|
thor (0.14.6)
|
33
|
-
tilt (1.
|
33
|
+
tilt (1.3)
|
34
|
+
tobias-jmx (0.8)
|
34
35
|
tobias-sinatra-url-for (0.2.1)
|
35
36
|
sinatra (>= 0.9.1.1)
|
36
|
-
torquebox (1.0.0
|
37
|
-
torquebox-base (= 1.0.0
|
38
|
-
torquebox-messaging (= 1.0.0
|
39
|
-
torquebox-naming (= 1.0.0
|
40
|
-
torquebox-rake-support (= 1.0.0
|
41
|
-
torquebox-vfs (= 1.0.0
|
42
|
-
torquebox-web (= 1.0.0
|
43
|
-
torquebox-base (1.0.0
|
44
|
-
torquebox-messaging (1.0.0
|
45
|
-
torquebox-base (= 1.0.0
|
46
|
-
torquebox-naming (= 1.0.0
|
47
|
-
torquebox-naming (1.0.0
|
48
|
-
torquebox-rake-support (1.0.0
|
49
|
-
torquebox-vfs (1.0.0
|
50
|
-
torquebox-web (1.0.0
|
37
|
+
torquebox (1.0.0)
|
38
|
+
torquebox-base (= 1.0.0)
|
39
|
+
torquebox-messaging (= 1.0.0)
|
40
|
+
torquebox-naming (= 1.0.0)
|
41
|
+
torquebox-rake-support (= 1.0.0)
|
42
|
+
torquebox-vfs (= 1.0.0)
|
43
|
+
torquebox-web (= 1.0.0)
|
44
|
+
torquebox-base (1.0.0-java)
|
45
|
+
torquebox-messaging (1.0.0-java)
|
46
|
+
torquebox-base (= 1.0.0)
|
47
|
+
torquebox-naming (= 1.0.0)
|
48
|
+
torquebox-naming (1.0.0-java)
|
49
|
+
torquebox-rake-support (1.0.0)
|
50
|
+
torquebox-vfs (1.0.0-java)
|
51
|
+
torquebox-web (1.0.0-java)
|
51
52
|
watchr (0.7)
|
52
53
|
|
53
54
|
PLATFORMS
|
@@ -56,14 +57,15 @@ PLATFORMS
|
|
56
57
|
DEPENDENCIES
|
57
58
|
haml (~> 3.0)
|
58
59
|
jeweler
|
59
|
-
jmx (= 0.7)
|
60
60
|
json
|
61
61
|
rack-accept
|
62
62
|
rack-flash
|
63
63
|
rack-test
|
64
64
|
rspec
|
65
|
+
sass (~> 3.0)
|
65
66
|
sinatra (= 1.1.2)
|
66
67
|
thor
|
68
|
+
tobias-jmx (= 0.8)
|
67
69
|
tobias-sinatra-url-for
|
68
|
-
torquebox (= 1.0.0
|
70
|
+
torquebox (= 1.0.0)
|
69
71
|
watchr
|
data/README.md
CHANGED
@@ -100,7 +100,9 @@ Returns:
|
|
100
100
|
"topics":"http://localhost:8080/backstage/topics?format=json",
|
101
101
|
"message_processors":"http://localhost:8080/backstage/message_processors?format=json",
|
102
102
|
"jobs":"http://localhost:8080/backstage/jobs?format=json",
|
103
|
-
"services":"http://localhost:8080/backstage/services?format=json"
|
103
|
+
"services":"http://localhost:8080/backstage/services?format=json",
|
104
|
+
"pools":"http://localhost:8080/backstage/pools?format=json",
|
105
|
+
"logs":"http://localhost:8080/backstage/logs?format=json"
|
104
106
|
}
|
105
107
|
}
|
106
108
|
|
data/Rakefile
CHANGED
@@ -16,8 +16,8 @@ Jeweler::Tasks.new do |gem|
|
|
16
16
|
gem.name = "torquebox-backstage"
|
17
17
|
gem.homepage = "http://github.com/torquebox/backstage"
|
18
18
|
gem.license = "MIT"
|
19
|
-
gem.summary = %Q{BackStage - Queue/Topic/Job viewer for TorqueBox}
|
20
|
-
gem.description = %Q{BackStage
|
19
|
+
gem.summary = %Q{BackStage - Queue/Topic/Job/etc viewer for TorqueBox}
|
20
|
+
gem.description = %Q{BackStage allows you to look behind the TorqueBox curtain, and view information about all of the components you have running. It includes support for remote code execution and log tailing to aid in debugging.}
|
21
21
|
gem.email = "tcrawley@redhat.com"
|
22
22
|
gem.authors = ["Tobias Crawley"]
|
23
23
|
gem.files = FileList["[A-Z]*", 'backstage.rb', 'config.ru', 'bin/*.rb', "{config,lib,spec,views}/**/*"]
|
data/TODO
CHANGED
data/TORQUEBOX_VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.0
|
1
|
+
1.0.0
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/backstage.rb
CHANGED
@@ -34,24 +34,26 @@ require 'apps'
|
|
34
34
|
require 'destinations'
|
35
35
|
require 'message_processors'
|
36
36
|
require 'jobs'
|
37
|
+
require 'logs'
|
37
38
|
require 'services'
|
38
39
|
|
39
40
|
|
40
|
-
|
41
|
+
Backstage.logger.warn "ENV['REQUIRE_AUTHENTICATION'] is not set, *disabling* authentication" unless ENV['REQUIRE_AUTHENTICATION']
|
41
42
|
|
42
43
|
module Backstage
|
43
44
|
BACKSTAGE_VERSION = File.readlines( File.join( File.dirname( __FILE__ ), 'VERSION' ) ).first.strip
|
44
45
|
TORQUEBOX_VERSION = File.readlines( File.join( File.dirname( __FILE__ ), 'TORQUEBOX_VERSION' ) ).first.strip
|
45
46
|
|
46
47
|
class Application < Sinatra::Base
|
47
|
-
enable :
|
48
|
+
enable :sessions
|
48
49
|
use Rack::Accept
|
49
50
|
use Rack::Flash
|
50
|
-
|
51
|
+
use Rack::CommonLogger, Backstage.logger
|
52
|
+
|
51
53
|
include Backstage::Authentication
|
52
54
|
|
53
55
|
set :views, Proc.new { File.join( File.dirname( __FILE__ ), "views" ) }
|
54
|
-
|
56
|
+
|
55
57
|
before do
|
56
58
|
require_authentication if ENV['REQUIRE_AUTHENTICATION']
|
57
59
|
end
|
@@ -60,7 +62,7 @@ module Backstage
|
|
60
62
|
content_type :json
|
61
63
|
|
62
64
|
{
|
63
|
-
:collections => [:pools, :apps, :queues, :topics, :message_processors, :jobs, :services].inject({}) do |collections, collection|
|
65
|
+
:collections => [:pools, :apps, :queues, :topics, :message_processors, :jobs, :services, :logs].inject({}) do |collections, collection|
|
64
66
|
collections[collection] = json_url_for( collection_path( collection ) )
|
65
67
|
collections
|
66
68
|
end
|
@@ -68,7 +70,11 @@ module Backstage
|
|
68
70
|
end
|
69
71
|
|
70
72
|
get '/' do
|
71
|
-
|
73
|
+
if html_requested?
|
74
|
+
haml :'dashboard/index'
|
75
|
+
else
|
76
|
+
redirect_to '/url'
|
77
|
+
end
|
72
78
|
end
|
73
79
|
|
74
80
|
get '/css/style.css' do
|
data/config/torquebox.yml
CHANGED
data/lib/apps/models/app.rb
CHANGED
@@ -27,7 +27,24 @@ module Backstage
|
|
27
27
|
def self.to_hash_attributes
|
28
28
|
super + [:name, :environment_name, :root_path, :deployed_at]
|
29
29
|
end
|
30
|
+
|
31
|
+
def has_logs?
|
32
|
+
File.directory?( log_dir )
|
33
|
+
end
|
30
34
|
|
35
|
+
def logs
|
36
|
+
logs = Log.all( log_dir )
|
37
|
+
logs.each { |log| log.parent = self }
|
38
|
+
logs
|
39
|
+
end
|
40
|
+
|
41
|
+
def log_dir
|
42
|
+
log_dir = File.join( root_path, 'logs' )
|
43
|
+
log_dir = File.join( Backstage.jboss_log_dir, name ) if !File.directory?( log_dir ) && Backstage.jboss_log_dir
|
44
|
+
log_dir = File.join( root_path, 'log' ) unless File.directory?( log_dir )
|
45
|
+
|
46
|
+
log_dir
|
47
|
+
end
|
31
48
|
end
|
32
49
|
end
|
33
50
|
|
data/lib/apps/routes.rb
CHANGED
@@ -14,5 +14,33 @@
|
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
16
|
|
17
|
-
Backstage
|
17
|
+
module Backstage
|
18
|
+
class Application < Sinatra::Base
|
19
|
+
resource :app
|
20
|
+
|
21
|
+
get "/app/:name/logs" do
|
22
|
+
@parent = App.find( Util.decode_name( params[:name] ) )
|
23
|
+
@collection = @parent.logs
|
24
|
+
if html_requested?
|
25
|
+
haml :'logs/index'
|
26
|
+
else
|
27
|
+
content_type :json
|
28
|
+
collection_to_json( @collection )
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
get "/app/:name/log/:id" do
|
33
|
+
@parent = App.find( Util.decode_name( params[:name] ) )
|
34
|
+
@object = Log.find( Util.decode_name( params[:id] ) )
|
35
|
+
@object.parent = @parent
|
36
|
+
|
37
|
+
if html_requested?
|
38
|
+
haml :'logs/show'
|
39
|
+
else
|
40
|
+
object_to_json( @object )
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
18
46
|
|
@@ -43,8 +43,8 @@ module Backstage
|
|
43
43
|
yield message
|
44
44
|
end
|
45
45
|
rescue Exception => ex
|
46
|
-
|
47
|
-
|
46
|
+
Backstage.logger "WARNING - failed to access messages for queue #{jndi_name}: #{ex}"
|
47
|
+
Backstage.logger ex
|
48
48
|
end
|
49
49
|
|
50
50
|
def display_name
|
@@ -72,6 +72,16 @@ module Backstage
|
|
72
72
|
name =~ %r{/queues/torquebox/(.*)} ? $1 : 'n/a'
|
73
73
|
end
|
74
74
|
|
75
|
+
def pause
|
76
|
+
super
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
def resume
|
81
|
+
super
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
75
85
|
def status
|
76
86
|
mbean.paused ? 'Paused' : 'Running'
|
77
87
|
end
|
data/lib/destinations/routes.rb
CHANGED
@@ -38,7 +38,19 @@ module Backstage
|
|
38
38
|
object_to_json( @object )
|
39
39
|
end
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
|
+
# we can't implement move until we figure out how to get the
|
43
|
+
# actual HQ internal message id on the client side
|
44
|
+
# post "/queue/:name/message/:id/move" do
|
45
|
+
# @destination = Queue.find( Util.decode_name( params[:name] ) )
|
46
|
+
# @destination.mbean.move_message( params[:id], params[:queue])
|
47
|
+
# if html_requested?
|
48
|
+
# haml :'messages/show'
|
49
|
+
# else
|
50
|
+
# object_to_json( @destination )
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
|
42
54
|
end
|
43
55
|
end
|
44
56
|
|
data/lib/has_mbean.rb
CHANGED
@@ -16,6 +16,7 @@
|
|
16
16
|
|
17
17
|
module Backstage
|
18
18
|
module HasMBean
|
19
|
+
include Comparable
|
19
20
|
java_import javax.management.ObjectName
|
20
21
|
|
21
22
|
def self.included(base)
|
@@ -27,6 +28,10 @@ module Backstage
|
|
27
28
|
self.mbean_name = mbean_name
|
28
29
|
self.mbean = mbean
|
29
30
|
end
|
31
|
+
|
32
|
+
def <=>(other)
|
33
|
+
full_name <=> other.full_name
|
34
|
+
end
|
30
35
|
|
31
36
|
def full_name
|
32
37
|
mbean_name.to_s
|
@@ -44,7 +49,7 @@ module Backstage
|
|
44
49
|
end
|
45
50
|
|
46
51
|
def all(filter_string = filter)
|
47
|
-
jmx_server.query_names( filter_string ).collect { |name| new( name, jmx_server[name] ) }
|
52
|
+
jmx_server.query_names( filter_string ).collect { |name| new( name, jmx_server[name] ) }.sort
|
48
53
|
end
|
49
54
|
|
50
55
|
def find(name)
|
data/lib/helpers.rb
CHANGED
@@ -13,14 +13,37 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
|
-
|
16
|
+
require 'logger'
|
17
17
|
require 'util'
|
18
18
|
require 'authentication'
|
19
19
|
require 'sinatra/url_for'
|
20
20
|
|
21
21
|
module Backstage
|
22
|
-
|
22
|
+
def self.jboss_log_dir
|
23
|
+
java.lang.System.get_property( 'jboss.server.log.dir' )
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.jboss_app_name
|
27
|
+
App.find( "torquebox.apps:app=torquebox-backstage" ) ? 'torquebox-backstage' : 'backstage'
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.logger
|
31
|
+
return @logger if @logger
|
23
32
|
|
33
|
+
# log to <jboss log dir>/torquebox/<RACK_ENV>.log if you can,
|
34
|
+
# otherwise do <app dir>/log/<RACK_ENV>.log
|
35
|
+
if Backstage.jboss_log_dir && File.directory?( Backstage.jboss_log_dir )
|
36
|
+
log_dir = File.join( Backstage.jboss_log_dir, Backstage.jboss_app_name )
|
37
|
+
else
|
38
|
+
log_dir = File.join( File.dirname( __FILE__ ), '..', 'log' )
|
39
|
+
end
|
40
|
+
FileUtils.mkdir_p( log_dir )
|
41
|
+
|
42
|
+
@logger = Logger.new( File.join( log_dir, "#{ENV['RACK_ENV']}.log" ) )
|
43
|
+
end
|
44
|
+
|
45
|
+
class Application < Sinatra::Base
|
46
|
+
|
24
47
|
helpers do
|
25
48
|
include Backstage::Authentication
|
26
49
|
include Sinatra::UrlForHelper
|
@@ -29,7 +52,7 @@ module Backstage
|
|
29
52
|
options[:format] = 'json'
|
30
53
|
url_for( fragment, :full, options )
|
31
54
|
end
|
32
|
-
|
55
|
+
|
33
56
|
def object_path(object)
|
34
57
|
object_action_or_collection_path(*(object.association_chain << nil))
|
35
58
|
end
|
@@ -45,7 +68,7 @@ module Backstage
|
|
45
68
|
end
|
46
69
|
alias_method :object_action_path, :object_action_or_collection_path
|
47
70
|
alias_method :collection_path, :object_action_or_collection_path
|
48
|
-
|
71
|
+
|
49
72
|
def redirect_to(location)
|
50
73
|
redirect url_for(location, :full)
|
51
74
|
end
|
@@ -59,15 +82,15 @@ module Backstage
|
|
59
82
|
dom_class << 'status' << value.downcase if name.to_s.downcase == 'status' # hack
|
60
83
|
"<tr class='data-row'><td class='label'>#{name}</td><td class='#{dom_class.join(' ')}'>#{value}</td></tr>"
|
61
84
|
end
|
62
|
-
|
85
|
+
|
63
86
|
def simple_class_name(object)
|
64
87
|
object.class.name.split( "::" ).last.underscore
|
65
88
|
end
|
66
|
-
|
89
|
+
|
67
90
|
def truncate(text, length = 30)
|
68
91
|
text.length > length ? text[0...length] + '...' : text
|
69
92
|
end
|
70
|
-
|
93
|
+
|
71
94
|
def class_for_body
|
72
95
|
klass = request.path_info.split('/').reverse.select { |part| part =~ /^[A-Za-z_]*$/ }
|
73
96
|
klass.empty? ? 'root' : klass
|
@@ -85,21 +108,24 @@ module Backstage
|
|
85
108
|
def html_requested?
|
86
109
|
params[:format] != 'json' && env['rack-accept.request'].media_type?( 'text/html' )
|
87
110
|
end
|
88
|
-
|
111
|
+
|
89
112
|
def collection_to_json( collection )
|
90
113
|
JSON.generate( collection.collect { |object| object_to_hash( object ) } )
|
91
114
|
end
|
92
115
|
|
93
116
|
def object_to_json(object)
|
94
|
-
JSON.generate( object_to_hash( object ) )
|
117
|
+
JSON.generate( object_to_hash( object ) )
|
95
118
|
end
|
96
|
-
|
119
|
+
|
97
120
|
def object_to_hash(object)
|
98
121
|
response = object.to_hash
|
99
|
-
|
100
|
-
actions
|
101
|
-
|
122
|
+
if object.respond_to?( :available_actions )
|
123
|
+
response[:actions] = object.available_actions.inject({}) do |actions, action|
|
124
|
+
actions[action] = json_url_for( object_action_path( response[:resource], action ) )
|
125
|
+
actions
|
126
|
+
end
|
102
127
|
end
|
128
|
+
|
103
129
|
response.each do |key, value|
|
104
130
|
if value.kind_of?( Resource )
|
105
131
|
response[key] = json_url_for( object_path( value ) )
|
@@ -115,10 +141,57 @@ module Backstage
|
|
115
141
|
response
|
116
142
|
end
|
117
143
|
|
144
|
+
def human_size(size)
|
145
|
+
if size > 1024
|
146
|
+
size = size.to_f
|
147
|
+
if size > 1024*1024
|
148
|
+
size /= 1024*1024
|
149
|
+
suffix = 'Mb'
|
150
|
+
elsif size > 1024
|
151
|
+
size /= 1024
|
152
|
+
suffix = 'kb'
|
153
|
+
end
|
154
|
+
size = size.round( 2 )
|
155
|
+
else
|
156
|
+
suffix = 'b'
|
157
|
+
end
|
158
|
+
|
159
|
+
"#{size} #{suffix}"
|
160
|
+
end
|
161
|
+
|
162
|
+
def torquebox_version_info
|
163
|
+
versions = []
|
164
|
+
torquebox = JMX::MBeanServer.new[javax.management.ObjectName.new( 'torquebox:type=system' )]
|
165
|
+
versions << ['Version', torquebox.version]
|
166
|
+
versions << ['Build Number', torquebox.build_number]
|
167
|
+
versions << ['Revision', torquebox.revision]
|
168
|
+
versions
|
169
|
+
end
|
170
|
+
|
171
|
+
def hornetq_version_info
|
172
|
+
versions = []
|
173
|
+
hornetq = JMX::MBeanServer.new[javax.management.ObjectName.new( 'org.hornetq:module=Core,type=Server' )]
|
174
|
+
versions << ['Version', hornetq.version]
|
175
|
+
versions << ['Clustered', hornetq.clustered]
|
176
|
+
versions
|
177
|
+
end
|
178
|
+
|
179
|
+
def jboss_version_info
|
180
|
+
versions = []
|
181
|
+
jboss = JMX::MBeanServer.new[javax.management.ObjectName.new( 'jboss.system:type=Server' )]
|
182
|
+
versions << ['Version', jboss.version]
|
183
|
+
versions
|
184
|
+
end
|
118
185
|
end
|
119
186
|
end
|
120
187
|
end
|
121
188
|
|
189
|
+
class Object
|
190
|
+
def blank?
|
191
|
+
nil? or (respond_to?( :empty? ) and empty?)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
122
195
|
class String
|
123
196
|
def classify
|
124
197
|
if self =~ %r{/}
|
@@ -133,11 +206,11 @@ class String
|
|
133
206
|
def constantize
|
134
207
|
eval( classify )
|
135
208
|
end
|
136
|
-
|
209
|
+
|
137
210
|
def underscore
|
138
211
|
gsub(/([a-zA-Z])([A-Z])/, '\1_\2').downcase
|
139
212
|
end
|
140
|
-
|
213
|
+
|
141
214
|
def humanize
|
142
215
|
split( '_' ).collect( &:capitalize ).join( ' ' )
|
143
216
|
end
|
@@ -147,3 +220,7 @@ class String
|
|
147
220
|
"#{self}s"
|
148
221
|
end
|
149
222
|
end
|
223
|
+
|
224
|
+
class Logger
|
225
|
+
alias_method :write, :<<
|
226
|
+
end
|