riak-client 0.8.3 → 0.9.0.beta
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/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
|