couchbase 0.9.7
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.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/HISTORY.markdown +50 -0
- data/LICENSE +201 -0
- data/README.markdown +135 -0
- data/Rakefile +27 -0
- data/couchbase.gemspec +28 -0
- data/lib/couchbase.rb +57 -0
- data/lib/couchbase/bucket.rb +138 -0
- data/lib/couchbase/couchdb.rb +107 -0
- data/lib/couchbase/document.rb +71 -0
- data/lib/couchbase/http_status.rb +118 -0
- data/lib/couchbase/latch.rb +71 -0
- data/lib/couchbase/memcached.rb +372 -0
- data/lib/couchbase/node.rb +49 -0
- data/lib/couchbase/rest_client.rb +124 -0
- data/lib/couchbase/version.rb +20 -0
- data/lib/couchbase/view.rb +182 -0
- data/test/setup.rb +28 -0
- data/test/support/buckets-config.json +843 -0
- data/test/support/sample_design_doc.json +9 -0
- data/test/test_bucket.rb +39 -0
- data/test/test_couchbase.rb +9 -0
- data/test/test_couchdb.rb +98 -0
- data/test/test_document.rb +11 -0
- data/test/test_latch.rb +88 -0
- data/test/test_memcached.rb +59 -0
- data/test/test_rest_client.rb +14 -0
- data/test/test_version.rb +7 -0
- data/test/test_view.rb +98 -0
- metadata +164 -0
data/lib/couchbase.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Author:: Couchbase <info@couchbase.com>
|
2
|
+
# Copyright:: 2011 Couchbase, Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
module Couchbase
|
19
|
+
autoload :Bucket, 'couchbase/bucket'
|
20
|
+
autoload :Couchdb, 'couchbase/couchdb'
|
21
|
+
autoload :Document, 'couchbase/document'
|
22
|
+
autoload :HttpStatus, 'couchbase/http_status'
|
23
|
+
autoload :Latch, 'couchbase/latch'
|
24
|
+
autoload :Memcached, 'couchbase/memcached'
|
25
|
+
autoload :Node, 'couchbase/node'
|
26
|
+
autoload :RestClient, 'couchbase/rest_client'
|
27
|
+
autoload :VERSION, 'couchbase/version'
|
28
|
+
autoload :View, 'couchbase/view'
|
29
|
+
|
30
|
+
# This error is raising when library detects that some operation
|
31
|
+
# doesn't implemented by the server. For example views API doesn't
|
32
|
+
# implemented by Membase 1.7.x
|
33
|
+
class NotImplemented < Exception; end
|
34
|
+
|
35
|
+
class ViewError < Exception
|
36
|
+
attr_reader :from, :reason
|
37
|
+
|
38
|
+
def initialize(from, reason)
|
39
|
+
@from = from
|
40
|
+
@reason = reason
|
41
|
+
super("#{from}: #{reason}")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class << self
|
46
|
+
# The method +new+ initializes new Bucket instance with all arguments passed.
|
47
|
+
#
|
48
|
+
# === Examples
|
49
|
+
# Couchbase.new("http://localhost:8091/pools/default") #=> establish connection with couchbase default pool and default bucket
|
50
|
+
# Couchbase.new("http://localhost:8091/pools/default", :bucket_name => 'blog') #=> select custom bucket
|
51
|
+
# Couchbase.new("http://localhost:8091/pools/default", :bucket_name => 'blog', :bucket_password => 'secret') #=> specify password for bucket (and SASL auth for memcached client)
|
52
|
+
def new(*args)
|
53
|
+
Bucket.new(*args)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# Author:: Couchbase <info@couchbase.com>
|
2
|
+
# Copyright:: 2011 Couchbase, Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'uri'
|
19
|
+
|
20
|
+
module Couchbase
|
21
|
+
|
22
|
+
# This class in charge of all stuff connected to communication with
|
23
|
+
# Couchbase. It includes CouchDB and Memcached APIs. Also it includes
|
24
|
+
# methods for HTTP transport from RestClient.
|
25
|
+
|
26
|
+
class Bucket
|
27
|
+
include RestClient
|
28
|
+
include Couchdb
|
29
|
+
include Memcached
|
30
|
+
|
31
|
+
attr_accessor :pool_uri, :environment, :type, :nodes,
|
32
|
+
:streaming_uri, :name, :uri, :vbuckets
|
33
|
+
|
34
|
+
# Initializes connection using +pool_uri+ and optional
|
35
|
+
# +:bucket_name+ and +:bucket_password+ (for protected buckets). Bucket
|
36
|
+
# name will be used as a username for all authorizations (SASL for
|
37
|
+
# Memcached API and Basic for HTTP). It also accepts +:environment+
|
38
|
+
# parameter wich intended to let library know what mode it should
|
39
|
+
# use when it applicable (for example it skips/preserves design
|
40
|
+
# documents with 'dev_' prefix for CouchDB API). You can specify
|
41
|
+
# any string starting from 'dev' or 'test' to activate development
|
42
|
+
# mode.
|
43
|
+
#
|
44
|
+
# Also starts thread which will simultanuously listen for
|
45
|
+
# configuration update via +streaming_uri+. Server should push
|
46
|
+
# update notification about adding or removing nodes from cluster.
|
47
|
+
#
|
48
|
+
# Raises ArgumentError when it cannot find specified bucket in given
|
49
|
+
# pool.
|
50
|
+
def initialize(pool_uri, options = {})
|
51
|
+
@latch = Latch.new(:in_progress, :ready)
|
52
|
+
@name = options[:bucket_name] || "default"
|
53
|
+
@pool_uri = URI.parse(pool_uri)
|
54
|
+
@environment = if options[:environment].to_s =~ /^(dev|test)/
|
55
|
+
:development
|
56
|
+
else
|
57
|
+
:production
|
58
|
+
end
|
59
|
+
config = http_get("#{@pool_uri}/buckets").detect do |bucket|
|
60
|
+
bucket['name'] == @name
|
61
|
+
end
|
62
|
+
unless config
|
63
|
+
raise ArgumentError,
|
64
|
+
"There no such bucket with name '#{@name}' in pool #{pool_uri}"
|
65
|
+
end
|
66
|
+
@uri = @pool_uri.merge(config['uri'])
|
67
|
+
@streaming_uri = @pool_uri.merge(config['streamingUri'])
|
68
|
+
if password = options[:bucket_password]
|
69
|
+
@credentials = {:username => @name, :password => password}
|
70
|
+
end
|
71
|
+
super
|
72
|
+
|
73
|
+
# Considering all initialization stuff completed and now we can
|
74
|
+
# start config listener
|
75
|
+
listen_for_config_changes
|
76
|
+
|
77
|
+
@latch.wait
|
78
|
+
end
|
79
|
+
|
80
|
+
# Select next node for work with Couchbase. Currently it makes sense
|
81
|
+
# only for couchdb API, because memcached client works using moxi.
|
82
|
+
def next_node
|
83
|
+
nodes.shuffle.first
|
84
|
+
end
|
85
|
+
|
86
|
+
# Perform configuration using configuration cache. It turn all URIs
|
87
|
+
# into full form (with schema, host and port).
|
88
|
+
#
|
89
|
+
# You can override this method in included modules or descendants if
|
90
|
+
# you'd like to reconfigure them when new configuration arrives from
|
91
|
+
# server.
|
92
|
+
def setup(config)
|
93
|
+
@type = config['bucketType']
|
94
|
+
@nodes = config['nodes'].map do |node|
|
95
|
+
Node.new(node['status'],
|
96
|
+
node['hostname'].split(':').first,
|
97
|
+
node['ports'],
|
98
|
+
node['couchApiBase'])
|
99
|
+
end
|
100
|
+
if @type == 'membase'
|
101
|
+
@vbuckets = config['vBucketServerMap']['vBucketMap']
|
102
|
+
end
|
103
|
+
super
|
104
|
+
@latch.toggle
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
# Run background thread to listen for configuration changes.
|
110
|
+
# Rewrites configuration for each update. Curl::Multi uses select()
|
111
|
+
# call when waiting for data, so is should be efficient use ruby
|
112
|
+
# threads here.
|
113
|
+
def listen_for_config_changes
|
114
|
+
Thread.new do
|
115
|
+
multi = Curl::Multi.new
|
116
|
+
multi.add(mk_curl(@streaming_uri.to_s))
|
117
|
+
multi.perform
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def mk_curl(url)
|
122
|
+
Curl::Easy.new(url) do |curl|
|
123
|
+
curl.useragent = "couchbase-ruby-client/#{Couchbase::VERSION}"
|
124
|
+
if @credentials
|
125
|
+
curl.http_auth_types = :basic
|
126
|
+
curl.username = @credentials[:username]
|
127
|
+
curl.password = @credentials[:password]
|
128
|
+
end
|
129
|
+
curl.verbose = true if Kernel.respond_to?(:debugger)
|
130
|
+
curl.on_body do |data|
|
131
|
+
config = Yajl::Parser.parse(data)
|
132
|
+
setup(config) if config
|
133
|
+
data.bytesize
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# Author:: Couchbase <info@couchbase.com>
|
2
|
+
# Copyright:: 2011 Couchbase, Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
module Couchbase
|
19
|
+
module Couchdb
|
20
|
+
|
21
|
+
# Initializes CouchDB related part of connection.
|
22
|
+
#
|
23
|
+
# @param [ String ] pool_uri Couchbase pool URI.
|
24
|
+
#
|
25
|
+
# @param [ Hash ] options Connection options. This is the hash the client
|
26
|
+
# passed to Couchbase.new to start the session
|
27
|
+
def initialize(pool_uri, options = {})
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
# Fetch design docs stored in current bucket
|
32
|
+
#
|
33
|
+
# @return [ Hash ]
|
34
|
+
def design_docs
|
35
|
+
docs = http_get("#{next_node.couch_api_base}/_all_docs",
|
36
|
+
:params => {:startkey => "_design/", :endkey => "_design0", :include_docs => true})
|
37
|
+
result = {}
|
38
|
+
docs['rows'].each do |doc|
|
39
|
+
doc = Document.wrap(self, doc)
|
40
|
+
key = doc['_id'].sub(/^_design\//, '')
|
41
|
+
next if @environment == :production && key =~ /dev_/
|
42
|
+
result[key] = doc
|
43
|
+
end
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
# Update or create design doc with supplied views
|
48
|
+
#
|
49
|
+
# @param [ Hash, IO, String ] data The source object containing JSON
|
50
|
+
# encoded design document. It must have
|
51
|
+
# <tt>_id</tt> key set, this key should
|
52
|
+
# start with <tt>_design/</tt>.
|
53
|
+
#
|
54
|
+
# @return [ Couchbase::Document ] instance
|
55
|
+
def save_design_doc(data)
|
56
|
+
doc = parse_design_document(data)
|
57
|
+
rv = http_put("#{next_node.couch_api_base}/#{doc['_id']}", {}, doc)
|
58
|
+
doc['_rev'] = rv['rev']
|
59
|
+
doc
|
60
|
+
end
|
61
|
+
|
62
|
+
# Fetch all documents from the bucket.
|
63
|
+
#
|
64
|
+
# @param [ Hash ] params Params for CouchDB <tt>/_all_docs</tt> query
|
65
|
+
#
|
66
|
+
# @return [ Couchbase::View ] View object
|
67
|
+
def all_docs(params = {})
|
68
|
+
View.new(self, "#{next_node.couch_api_base}/_all_docs", params)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Delete design doc with given id and revision.
|
72
|
+
#
|
73
|
+
# @param [ String ] id Design document id. It might have '_design/'
|
74
|
+
# prefix.
|
75
|
+
#
|
76
|
+
# @param [ String ] rev Document revision. It uses latest revision if
|
77
|
+
# <tt>rev</tt> parameter is nil.
|
78
|
+
#
|
79
|
+
def delete_design_doc(id, rev = nil)
|
80
|
+
ddoc = design_docs[id.sub(/^_design\//, '')]
|
81
|
+
return nil unless ddoc
|
82
|
+
http_delete("#{next_node.couch_api_base}/#{ddoc['_id']}",
|
83
|
+
:params => {:rev => rev || ddoc['_rev']})
|
84
|
+
end
|
85
|
+
|
86
|
+
protected
|
87
|
+
|
88
|
+
def parse_design_document(doc)
|
89
|
+
data = case doc
|
90
|
+
when String
|
91
|
+
Yajl::Parser.parse(doc)
|
92
|
+
when IO
|
93
|
+
Yajl::Parser.parse(doc.read)
|
94
|
+
when Hash
|
95
|
+
doc
|
96
|
+
else
|
97
|
+
raise ArgumentError, "Document should be Hash, String or IO instance"
|
98
|
+
end
|
99
|
+
|
100
|
+
if data['_id'].to_s !~ /^_design\//
|
101
|
+
raise ArgumentError, "'_id' key must be set and start with '_design/'."
|
102
|
+
end
|
103
|
+
data['language'] ||= 'javascript'
|
104
|
+
Document.wrap(self, data)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# Author:: Couchbase <info@couchbase.com>
|
2
|
+
# Copyright:: 2011 Couchbase, Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
module Couchbase
|
19
|
+
class Document
|
20
|
+
# Undefine as much methods as we can to free names for views
|
21
|
+
instance_methods.each do |m|
|
22
|
+
undef_method(m) if m.to_s !~ /(?:^__|^nil\?$|^send$|^object_id$|^class$|)/
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_accessor :data, :views
|
26
|
+
|
27
|
+
def initialize(connection, data)
|
28
|
+
@data = data
|
29
|
+
@connection = connection
|
30
|
+
@views = []
|
31
|
+
begin
|
32
|
+
if design_doc?
|
33
|
+
data['views'].each do |name, funs|
|
34
|
+
@views << name
|
35
|
+
self.instance_eval <<-EOV, __FILE__, __LINE__ + 1
|
36
|
+
def #{name}(params = {})
|
37
|
+
endpoint = "\#{@connection.next_node.couch_api_base}/\#{@data['_id']}/_view/#{name}"
|
38
|
+
View.new(@connection, endpoint, params)
|
39
|
+
end
|
40
|
+
EOV
|
41
|
+
end
|
42
|
+
end
|
43
|
+
rescue NoMethodError
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.wrap(connection, data)
|
48
|
+
Document.new(connection, data['doc'] || data)
|
49
|
+
end
|
50
|
+
|
51
|
+
def [](key)
|
52
|
+
@data[key]
|
53
|
+
end
|
54
|
+
|
55
|
+
def []=(key, value)
|
56
|
+
@data[key] = value
|
57
|
+
end
|
58
|
+
|
59
|
+
def design_doc?
|
60
|
+
!!(@data['_id'] =~ %r(_design/))
|
61
|
+
end
|
62
|
+
|
63
|
+
def has_views?
|
64
|
+
!!(design_doc? && !@views.empty?)
|
65
|
+
end
|
66
|
+
|
67
|
+
def inspect
|
68
|
+
%(#<#{self.class.name}:#{self.object_id} #{@data.inspect}>)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# Author:: Couchbase <info@couchbase.com>
|
2
|
+
# Copyright:: 2011 Couchbase, Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
module Couchbase
|
19
|
+
|
20
|
+
# This module contains definitions for exceptions which wrap HTTP
|
21
|
+
# errors. Also it collect them in useful structure
|
22
|
+
# +HttpStatus::Errors+ which is map with status code as a key and
|
23
|
+
# exception class as a value. See the usage example below.
|
24
|
+
#
|
25
|
+
# === Example
|
26
|
+
#
|
27
|
+
# data = Yajl::Parser.parse(curl.body_str)
|
28
|
+
# if error_class = HttpStatus::Errors[curl.response_code]
|
29
|
+
# raise error_class.new(data['error'], data['reason'])
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
|
33
|
+
module HttpStatus
|
34
|
+
|
35
|
+
# Base class for all HTTP error codes. It povides handy methods for
|
36
|
+
# investigating what happened. For example, it stores +status_code+
|
37
|
+
# and human readable +status_message+ automatically and allows to
|
38
|
+
# provide context specific code and message (+error+ and +reason+
|
39
|
+
# correspondingly)
|
40
|
+
|
41
|
+
class Status < Exception
|
42
|
+
class << self
|
43
|
+
attr_accessor :status_code, :status_message
|
44
|
+
alias_method :to_i, :status_code
|
45
|
+
|
46
|
+
def status_line
|
47
|
+
"#{status_code} #{status_message}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
attr_reader :error, :reason
|
52
|
+
def initialize(error, reason)
|
53
|
+
@error = error
|
54
|
+
@reason = reason
|
55
|
+
super("#{error}: #{reason}")
|
56
|
+
end
|
57
|
+
|
58
|
+
def status_code
|
59
|
+
self.class.status_code
|
60
|
+
end
|
61
|
+
|
62
|
+
def status_message
|
63
|
+
self.class.status_message
|
64
|
+
end
|
65
|
+
|
66
|
+
def status_line
|
67
|
+
self.class.status_line
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_i
|
71
|
+
self.class.to_i
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
StatusMessage = {
|
76
|
+
400 => 'Bad Request',
|
77
|
+
401 => 'Unauthorized',
|
78
|
+
402 => 'Payment Required',
|
79
|
+
403 => 'Forbidden',
|
80
|
+
404 => 'Not Found',
|
81
|
+
405 => 'Method Not Allowed',
|
82
|
+
406 => 'Not Acceptable',
|
83
|
+
407 => 'Proxy Authentication Required',
|
84
|
+
408 => 'Request Timeout',
|
85
|
+
409 => 'Conflict',
|
86
|
+
410 => 'Gone',
|
87
|
+
411 => 'Length Required',
|
88
|
+
412 => 'Precondition Failed',
|
89
|
+
413 => 'Request Entity Too Large',
|
90
|
+
414 => 'Request-URI Too Large',
|
91
|
+
415 => 'Unsupported Media Type',
|
92
|
+
416 => 'Request Range Not Satisfiable',
|
93
|
+
417 => 'Expectation Failed',
|
94
|
+
422 => 'Unprocessable Entity',
|
95
|
+
423 => 'Locked',
|
96
|
+
424 => 'Failed Dependency',
|
97
|
+
500 => 'Internal Server Error',
|
98
|
+
501 => 'Not Implemented',
|
99
|
+
502 => 'Bad Gateway',
|
100
|
+
503 => 'Service Unavailable',
|
101
|
+
504 => 'Gateway Timeout',
|
102
|
+
505 => 'HTTP Version Not Supported',
|
103
|
+
507 => 'Insufficient Storage'
|
104
|
+
}
|
105
|
+
|
106
|
+
# Hash with error code as a key and exceptin class as a value
|
107
|
+
Errors = {}
|
108
|
+
|
109
|
+
StatusMessage.each do |status_code, status_message|
|
110
|
+
klass = Class.new(Status)
|
111
|
+
klass.status_code = status_code
|
112
|
+
klass.status_message = status_message
|
113
|
+
klass_name = status_message.gsub(/[ \-]/,'')
|
114
|
+
const_set(klass_name, klass)
|
115
|
+
HttpStatus::Errors[status_code] = const_get(klass_name)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|