riak-client 0.8.3 → 0.9.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +10 -3
- data/lib/riak/bucket.rb +31 -46
- data/lib/riak/client.rb +18 -4
- data/lib/riak/client/excon_backend.rb +15 -1
- data/lib/riak/client/http_backend.rb +169 -196
- data/lib/riak/client/http_backend/configuration.rb +56 -0
- data/lib/riak/client/http_backend/object_methods.rb +101 -0
- data/lib/riak/client/http_backend/request_headers.rb +46 -0
- data/lib/riak/client/http_backend/transport_methods.rb +208 -0
- data/lib/riak/core_ext/blank.rb +14 -2
- data/lib/riak/link.rb +9 -4
- data/lib/riak/locale/en.yml +2 -0
- data/lib/riak/map_reduce.rb +37 -103
- data/lib/riak/map_reduce/filter_builder.rb +106 -0
- data/lib/riak/map_reduce/phase.rb +108 -0
- data/lib/riak/robject.rb +19 -96
- data/lib/riak/util/escape.rb +7 -0
- data/riak-client.gemspec +5 -5
- data/spec/riak/bucket_spec.rb +57 -111
- data/spec/riak/client_spec.rb +97 -78
- data/spec/riak/escape_spec.rb +7 -0
- data/spec/riak/http_backend/object_methods_spec.rb +218 -0
- data/spec/riak/http_backend_spec.rb +204 -65
- data/spec/riak/link_spec.rb +8 -0
- data/spec/riak/map_reduce_spec.rb +26 -151
- data/spec/riak/object_spec.rb +36 -350
- data/spec/riak/search_spec.rb +5 -16
- data/spec/spec_helper.rb +1 -1
- metadata +29 -8
@@ -0,0 +1,56 @@
|
|
1
|
+
# Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
require 'riak'
|
15
|
+
|
16
|
+
module Riak
|
17
|
+
class Client
|
18
|
+
class HTTPBackend
|
19
|
+
# Riak 0.14 provides a root URL that enumerates all of the
|
20
|
+
# HTTP endpoints and their paths. This module adds methods to
|
21
|
+
# auto-discover those endpoints via the root URL.
|
22
|
+
module Configuration
|
23
|
+
private
|
24
|
+
def server_config
|
25
|
+
@server_config ||= {}.tap do |hash|
|
26
|
+
begin
|
27
|
+
response = get(200, "/", {}, {})
|
28
|
+
Link.parse(response[:headers]['link'].first).each {|l| hash[l.tag.intern] = l.url }
|
29
|
+
rescue Riak::FailedRequest
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def riak_kv_wm_raw
|
35
|
+
server_config[:riak_kv_wm_raw] || client.prefix
|
36
|
+
end
|
37
|
+
|
38
|
+
def riak_kv_wm_link_walker
|
39
|
+
server_config[:riak_kv_wm_link_walker] || client.prefix
|
40
|
+
end
|
41
|
+
|
42
|
+
def riak_kv_wm_mapred
|
43
|
+
server_config[:riak_kv_wm_mapred] || client.mapred
|
44
|
+
end
|
45
|
+
|
46
|
+
def riak_kv_wm_ping
|
47
|
+
server_config[:riak_kv_wm_ping] || "/ping"
|
48
|
+
end
|
49
|
+
|
50
|
+
def riak_kv_wm_stats
|
51
|
+
server_config[:riak_kv_wm_stats] || "/stats"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
require 'riak'
|
15
|
+
require 'time'
|
16
|
+
|
17
|
+
module Riak
|
18
|
+
class Client
|
19
|
+
class HTTPBackend
|
20
|
+
# Methods for assisting with the handling of {RObject}s present
|
21
|
+
# in HTTP requests and responses.
|
22
|
+
module ObjectMethods
|
23
|
+
# HTTP header hash that will be sent along when reloading the object
|
24
|
+
# @return [Hash] hash of HTTP headers
|
25
|
+
def reload_headers(robject)
|
26
|
+
{}.tap do |h|
|
27
|
+
h['If-None-Match'] = robject.etag if robject.etag.present?
|
28
|
+
h['If-Modified-Since'] = robject.last_modified.httpdate if robject.last_modified.present?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# HTTP header hash that will be sent along when storing the object
|
33
|
+
# @return [Hash] hash of HTTP Headers
|
34
|
+
def store_headers(robject)
|
35
|
+
{}.tap do |hash|
|
36
|
+
hash["Content-Type"] = robject.content_type
|
37
|
+
hash["X-Riak-Vclock"] = robject.vclock if robject.vclock
|
38
|
+
if robject.prevent_stale_writes && robject.etag.present?
|
39
|
+
hash["If-Match"] = robject.etag
|
40
|
+
elsif robject.prevent_stale_writes
|
41
|
+
hash["If-None-Match"] = "*"
|
42
|
+
end
|
43
|
+
unless robject.links.blank?
|
44
|
+
hash["Link"] = robject.links.reject {|l| l.rel == "up" }.map(&:to_s).join(", ")
|
45
|
+
end
|
46
|
+
unless robject.meta.blank?
|
47
|
+
robject.meta.each do |k,v|
|
48
|
+
hash["X-Riak-Meta-#{k}"] = v.to_s
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Load object data from an HTTP response
|
55
|
+
# @param [Hash] response a response from {Riak::Client::HTTPBackend}
|
56
|
+
def load_object(robject, response)
|
57
|
+
extract_header(robject, response, "location", :key) {|v| URI.unescape(v.split("/").last) }
|
58
|
+
extract_header(robject, response, "content-type", :content_type)
|
59
|
+
extract_header(robject, response, "x-riak-vclock", :vclock)
|
60
|
+
extract_header(robject, response, "link", :links) {|v| Set.new(Link.parse(v)) }
|
61
|
+
extract_header(robject, response, "etag", :etag)
|
62
|
+
extract_header(robject, response, "last-modified", :last_modified) {|v| Time.httpdate(v) }
|
63
|
+
robject.meta = response[:headers].inject({}) do |h,(k,v)|
|
64
|
+
if k =~ /x-riak-meta-(.*)/
|
65
|
+
h[$1] = v
|
66
|
+
end
|
67
|
+
h
|
68
|
+
end
|
69
|
+
robject.conflict = (response[:code] && response[:code].to_i == 300 && robject.content_type =~ /multipart\/mixed/)
|
70
|
+
robject.siblings = robject.conflict? ? extract_siblings(robject, response[:body]) : nil
|
71
|
+
robject.raw_data = response[:body] if response[:body].present? && !robject.conflict?
|
72
|
+
robject
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
def extract_header(robject, response, name, attribute=nil, &block)
|
77
|
+
extract_if_present(robject, response[:headers], name, attribute) do |value|
|
78
|
+
block ? block.call(value[0]) : value[0]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def extract_if_present(robject, hash, key, attribute=nil)
|
83
|
+
if hash[key].present?
|
84
|
+
attribute ||= key
|
85
|
+
value = block_given? ? yield(hash[key]) : hash[key]
|
86
|
+
robject.send("#{attribute}=", value)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def extract_siblings(robject, data)
|
91
|
+
Util::Multipart.parse(data, Util::Multipart.extract_boundary(robject.content_type)).map do |part|
|
92
|
+
RObject.new(robject.bucket, robject.key) do |sibling|
|
93
|
+
load_object(sibling, part)
|
94
|
+
sibling.vclock = robject.vclock
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
require 'riak'
|
15
|
+
|
16
|
+
module Riak
|
17
|
+
class Client
|
18
|
+
class HTTPBackend
|
19
|
+
# @private
|
20
|
+
class RequestHeaders < Riak::Util::Headers
|
21
|
+
alias each each_capitalized
|
22
|
+
|
23
|
+
def initialize(hash)
|
24
|
+
initialize_http_header(hash)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_a
|
28
|
+
[].tap do |arr|
|
29
|
+
each_capitalized do |k,v|
|
30
|
+
arr << "#{k}: #{v}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_hash
|
36
|
+
{}.tap do |hash|
|
37
|
+
each_capitalized do |k,v|
|
38
|
+
hash[k] ||= []
|
39
|
+
hash[k] << v
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
# Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
require 'riak'
|
15
|
+
|
16
|
+
module Riak
|
17
|
+
class Client
|
18
|
+
class HTTPBackend
|
19
|
+
# Methods related to performing HTTP requests in a consistent
|
20
|
+
# fashion across multiple client libraries. HTTP/1.1 verbs are
|
21
|
+
# presented as methods.
|
22
|
+
module TransportMethods
|
23
|
+
# Performs a HEAD request to the specified resource on the Riak server.
|
24
|
+
# @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
|
25
|
+
# @param [String, Array<String,Hash>] resource a relative path or array of path segments and optional query params Hash that will be joined to the root URI
|
26
|
+
# @overload head(expect, *resource)
|
27
|
+
# @overload head(expect, *resource, headers)
|
28
|
+
# Send the request with custom headers
|
29
|
+
# @param [Hash] headers custom headers to send with the request
|
30
|
+
# @return [Hash] response data, containing only the :headers and :code keys
|
31
|
+
# @raise [FailedRequest] if the response code doesn't match the expected response
|
32
|
+
def head(expect, *resource)
|
33
|
+
headers = default_headers.merge(resource.extract_options!)
|
34
|
+
verify_path!(resource)
|
35
|
+
perform(:head, path(*resource), headers, expect)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Performs a GET request to the specified resource on the Riak server.
|
39
|
+
# @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
|
40
|
+
# @param [String, Array<String,Hash>] resource a relative path or array of path segments and optional query params Hash that will be joined to the root URI
|
41
|
+
# @overload get(expect, *resource)
|
42
|
+
# @overload get(expect, *resource, headers)
|
43
|
+
# Send the request with custom headers
|
44
|
+
# @param [Hash] headers custom headers to send with the request
|
45
|
+
# @overload get(expect, *resource, headers={})
|
46
|
+
# Stream the response body through the supplied block
|
47
|
+
# @param [Hash] headers custom headers to send with the request
|
48
|
+
# @yield [chunk] yields successive chunks of the response body as strings
|
49
|
+
# @return [Hash] response data, containing only the :headers and :code keys
|
50
|
+
# @return [Hash] response data, containing :headers, :body, and :code keys
|
51
|
+
# @raise [FailedRequest] if the response code doesn't match the expected response
|
52
|
+
def get(expect, *resource, &block)
|
53
|
+
headers = default_headers.merge(resource.extract_options!)
|
54
|
+
verify_path!(resource)
|
55
|
+
perform(:get, path(*resource), headers, expect, &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Performs a PUT request to the specified resource on the Riak server.
|
59
|
+
# @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
|
60
|
+
# @param [String, Array<String,Hash>] resource a relative path or array of path segments and optional query params Hash that will be joined to the root URI
|
61
|
+
# @param [String] body the request body to send to the server
|
62
|
+
# @overload put(expect, *resource, body)
|
63
|
+
# @overload put(expect, *resource, body, headers)
|
64
|
+
# Send the request with custom headers
|
65
|
+
# @param [Hash] headers custom headers to send with the request
|
66
|
+
# @overload put(expect, *resource, body, headers={})
|
67
|
+
# Stream the response body through the supplied block
|
68
|
+
# @param [Hash] headers custom headers to send with the request
|
69
|
+
# @yield [chunk] yields successive chunks of the response body as strings
|
70
|
+
# @return [Hash] response data, containing only the :headers and :code keys
|
71
|
+
# @return [Hash] response data, containing :headers, :code, and :body keys
|
72
|
+
# @raise [FailedRequest] if the response code doesn't match the expected response
|
73
|
+
def put(expect, *resource, &block)
|
74
|
+
headers = default_headers.merge(resource.extract_options!)
|
75
|
+
uri, data = verify_path_and_body!(resource)
|
76
|
+
perform(:put, path(*uri), headers, expect, data, &block)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Performs a POST request to the specified resource on the Riak server.
|
80
|
+
# @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
|
81
|
+
# @param [String, Array<String>] resource a relative path or array of path segments that will be joined to the root URI
|
82
|
+
# @param [String] body the request body to send to the server
|
83
|
+
# @overload post(expect, *resource, body)
|
84
|
+
# @overload post(expect, *resource, body, headers)
|
85
|
+
# Send the request with custom headers
|
86
|
+
# @param [Hash] headers custom headers to send with the request
|
87
|
+
# @overload post(expect, *resource, body, headers={})
|
88
|
+
# Stream the response body through the supplied block
|
89
|
+
# @param [Hash] headers custom headers to send with the request
|
90
|
+
# @yield [chunk] yields successive chunks of the response body as strings
|
91
|
+
# @return [Hash] response data, containing only the :headers and :code keys
|
92
|
+
# @return [Hash] response data, containing :headers, :code and :body keys
|
93
|
+
# @raise [FailedRequest] if the response code doesn't match the expected response
|
94
|
+
def post(expect, *resource, &block)
|
95
|
+
headers = default_headers.merge(resource.extract_options!)
|
96
|
+
uri, data = verify_path_and_body!(resource)
|
97
|
+
perform(:post, path(*uri), headers, expect, data, &block)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Performs a DELETE request to the specified resource on the Riak server.
|
101
|
+
# @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
|
102
|
+
# @param [String, Array<String,Hash>] resource a relative path or array of path segments and optional query params Hash that will be joined to the root URI
|
103
|
+
# @overload delete(expect, *resource)
|
104
|
+
# @overload delete(expect, *resource, headers)
|
105
|
+
# Send the request with custom headers
|
106
|
+
# @param [Hash] headers custom headers to send with the request
|
107
|
+
# @overload delete(expect, *resource, headers={})
|
108
|
+
# Stream the response body through the supplied block
|
109
|
+
# @param [Hash] headers custom headers to send with the request
|
110
|
+
# @yield [chunk] yields successive chunks of the response body as strings
|
111
|
+
# @return [Hash] response data, containing only the :headers and :code keys
|
112
|
+
# @return [Hash] response data, containing :headers, :code and :body keys
|
113
|
+
# @raise [FailedRequest] if the response code doesn't match the expected response
|
114
|
+
def delete(expect, *resource, &block)
|
115
|
+
headers = default_headers.merge(resource.extract_options!)
|
116
|
+
verify_path!(resource)
|
117
|
+
perform(:delete, path(*resource), headers, expect, &block)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Executes requests according to the underlying HTTP client library semantics.
|
121
|
+
# @abstract Subclasses must implement this internal method to perform HTTP requests
|
122
|
+
# according to the API of their HTTP libraries.
|
123
|
+
# @param [Symbol] method one of :head, :get, :post, :put, :delete
|
124
|
+
# @param [URI] uri the HTTP URI to request
|
125
|
+
# @param [Hash] headers headers to send along with the request
|
126
|
+
# @param [Fixnum, Array] expect the expected response code(s)
|
127
|
+
# @param [String, IO] body the PUT or POST request body
|
128
|
+
# @return [Hash] response data, containing :headers, :code and :body keys. Only :headers and :code should be present when the body is streamed or the method is :head.
|
129
|
+
# @yield [chunk] if the method is not :head, successive chunks of the response body will be yielded as strings
|
130
|
+
# @raise [NotImplementedError] if a subclass does not implement this method
|
131
|
+
def perform(method, uri, headers, expect, body=nil)
|
132
|
+
raise NotImplementedError
|
133
|
+
end
|
134
|
+
|
135
|
+
# Default header hash sent with every request, based on settings in the client
|
136
|
+
# @return [Hash] headers that will be merged with user-specified headers on every request
|
137
|
+
def default_headers
|
138
|
+
{
|
139
|
+
"Accept" => "multipart/mixed, application/json;q=0.7, */*;q=0.5",
|
140
|
+
"X-Riak-ClientId" => @client.client_id
|
141
|
+
}
|
142
|
+
end
|
143
|
+
|
144
|
+
# @return [URI] The calculated root URI for the Riak HTTP endpoint
|
145
|
+
def root_uri
|
146
|
+
URI.parse("http://#{client.host}:#{client.port}")
|
147
|
+
end
|
148
|
+
|
149
|
+
# Calculates an absolute URI from a relative path specification
|
150
|
+
# @param [Array<String,Hash>] segments a relative path or sequence of path segments and optional query params Hash that will be joined to the root URI
|
151
|
+
# @return [URI] an absolute URI for the resource
|
152
|
+
def path(*segments)
|
153
|
+
query = segments.extract_options!.to_param
|
154
|
+
root_uri.merge(segments.join("/").gsub(/\/+/, "/").sub(/^\//, '')).tap do |uri|
|
155
|
+
uri.query = query if query.present?
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Verifies that both a resource path and body are present in the arguments
|
160
|
+
# @param [Array] args the arguments to verify
|
161
|
+
# @raise [ArgumentError] if the body or resource is missing, or if the body is not a String
|
162
|
+
def verify_path_and_body!(args)
|
163
|
+
body = args.pop
|
164
|
+
begin
|
165
|
+
verify_path!(args)
|
166
|
+
rescue ArgumentError
|
167
|
+
raise ArgumentError, t("path_and_body_required")
|
168
|
+
end
|
169
|
+
|
170
|
+
raise ArgumentError, t("request_body_type") unless String === body || IO === body
|
171
|
+
[args, body]
|
172
|
+
end
|
173
|
+
|
174
|
+
# Verifies that the specified resource is valid
|
175
|
+
# @param [String, Array] resource the resource specification
|
176
|
+
# @raise [ArgumentError] if the resource path is too short
|
177
|
+
def verify_path!(resource)
|
178
|
+
resource = Array(resource).flatten
|
179
|
+
raise ArgumentError, t("resource_path_short") unless resource.length > 1 || resource.include?(@client.mapred)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Checks the expected response codes against the actual response code. Use internally when
|
183
|
+
# implementing {#perform}.
|
184
|
+
# @param [String, Fixnum, Array<String,Fixnum>] expected the expected response code(s)
|
185
|
+
# @param [String, Fixnum] actual the received response code
|
186
|
+
# @return [Boolean] whether the actual response code is acceptable given the expectations
|
187
|
+
def valid_response?(expected, actual)
|
188
|
+
Array(expected).map(&:to_i).include?(actual.to_i)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Checks whether a combination of the HTTP method, response code, and block should
|
192
|
+
# result in returning the :body in the response hash. Use internally when implementing {#perform}.
|
193
|
+
# @param [Symbol] method the HTTP method
|
194
|
+
# @param [String, Fixnum] code the received response code
|
195
|
+
# @param [Boolean] has_block whether a streaming block was passed to {#perform}. Pass block_given? to this parameter.
|
196
|
+
# @return [Boolean] whether to return the body in the response hash
|
197
|
+
def return_body?(method, code, has_block)
|
198
|
+
method != :head && !valid_response?([204,205,304], code) && !has_block
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
def response_headers
|
203
|
+
Thread.current[:response_headers] ||= Riak::Util::Headers.new
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
data/lib/riak/core_ext/blank.rb
CHANGED
@@ -11,13 +11,25 @@ unless Object.new.respond_to? :blank?
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
class FalseClass
|
15
|
+
def blank?
|
16
|
+
true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class TrueClass
|
21
|
+
def blank?
|
22
|
+
false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
14
26
|
class Set
|
15
27
|
alias :blank? :empty?
|
16
28
|
end
|
17
29
|
|
18
30
|
class String
|
19
31
|
def blank?
|
20
|
-
|
32
|
+
self !~ /[^\s]/
|
21
33
|
end
|
22
34
|
end
|
23
35
|
|
@@ -33,7 +45,7 @@ end
|
|
33
45
|
unless Object.new.respond_to? :present?
|
34
46
|
class Object
|
35
47
|
def present?
|
36
|
-
|
48
|
+
!blank?
|
37
49
|
end
|
38
50
|
end
|
39
51
|
end
|