couchbase 1.1.5 → 1.2.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.gitignore +2 -1
  2. data/.travis.yml +12 -1
  3. data/HISTORY.markdown +112 -1
  4. data/README.markdown +149 -6
  5. data/couchbase.gemspec +5 -1
  6. data/ext/couchbase_ext/.gitignore +4 -0
  7. data/ext/couchbase_ext/arguments.c +973 -0
  8. data/ext/couchbase_ext/arithmetic.c +322 -0
  9. data/ext/couchbase_ext/bucket.c +1092 -0
  10. data/ext/couchbase_ext/couchbase_ext.c +618 -3247
  11. data/ext/couchbase_ext/couchbase_ext.h +519 -0
  12. data/ext/couchbase_ext/delete.c +167 -0
  13. data/ext/couchbase_ext/extconf.rb +24 -5
  14. data/ext/couchbase_ext/get.c +301 -0
  15. data/ext/couchbase_ext/gethrtime.c +124 -0
  16. data/ext/couchbase_ext/http.c +402 -0
  17. data/ext/couchbase_ext/observe.c +174 -0
  18. data/ext/couchbase_ext/result.c +126 -0
  19. data/ext/couchbase_ext/stats.c +169 -0
  20. data/ext/couchbase_ext/store.c +522 -0
  21. data/ext/couchbase_ext/timer.c +192 -0
  22. data/ext/couchbase_ext/touch.c +190 -0
  23. data/ext/couchbase_ext/unlock.c +180 -0
  24. data/ext/couchbase_ext/utils.c +471 -0
  25. data/ext/couchbase_ext/version.c +147 -0
  26. data/lib/action_dispatch/middleware/session/couchbase_store.rb +38 -0
  27. data/lib/active_support/cache/couchbase_store.rb +356 -0
  28. data/lib/couchbase.rb +24 -3
  29. data/lib/couchbase/bucket.rb +372 -9
  30. data/lib/couchbase/result.rb +26 -0
  31. data/lib/couchbase/utils.rb +59 -0
  32. data/lib/couchbase/version.rb +1 -1
  33. data/lib/couchbase/view.rb +305 -0
  34. data/lib/couchbase/view_row.rb +230 -0
  35. data/lib/ext/multi_json_fix.rb +47 -0
  36. data/lib/rack/session/couchbase.rb +104 -0
  37. data/tasks/compile.rake +5 -14
  38. data/test/setup.rb +6 -2
  39. data/test/test_arithmetic.rb +32 -2
  40. data/test/test_async.rb +18 -4
  41. data/test/test_bucket.rb +11 -1
  42. data/test/test_cas.rb +13 -3
  43. data/test/test_couchbase_rails_cache_store.rb +294 -0
  44. data/test/test_delete.rb +60 -3
  45. data/test/test_format.rb +28 -17
  46. data/test/test_get.rb +91 -14
  47. data/test/test_store.rb +31 -1
  48. data/test/{test_flush.rb → test_timer.rb} +11 -18
  49. data/test/test_touch.rb +33 -5
  50. data/test/test_unlock.rb +120 -0
  51. data/test/test_utils.rb +26 -0
  52. metadata +101 -8
@@ -0,0 +1,26 @@
1
+ # Author:: Couchbase <info@couchbase.com>
2
+ # Copyright:: 2011, 2012 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 Result
20
+ def initialize(attrs = {})
21
+ attrs.each do |k, v|
22
+ instance_variable_set("@#{k}", v) if respond_to?(k)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,59 @@
1
+ # Author:: Couchbase <info@couchbase.com>
2
+ # Copyright:: 2011-2012 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
+ class Utils
21
+
22
+ def self.build_query(uri, params = nil)
23
+ uri = uri.dup
24
+ return uri if params.nil? || params.empty?
25
+ uri << "?"
26
+ uri << params.map do |k, v|
27
+ next if !v && k.to_s == "group"
28
+ if %w{key keys startkey endkey start_key end_key}.include?(k.to_s)
29
+ v = MultiJson.dump(v)
30
+ end
31
+ if v.class == Array
32
+ build_query(v.map { |x| [k, x] })
33
+ else
34
+ "#{escape(k)}=#{escape(v)}"
35
+ end
36
+ end.compact.join("&")
37
+ end
38
+
39
+ def self.escape(s)
40
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/nu) {
41
+ '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
42
+ }.tr(' ', '+')
43
+ end
44
+
45
+ # Return the bytesize of String; uses String#size under Ruby 1.8 and
46
+ # String#bytesize under 1.9.
47
+ if ''.respond_to?(:bytesize)
48
+ def self.bytesize(string)
49
+ string.bytesize
50
+ end
51
+ else
52
+ def self.bytesize(string)
53
+ string.size
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -17,5 +17,5 @@
17
17
 
18
18
  # Couchbase ruby client
19
19
  module Couchbase
20
- VERSION = "1.1.5"
20
+ VERSION = "1.2.0.beta"
21
21
  end
@@ -0,0 +1,305 @@
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
+ module Error
21
+ class View < Base
22
+ attr_reader :from, :reason
23
+
24
+ def initialize(from, reason, prefix = "SERVER: ")
25
+ @from = from
26
+ @reason = reason
27
+ super("#{prefix}#{from}: #{reason}")
28
+ end
29
+ end
30
+ end
31
+
32
+ # This class implements Couchbase View execution
33
+ #
34
+ # @see http://www.couchbase.com/docs/couchbase-manual-2.0/couchbase-views.html
35
+ class View
36
+ include Enumerable
37
+
38
+ attr_reader :params
39
+
40
+ # Set up view endpoint and optional params
41
+ #
42
+ # @param [Couchbase::Bucket] bucket Connection object which
43
+ # stores all info about how to make requests to Couchbase views.
44
+ #
45
+ # @param [String] endpoint Full Couchbase View URI.
46
+ #
47
+ # @param [Hash] params Optional parameter which will be passed to
48
+ # {View#fetch}
49
+ #
50
+ def initialize(bucket, endpoint, params = {})
51
+ @bucket = bucket
52
+ @endpoint = endpoint
53
+ @params = params
54
+ @wrapper_class = params.delete(:wrapper_class) || ViewRow
55
+ unless @wrapper_class.respond_to?(:wrap)
56
+ raise ArgumentError, "wrapper class should reposond to :wrap, check the options"
57
+ end
58
+ end
59
+
60
+ # Yields each document that was fetched by view. It doesn't instantiate
61
+ # all the results because of streaming JSON parser. Returns Enumerator
62
+ # unless block given.
63
+ #
64
+ # @param [Hash] params Params for Couchdb query. Some useful are:
65
+ # :start_key, :start_key_doc_id, :descending. See {View#fetch}.
66
+ #
67
+ # @example Use each method with block
68
+ #
69
+ # view.each do |doc|
70
+ # # do something with doc
71
+ # end
72
+ #
73
+ # @example Use Enumerator version
74
+ #
75
+ # enum = view.each # request hasn't issued yet
76
+ # enum.map{|doc| doc.title.upcase}
77
+ #
78
+ # @example Pass options during view initialization
79
+ #
80
+ # endpoint = "http://localhost:5984/default/_design/blog/_view/recent"
81
+ # view = View.new(conn, endpoint, :descending => true)
82
+ # view.each do |document|
83
+ # # do something with document
84
+ # end
85
+ #
86
+ def each(params = {})
87
+ return enum_for(:each, params) unless block_given?
88
+ fetch(params) {|doc| yield(doc)}
89
+ end
90
+
91
+ # Registers callback function for handling error objects in view
92
+ # results stream.
93
+ #
94
+ # @yieldparam [String] from Location of the node where error occured
95
+ # @yieldparam [String] reason The reason message describing what
96
+ # happened.
97
+ #
98
+ # @example Using +#on_error+ to log all errors in view result
99
+ #
100
+ # # JSON-encoded view result
101
+ # #
102
+ # # {
103
+ # # "total_rows": 0,
104
+ # # "rows": [ ],
105
+ # # "errors": [
106
+ # # {
107
+ # # "from": "127.0.0.1:5984",
108
+ # # "reason": "Design document `_design/testfoobar` missing in database `test_db_b`."
109
+ # # },
110
+ # # {
111
+ # # "from": "http:// localhost:5984/_view_merge/",
112
+ # # "reason": "Design document `_design/testfoobar` missing in database `test_db_c`."
113
+ # # }
114
+ # # ]
115
+ # # }
116
+ #
117
+ # view.on_error do |from, reason|
118
+ # logger.warn("#{view.inspect} received the error '#{reason}' from #{from}")
119
+ # end
120
+ # docs = view.fetch
121
+ #
122
+ # @example More concise example to just count errors
123
+ #
124
+ # errcount = 0
125
+ # view.on_error{|f,r| errcount += 1}.fetch
126
+ #
127
+ def on_error(&callback)
128
+ @on_error = callback
129
+ self # enable call chains
130
+ end
131
+
132
+ # Performs query to Couchbase view. This method will stream results if block
133
+ # given or return complete result set otherwise. In latter case it defines
134
+ # method +total_rows+ returning corresponding entry from
135
+ # Couchbase result object.
136
+ #
137
+ # @note Avoid using +$+ symbol as prefix for properties in your
138
+ # documents, because server marks with it meta fields like flags and
139
+ # expiration, therefore dollar prefix is some kind of reserved. It
140
+ # won't hurt your application. Currently the {ViewRow}
141
+ # class extracts +$flags+, +$cas+ and +$expiration+ properties from
142
+ # the document and store them in {ViewRow#meta} hash.
143
+ #
144
+ # @param [Hash] params parameters for Couchbase query.
145
+ # @option params [true, false] :include_docs (false) Include the full
146
+ # content of the documents in the return.
147
+ # @option params [true, false] :descending (false) Return the documents
148
+ # in descending by key order
149
+ # @option params [String, Fixnum, Hash, Array] :key Return only
150
+ # documents that match the specified key. Will be JSON encoded.
151
+ # @option params [Array] :keys The same as +:key+, but will work for
152
+ # set of keys. Will be JSON encoded.
153
+ # @option params [String, Fixnum, Hash, Array] :startkey Return
154
+ # records starting with the specified key. +:start_key+ option should
155
+ # also work here. Will be JSON encoded.
156
+ # @option params [String] :startkey_docid Document id to start with
157
+ # (to allow pagination for duplicate startkeys). +:start_key_doc_id+
158
+ # also should work.
159
+ # @option params [String, Fixnum, Hash, Array] :endkey Stop returning
160
+ # records when the specified key is reached. +:end_key+ option should
161
+ # also work here. Will be JSON encoded.
162
+ # @option params [String] :endkey_docid Last document id to include
163
+ # in the output (to allow pagination for duplicate startkeys).
164
+ # +:end_key_doc_id+ also should work.
165
+ # @option params [true, false] :inclusive_end (true) Specifies whether
166
+ # the specified end key should be included in the result
167
+ # @option params [Fixnum] :limit Limit the number of documents in the
168
+ # output.
169
+ # @option params [Fixnum] :skip Skip this number of records before
170
+ # starting to return the results.
171
+ # @option params [String, Symbol] :on_error (:continue) Sets the
172
+ # response in the event of an error. Supported values:
173
+ # :continue:: Continue to generate view information in the event of an
174
+ # error, including the error information in the view
175
+ # response stream.
176
+ # :stop:: Stop immediately when an error condition occurs. No
177
+ # further view information will be returned.
178
+ # @option params [Fixnum] :connection_timeout (60000) Timeout before the
179
+ # view request is dropped (milliseconds)
180
+ # @option params [true, false] :reduce (true) Use the reduction function
181
+ # @option params [true, false] :group (false) Group the results using
182
+ # the reduce function to a group or single row.
183
+ # @option params [Fixnum] :group_level Specify the group level to be
184
+ # used.
185
+ # @option params [String, Symbol, false] :stale (:update_after) Allow
186
+ # the results from a stale view to be used. Supported values:
187
+ # false:: Force a view update before returning data
188
+ # :ok:: Allow stale views
189
+ # :update_after:: Allow stale view, update view after it has been
190
+ # accessed
191
+ # @option params [Hash] :body Accepts the same parameters, except
192
+ # +:body+ of course, but sends them in POST body instead of query
193
+ # string. It could be useful for really large and complex parameters.
194
+ #
195
+ # @yieldparam [Couchbase::ViewRow] document
196
+ #
197
+ # @return [Array] with documents. There will be +total_entries+
198
+ # method defined on this array if it's possible.
199
+ #
200
+ # @raise [Couchbase::Error::View] when +on_error+ callback is nil and
201
+ # error object found in the result stream.
202
+ #
203
+ # @example Query +recent_posts+ view with key filter
204
+ # doc.recent_posts(:body => {:keys => ["key1", "key2"]})
205
+ #
206
+ # @example Fetch second page of result set (splitted in 10 items per page)
207
+ # page = 2
208
+ # per_page = 10
209
+ # doc.recent_posts(:skip => (page - 1) * per_page, :limit => per_page)
210
+ #
211
+ # @example Simple join using Map/Reduce
212
+ # # Given the bucket with Posts(:id, :type, :title, :body) and
213
+ # # Comments(:id, :type, :post_id, :author, :body). The map function
214
+ # # below (in javascript) will build the View index called
215
+ # # "recent_posts_with_comments" which will behave like left inner join.
216
+ # #
217
+ # # function(doc) {
218
+ # # switch (doc.type) {
219
+ # # case "Post":
220
+ # # emit([doc.id, 0], null);
221
+ # # break;
222
+ # # case "Comment":
223
+ # # emit([doc.post_id, 1], null);
224
+ # # break;
225
+ # # }
226
+ # # }
227
+ # #
228
+ # post_id = 42
229
+ # doc.recent_posts_with_comments(:start_key => [post_id, 0],
230
+ # :end_key => [post_id, 1],
231
+ # :include_docs => true)
232
+ def fetch(params = {})
233
+ params = @params.merge(params)
234
+ options = {:chunked => true, :extended => true, :type => :view}
235
+ if body = params.delete(:body)
236
+ body = MultiJson.dump(body) unless body.is_a?(String)
237
+ options.update(:body => body, :method => params.delete(:method) || :post)
238
+ end
239
+ path = Utils.build_query(@endpoint, params)
240
+ request = @bucket.make_http_request(path, options)
241
+ res = []
242
+ request.on_body do |chunk|
243
+ res << chunk
244
+ request.pause if chunk.value.nil? || chunk.error
245
+ end
246
+ filter = ["/rows/", "/errors/"]
247
+ filter << "/total_rows" unless block_given?
248
+ parser = YAJI::Parser.new(:filter => filter, :with_path => true)
249
+ docs = []
250
+ parser.on_object do |path, obj|
251
+ case path
252
+ when "/total_rows"
253
+ # if total_rows key present, save it and take next object
254
+ docs.instance_eval("def total_rows; #{obj}; end")
255
+ when "/errors/"
256
+ from, reason = obj["from"], obj["reason"]
257
+ if @on_error
258
+ @on_error.call(from, reason)
259
+ else
260
+ raise Error::View.new(from, reason)
261
+ end
262
+ else
263
+ if block_given?
264
+ yield @wrapper_class.wrap(@bucket, obj)
265
+ else
266
+ docs << @wrapper_class.wrap(@bucket, obj)
267
+ end
268
+ end
269
+ end
270
+ # run event loop until the terminating chunk will be found
271
+ # last_res variable keeps latest known chunk of the result
272
+ last_res = nil
273
+ loop do
274
+ # feed response received chunks to the parser
275
+ while r = res.shift
276
+ if r.error
277
+ if @on_error
278
+ @on_error.call("http_error", r.error)
279
+ break
280
+ else
281
+ raise Error::View.new("http_error", r.error, nil)
282
+ end
283
+ end
284
+ last_res = r
285
+ parser << r.value
286
+ end
287
+ if last_res.nil? || !last_res.completed? # shall we run the event loop?
288
+ request.continue
289
+ else
290
+ break
291
+ end
292
+ end
293
+ # return nil for call with block
294
+ block_given? ? nil : docs
295
+ end
296
+
297
+
298
+ # Returns a string containing a human-readable representation of the {View}
299
+ #
300
+ # @return [String]
301
+ def inspect
302
+ %(#<#{self.class.name}:#{self.object_id} @endpoint=#{@endpoint.inspect} @params=#{@params.inspect}>)
303
+ end
304
+ end
305
+ end
@@ -0,0 +1,230 @@
1
+ # Author:: Couchbase <info@couchbase.com>
2
+ # Copyright:: 2011-2012 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
+ # This class encapsulates structured JSON document
20
+ #
21
+ # @since 1.2.0
22
+ #
23
+ # It behaves like Hash, but also defines special methods for each view if
24
+ # the documnent considered as Design document.
25
+ #
26
+ # @see http://www.couchbase.com/docs/couchbase-manual-2.0/couchbase-views-datastore.html
27
+ class ViewRow
28
+
29
+ # Undefine as much methods as we can to free names for views
30
+ instance_methods.each do |m|
31
+ undef_method(m) if m.to_s !~ /(?:^__|^nil\?$|^send$|^object_id$|^class$|)/
32
+ end
33
+
34
+ # The hash built from JSON document.
35
+ #
36
+ # @since 1.2.0
37
+ #
38
+ # This is complete response from the Couchbase
39
+ #
40
+ # @return [Hash]
41
+ attr_accessor :data
42
+
43
+ # The key which was emitted by map function
44
+ #
45
+ # @since 1.2.0
46
+ #
47
+ # @see http://www.couchbase.com/docs/couchbase-manual-2.0/couchbase-views-writing-map.html
48
+ #
49
+ # Usually it is String (the object +_id+) but it could be also any
50
+ # compount JSON value.
51
+ #
52
+ # @return [Object]
53
+ attr_accessor :key
54
+
55
+ # The value which was emitted by map function
56
+ #
57
+ # @since 1.2.0
58
+ #
59
+ # @see http://www.couchbase.com/docs/couchbase-manual-2.0/couchbase-views-writing-map.html
60
+ #
61
+ # @return [Object]
62
+ attr_accessor :value
63
+
64
+ # The document hash.
65
+ #
66
+ # @since 1.2.0
67
+ #
68
+ # It usually available when view executed with +:include_doc+ argument.
69
+ #
70
+ # @return [Hash]
71
+ attr_accessor :doc
72
+
73
+ # The identificator of the document
74
+ #
75
+ # @since 1.2.0
76
+ #
77
+ # @return [String]
78
+ attr_accessor :id
79
+
80
+ # The meta data linked to the document
81
+ #
82
+ # @since 1.2.0
83
+ #
84
+ # @return [Hash]
85
+ attr_accessor :meta
86
+
87
+ # The list of views defined or empty array
88
+ #
89
+ # @since 1.2.0
90
+ #
91
+ # @return [Array<View>]
92
+ attr_accessor :views
93
+
94
+ # The list of spatial views defined or empty array
95
+ #
96
+ # @since 1.2.0
97
+ #
98
+ # @return [Array<View>]
99
+ attr_accessor :spatial
100
+
101
+ # Initialize the document instance
102
+ #
103
+ # @since 1.2.0
104
+ #
105
+ # It takes reference to the bucket, data hash. It will define view
106
+ # methods if the data object looks like design document.
107
+ #
108
+ # @param [Couchbase::Bucket] bucket the reference to connection
109
+ # @param [Hash] data the data hash, which was built from JSON document
110
+ # representation
111
+ def initialize(bucket, data)
112
+ @bucket = bucket
113
+ @data = data
114
+ @key = data['key']
115
+ @value = data['value']
116
+ if data['doc']
117
+ @meta = data['doc']['meta']
118
+ @doc = data['doc']['json']
119
+ end
120
+ @id = data['id'] || @meta && @meta['id']
121
+ @views = []
122
+ @spatial = []
123
+ if design_doc?
124
+ if @doc.has_key?('views')
125
+ @doc['views'].each do |name, _|
126
+ @views << name
127
+ self.instance_eval <<-EOV, __FILE__, __LINE__ + 1
128
+ def #{name}(params = {})
129
+ View.new(@bucket, "\#{@id}/_view/#{name}", params)
130
+ end
131
+ EOV
132
+ end
133
+ end
134
+ if @doc.has_key?('spatial')
135
+ @doc['spatial'].each do |name, _|
136
+ @spatial << name
137
+ self.instance_eval <<-EOV, __FILE__, __LINE__ + 1
138
+ def #{name}(params = {})
139
+ View.new(@bucket, "\#{@id}/_spatial/#{name}", params)
140
+ end
141
+ EOV
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ # Wraps data hash into ViewRow instance
148
+ #
149
+ # @since 1.2.0
150
+ #
151
+ # @see ViewRow#initialize
152
+ #
153
+ # @param [Couchbase::Bucket] bucket the reference to connection
154
+ # @param [Hash] data the data hash, which was built from JSON document
155
+ # representation
156
+ #
157
+ # @return [ViewRow]
158
+ def self.wrap(bucket, data)
159
+ ViewRow.new(bucket, data)
160
+ end
161
+
162
+ # Get attribute of the document
163
+ #
164
+ # @since 1.2.0
165
+ #
166
+ # Fetches attribute from underlying document hash
167
+ #
168
+ # @param [String] key the attribute name
169
+ #
170
+ # @return [Object] property value or nil
171
+ def [](key)
172
+ @doc[key]
173
+ end
174
+
175
+ # Check attribute existence
176
+ #
177
+ # @since 1.2.0
178
+ #
179
+ # @param [String] key the attribute name
180
+ #
181
+ # @return [true, false] +true+ if the given attribute is present in in
182
+ # the document.
183
+ def has_key?(key)
184
+ @doc.has_key?(key)
185
+ end
186
+
187
+ # Set document attribute
188
+ #
189
+ # @since 1.2.0
190
+ #
191
+ # Set or update the attribute in the document hash
192
+ #
193
+ # @param [String] key the attribute name
194
+ # @param [Object] value the attribute value
195
+ #
196
+ # @return [Object] the value
197
+ def []=(key, value)
198
+ @doc[key] = value
199
+ end
200
+
201
+ # Check if the document is design
202
+ #
203
+ # @since 1.2.0
204
+ #
205
+ # @return [true, false]
206
+ def design_doc?
207
+ !!(@doc && @id =~ %r(_design/))
208
+ end
209
+
210
+ # Check if the document has views defines
211
+ #
212
+ # @since 1.2.0
213
+ #
214
+ # @see ViewRow#views
215
+ #
216
+ # @return [true, false] +true+ if the document have views
217
+ def has_views?
218
+ !!(design_doc? && !@views.empty?)
219
+ end
220
+
221
+ def inspect
222
+ desc = "#<#{self.class.name}:#{self.object_id} "
223
+ desc << [:@id, :@key, :@value, :@doc, :@meta, :@views].map do |iv|
224
+ "#{iv}=#{instance_variable_get(iv).inspect}"
225
+ end.join(' ')
226
+ desc << ">"
227
+ desc
228
+ end
229
+ end
230
+ end