jmoses-couchbase 1.3.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. data/.gitignore +15 -0
  2. data/.travis.yml +22 -0
  3. data/.yardopts +5 -0
  4. data/CONTRIBUTING.markdown +75 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +201 -0
  7. data/Makefile +3 -0
  8. data/README.markdown +665 -0
  9. data/RELEASE_NOTES.markdown +819 -0
  10. data/Rakefile +20 -0
  11. data/couchbase.gemspec +49 -0
  12. data/examples/chat-em/Gemfile +7 -0
  13. data/examples/chat-em/README.markdown +45 -0
  14. data/examples/chat-em/server.rb +82 -0
  15. data/examples/chat-goliath-grape/Gemfile +5 -0
  16. data/examples/chat-goliath-grape/README.markdown +50 -0
  17. data/examples/chat-goliath-grape/app.rb +67 -0
  18. data/examples/chat-goliath-grape/config/app.rb +20 -0
  19. data/examples/transcoders/Gemfile +3 -0
  20. data/examples/transcoders/README.markdown +59 -0
  21. data/examples/transcoders/cb-zcat +40 -0
  22. data/examples/transcoders/cb-zcp +45 -0
  23. data/examples/transcoders/gzip_transcoder.rb +49 -0
  24. data/examples/transcoders/options.rb +54 -0
  25. data/ext/couchbase_ext/.gitignore +4 -0
  26. data/ext/couchbase_ext/arguments.c +956 -0
  27. data/ext/couchbase_ext/arithmetic.c +316 -0
  28. data/ext/couchbase_ext/bucket.c +1373 -0
  29. data/ext/couchbase_ext/context.c +65 -0
  30. data/ext/couchbase_ext/couchbase_ext.c +1364 -0
  31. data/ext/couchbase_ext/couchbase_ext.h +644 -0
  32. data/ext/couchbase_ext/delete.c +163 -0
  33. data/ext/couchbase_ext/eventmachine_plugin.c +452 -0
  34. data/ext/couchbase_ext/extconf.rb +169 -0
  35. data/ext/couchbase_ext/get.c +316 -0
  36. data/ext/couchbase_ext/gethrtime.c +129 -0
  37. data/ext/couchbase_ext/http.c +432 -0
  38. data/ext/couchbase_ext/multithread_plugin.c +1090 -0
  39. data/ext/couchbase_ext/observe.c +171 -0
  40. data/ext/couchbase_ext/plugin_common.c +171 -0
  41. data/ext/couchbase_ext/result.c +129 -0
  42. data/ext/couchbase_ext/stats.c +163 -0
  43. data/ext/couchbase_ext/store.c +542 -0
  44. data/ext/couchbase_ext/timer.c +192 -0
  45. data/ext/couchbase_ext/touch.c +186 -0
  46. data/ext/couchbase_ext/unlock.c +176 -0
  47. data/ext/couchbase_ext/utils.c +551 -0
  48. data/ext/couchbase_ext/version.c +142 -0
  49. data/lib/action_dispatch/middleware/session/couchbase_store.rb +38 -0
  50. data/lib/active_support/cache/couchbase_store.rb +430 -0
  51. data/lib/couchbase.rb +155 -0
  52. data/lib/couchbase/bucket.rb +457 -0
  53. data/lib/couchbase/cluster.rb +119 -0
  54. data/lib/couchbase/connection_pool.rb +58 -0
  55. data/lib/couchbase/constants.rb +12 -0
  56. data/lib/couchbase/result.rb +26 -0
  57. data/lib/couchbase/transcoder.rb +120 -0
  58. data/lib/couchbase/utils.rb +62 -0
  59. data/lib/couchbase/version.rb +21 -0
  60. data/lib/couchbase/view.rb +506 -0
  61. data/lib/couchbase/view_row.rb +272 -0
  62. data/lib/ext/multi_json_fix.rb +56 -0
  63. data/lib/rack/session/couchbase.rb +108 -0
  64. data/tasks/benchmark.rake +6 -0
  65. data/tasks/compile.rake +160 -0
  66. data/tasks/test.rake +100 -0
  67. data/tasks/util.rake +21 -0
  68. data/test/profile/.gitignore +1 -0
  69. data/test/profile/Gemfile +6 -0
  70. data/test/profile/benchmark.rb +195 -0
  71. data/test/setup.rb +178 -0
  72. data/test/test_arithmetic.rb +185 -0
  73. data/test/test_async.rb +316 -0
  74. data/test/test_bucket.rb +276 -0
  75. data/test/test_cas.rb +235 -0
  76. data/test/test_couchbase.rb +77 -0
  77. data/test/test_couchbase_connection_pool.rb +77 -0
  78. data/test/test_couchbase_rails_cache_store.rb +361 -0
  79. data/test/test_delete.rb +120 -0
  80. data/test/test_errors.rb +82 -0
  81. data/test/test_eventmachine.rb +70 -0
  82. data/test/test_format.rb +164 -0
  83. data/test/test_get.rb +407 -0
  84. data/test/test_stats.rb +57 -0
  85. data/test/test_store.rb +216 -0
  86. data/test/test_timer.rb +42 -0
  87. data/test/test_touch.rb +97 -0
  88. data/test/test_unlock.rb +119 -0
  89. data/test/test_utils.rb +58 -0
  90. data/test/test_version.rb +52 -0
  91. metadata +353 -0
@@ -0,0 +1,171 @@
1
+ /* vim: ft=c et ts=8 sts=4 sw=4 cino=
2
+ *
3
+ * Copyright 2011, 2012 Couchbase, Inc.
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
+ #include "couchbase_ext.h"
19
+
20
+ void
21
+ cb_observe_callback(lcb_t handle, const void *cookie, lcb_error_t error, const lcb_observe_resp_t *resp)
22
+ {
23
+ struct cb_context_st *ctx = (struct cb_context_st *)cookie;
24
+ struct cb_bucket_st *bucket = ctx->bucket;
25
+ VALUE key, res, exc;
26
+
27
+ if (resp->v.v0.key) {
28
+ key = STR_NEW((const char*)resp->v.v0.key, resp->v.v0.nkey);
29
+ exc = cb_check_error(error, "failed to execute observe request", key);
30
+ if (exc != Qnil) {
31
+ ctx->exception = exc;
32
+ }
33
+ res = rb_class_new_instance(0, NULL, cb_cResult);
34
+ rb_ivar_set(res, cb_id_iv_completed, Qfalse);
35
+ rb_ivar_set(res, cb_id_iv_error, ctx->exception);
36
+ rb_ivar_set(res, cb_id_iv_operation, cb_sym_observe);
37
+ rb_ivar_set(res, cb_id_iv_key, key);
38
+ rb_ivar_set(res, cb_id_iv_cas, ULL2NUM(resp->v.v0.cas));
39
+ rb_ivar_set(res, cb_id_iv_from_master, resp->v.v0.from_master ? Qtrue : Qfalse);
40
+ rb_ivar_set(res, cb_id_iv_time_to_persist, ULONG2NUM(resp->v.v0.ttp));
41
+ rb_ivar_set(res, cb_id_iv_time_to_replicate, ULONG2NUM(resp->v.v0.ttr));
42
+ switch (resp->v.v0.status) {
43
+ case LCB_OBSERVE_FOUND:
44
+ rb_ivar_set(res, cb_id_iv_status, cb_sym_found);
45
+ break;
46
+ case LCB_OBSERVE_PERSISTED:
47
+ rb_ivar_set(res, cb_id_iv_status, cb_sym_persisted);
48
+ break;
49
+ case LCB_OBSERVE_NOT_FOUND:
50
+ rb_ivar_set(res, cb_id_iv_status, cb_sym_not_found);
51
+ break;
52
+ default:
53
+ rb_ivar_set(res, cb_id_iv_status, Qnil);
54
+ }
55
+ if (bucket->async) { /* asynchronous */
56
+ if (ctx->proc != Qnil) {
57
+ cb_proc_call(bucket, ctx->proc, 1, res);
58
+ }
59
+ } else { /* synchronous */
60
+ if (NIL_P(ctx->exception)) {
61
+ VALUE stats = rb_hash_aref(ctx->rv, key);
62
+ if (NIL_P(stats)) {
63
+ stats = rb_ary_new();
64
+ rb_hash_aset(ctx->rv, key, stats);
65
+ }
66
+ rb_ary_push(stats, res);
67
+ }
68
+ }
69
+ } else {
70
+ if (bucket->async && ctx->proc != Qnil) {
71
+ res = rb_class_new_instance(0, NULL, cb_cResult);
72
+ rb_ivar_set(res, cb_id_iv_completed, Qtrue);
73
+ cb_proc_call(bucket, ctx->proc, 1, res);
74
+ }
75
+ ctx->nqueries--;
76
+ ctx->proc = Qnil;
77
+ if (bucket->async) {
78
+ cb_context_free(ctx);
79
+ }
80
+ }
81
+ (void)handle;
82
+ }
83
+
84
+ /*
85
+ * Observe key state
86
+ *
87
+ * @since 1.2.0.dp6
88
+ *
89
+ * @overload observe(*keys, options = {})
90
+ * @param keys [String, Symbol, Array] One or several keys to fetch
91
+ * @param options [Hash] Options for operation.
92
+ *
93
+ * @yieldparam ret [Result] the result of operation in asynchronous mode
94
+ * (valid attributes: +error+, +status+, +operation+, +key+, +cas+,
95
+ * +from_master+, +time_to_persist+, +time_to_replicate+).
96
+ *
97
+ * @return [Hash<String, Array<Result>>, Array<Result>] the state of the
98
+ * keys on all nodes. If the +keys+ argument was String or Symbol, this
99
+ * method will return just array of results (result per each node),
100
+ * otherwise it will return hash map.
101
+ *
102
+ * @example Observe single key
103
+ * c.observe("foo")
104
+ * #=> [#<Couchbase::Result:0x00000001650df0 ...>, ...]
105
+ *
106
+ * @example Observe multiple keys
107
+ * keys = ["foo", "bar"]
108
+ * stats = c.observe(keys)
109
+ * stats.size #=> 2
110
+ * stats["foo"] #=> [#<Couchbase::Result:0x00000001650df0 ...>, ...]
111
+ */
112
+
113
+ VALUE
114
+ cb_bucket_observe(int argc, VALUE *argv, VALUE self)
115
+ {
116
+ struct cb_bucket_st *bucket = DATA_PTR(self);
117
+ struct cb_context_st *ctx;
118
+ VALUE rv, proc, exc;
119
+ lcb_error_t err;
120
+ struct cb_params_st params;
121
+
122
+ if (!cb_bucket_connected_bang(bucket, cb_sym_observe)) {
123
+ return Qnil;
124
+ }
125
+
126
+ memset(&params, 0, sizeof(struct cb_params_st));
127
+ rb_scan_args(argc, argv, "0*&", &params.args, &proc);
128
+ if (!bucket->async && proc != Qnil) {
129
+ rb_raise(rb_eArgError, "synchronous mode doesn't support callbacks");
130
+ }
131
+ params.type = cb_cmd_observe;
132
+ params.bucket = bucket;
133
+ cb_params_build(&params);
134
+ ctx = cb_context_alloc_common(bucket, proc, params.cmd.observe.num);
135
+ err = lcb_observe(bucket->handle, (const void *)ctx,
136
+ params.cmd.observe.num, params.cmd.observe.ptr);
137
+ cb_params_destroy(&params);
138
+ exc = cb_check_error(err, "failed to schedule observe request", Qnil);
139
+ if (exc != Qnil) {
140
+ cb_context_free(ctx);
141
+ rb_exc_raise(exc);
142
+ }
143
+ bucket->nbytes += params.npayload;
144
+ if (bucket->async) {
145
+ cb_maybe_do_loop(bucket);
146
+ return Qnil;
147
+ } else {
148
+ if (ctx->nqueries > 0) {
149
+ /* we have some operations pending */
150
+ lcb_wait(bucket->handle);
151
+ }
152
+ exc = ctx->exception;
153
+ rv = ctx->rv;
154
+ cb_context_free(ctx);
155
+ if (exc != Qnil) {
156
+ rb_exc_raise(exc);
157
+ }
158
+ exc = bucket->exception;
159
+ if (exc != Qnil) {
160
+ bucket->exception = Qnil;
161
+ rb_exc_raise(exc);
162
+ }
163
+ if (params.cmd.observe.num > 1 || params.cmd.observe.array) {
164
+ return rv; /* return as a hash {key => {}, ...} */
165
+ } else {
166
+ VALUE vv = Qnil;
167
+ rb_hash_foreach(rv, cb_first_value_i, (VALUE)&vv);
168
+ return vv; /* return first value */
169
+ }
170
+ }
171
+ }
@@ -0,0 +1,171 @@
1
+ /* vim: ft=c et ts=8 sts=4 sw=4 cino=
2
+ *
3
+ * Copyright 2012 Couchbase, Inc.
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
+ #include "couchbase_ext.h"
19
+
20
+ #ifndef _WIN32
21
+
22
+ #include <errno.h>
23
+ #include <sys/types.h>
24
+ #include <sys/socket.h>
25
+
26
+ #ifndef RUBY_WIN32_H
27
+ # include <unistd.h>
28
+ #ifdef HAVE_FCNTL_H
29
+ # include <fcntl.h>
30
+ #endif
31
+ #define INVALID_SOCKET (-1)
32
+ #else /* RUBY_WIN32_h */
33
+ static st_table *socket_2_fd = NULL;
34
+ #endif
35
+
36
+ /* Copied from libev plugin */
37
+ lcb_ssize_t
38
+ cb_io_recv(struct lcb_io_opt_st *iops, lcb_socket_t sock,
39
+ void *buffer, lcb_size_t len, int flags)
40
+ {
41
+ lcb_ssize_t ret = recv(sock, buffer, len, flags);
42
+ if (ret < 0) {
43
+ iops->v.v0.error = errno;
44
+ }
45
+ return ret;
46
+ }
47
+
48
+ lcb_ssize_t
49
+ cb_io_recvv(struct lcb_io_opt_st *iops, lcb_socket_t sock,
50
+ struct lcb_iovec_st *iov, lcb_size_t niov)
51
+ {
52
+ struct msghdr msg;
53
+ struct iovec vec[2];
54
+ lcb_ssize_t ret;
55
+
56
+ if (niov != 2) {
57
+ return -1;
58
+ }
59
+ memset(&msg, 0, sizeof(msg));
60
+ msg.msg_iov = vec;
61
+ msg.msg_iovlen = iov[1].iov_len ? (lcb_size_t)2 : (lcb_size_t)1;
62
+ msg.msg_iov[0].iov_base = iov[0].iov_base;
63
+ msg.msg_iov[0].iov_len = iov[0].iov_len;
64
+ msg.msg_iov[1].iov_base = iov[1].iov_base;
65
+ msg.msg_iov[1].iov_len = iov[1].iov_len;
66
+ ret = recvmsg(sock, &msg, 0);
67
+
68
+ if (ret < 0) {
69
+ iops->v.v0.error = errno;
70
+ }
71
+
72
+ return ret;
73
+ }
74
+
75
+ lcb_ssize_t
76
+ cb_io_send(struct lcb_io_opt_st *iops, lcb_socket_t sock,
77
+ const void *msg, lcb_size_t len, int flags)
78
+ {
79
+ lcb_ssize_t ret = send(sock, msg, len, flags);
80
+ if (ret < 0) {
81
+ iops->v.v0.error = errno;
82
+ }
83
+ return ret;
84
+ }
85
+
86
+ lcb_ssize_t
87
+ cb_io_sendv(struct lcb_io_opt_st *iops, lcb_socket_t sock,
88
+ struct lcb_iovec_st *iov, lcb_size_t niov)
89
+ {
90
+ struct msghdr msg;
91
+ struct iovec vec[2];
92
+ lcb_ssize_t ret;
93
+
94
+ if (niov != 2) {
95
+ return -1;
96
+ }
97
+ memset(&msg, 0, sizeof(msg));
98
+ msg.msg_iov = vec;
99
+ msg.msg_iovlen = iov[1].iov_len ? (lcb_size_t)2 : (lcb_size_t)1;
100
+ msg.msg_iov[0].iov_base = iov[0].iov_base;
101
+ msg.msg_iov[0].iov_len = iov[0].iov_len;
102
+ msg.msg_iov[1].iov_base = iov[1].iov_base;
103
+ msg.msg_iov[1].iov_len = iov[1].iov_len;
104
+ ret = sendmsg(sock, &msg, 0);
105
+
106
+ if (ret < 0) {
107
+ iops->v.v0.error = errno;
108
+ }
109
+ return ret;
110
+ }
111
+
112
+ static int
113
+ make_socket_nonblocking(lcb_socket_t sock)
114
+ {
115
+ int flags = 0;
116
+ #ifdef F_GETFL
117
+ if ((flags = fcntl(sock, F_GETFL, NULL)) < 0) {
118
+ return -1;
119
+ }
120
+ #endif
121
+ if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) {
122
+ return -1;
123
+ }
124
+
125
+ return 0;
126
+ }
127
+
128
+ static int
129
+ close_socket(lcb_socket_t sock)
130
+ {
131
+ return close(sock);
132
+ }
133
+
134
+ lcb_socket_t
135
+ cb_io_socket(struct lcb_io_opt_st *iops, int domain, int type,
136
+ int protocol)
137
+ {
138
+ lcb_socket_t sock = socket(domain, type, protocol);
139
+ if (sock == INVALID_SOCKET) {
140
+ iops->v.v0.error = errno;
141
+ } else {
142
+ if (make_socket_nonblocking(sock) != 0) {
143
+ int error = errno;
144
+ iops->v.v0.close(iops, sock);
145
+ iops->v.v0.error = error;
146
+ sock = INVALID_SOCKET;
147
+ }
148
+ }
149
+
150
+ return sock;
151
+ }
152
+
153
+ void
154
+ cb_io_close(struct lcb_io_opt_st *iops, lcb_socket_t sock)
155
+ {
156
+ close_socket(sock);
157
+ (void)iops;
158
+ }
159
+
160
+ int
161
+ cb_io_connect(struct lcb_io_opt_st *iops, lcb_socket_t sock,
162
+ const struct sockaddr *name, unsigned int namelen)
163
+ {
164
+ int ret = connect(sock, name, (socklen_t)namelen);
165
+ if (ret < 0) {
166
+ iops->v.v0.error = errno;
167
+ }
168
+ return ret;
169
+ }
170
+
171
+ #endif
@@ -0,0 +1,129 @@
1
+ /* vim: ft=c et ts=8 sts=4 sw=4 cino=
2
+ *
3
+ * Copyright 2011, 2012 Couchbase, Inc.
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
+ #include "couchbase_ext.h"
19
+
20
+ /*
21
+ * Check if result of operation was successful.
22
+ *
23
+ * @since 1.0.0
24
+ *
25
+ * @return [true, false] +false+ if there is an +error+ object attached,
26
+ * +false+ otherwise.
27
+ */
28
+ VALUE
29
+ cb_result_success_p(VALUE self)
30
+ {
31
+ return RTEST(rb_attr_get(self, cb_id_iv_error)) ? Qfalse : Qtrue;
32
+ }
33
+
34
+ /*
35
+ * Returns a string containing a human-readable representation of the Result.
36
+ *
37
+ * @since 1.0.0
38
+ *
39
+ * @return [String]
40
+ */
41
+ VALUE
42
+ cb_result_inspect(VALUE self)
43
+ {
44
+ VALUE str, attr;
45
+ char buf[100];
46
+
47
+ str = rb_str_buf_new2("#<");
48
+ rb_str_buf_cat2(str, rb_obj_classname(self));
49
+ snprintf(buf, 100, ":%p", (void *)self);
50
+ rb_str_buf_cat2(str, buf);
51
+
52
+ attr = rb_attr_get(self, cb_id_iv_operation);
53
+ if (RTEST(attr)) {
54
+ rb_str_buf_cat2(str, " operation=");
55
+ rb_str_append(str, rb_inspect(attr));
56
+ }
57
+
58
+ attr = rb_attr_get(self, cb_id_iv_error);
59
+ if (RTEST(attr)) {
60
+ rb_str_buf_cat2(str, " error=");
61
+ rb_str_append(str, rb_inspect(attr));
62
+ }
63
+
64
+ attr = rb_attr_get(self, cb_id_iv_value);
65
+ if (RTEST(attr) && RTEST(rb_obj_is_kind_of(attr, cb_cBucket))) {
66
+ rb_str_buf_cat2(str, " bucket="); /* value also accessible using alias #bucket */
67
+ rb_str_append(str, rb_inspect(attr));
68
+ }
69
+
70
+ attr = rb_attr_get(self, cb_id_iv_key);
71
+ if (RTEST(attr)) {
72
+ rb_str_buf_cat2(str, " key=");
73
+ rb_str_append(str, rb_inspect(attr));
74
+ }
75
+
76
+ attr = rb_attr_get(self, cb_id_iv_status);
77
+ if (RTEST(attr)) {
78
+ rb_str_buf_cat2(str, " status=");
79
+ rb_str_append(str, rb_inspect(attr));
80
+ }
81
+
82
+ attr = rb_attr_get(self, cb_id_iv_cas);
83
+ if (RTEST(attr)) {
84
+ rb_str_buf_cat2(str, " cas=");
85
+ rb_str_append(str, rb_inspect(attr));
86
+ }
87
+
88
+ attr = rb_attr_get(self, cb_id_iv_flags);
89
+ if (RTEST(attr)) {
90
+ rb_str_buf_cat2(str, " flags=0x");
91
+ rb_str_append(str, rb_funcall(attr, cb_id_to_s, 1, INT2FIX(16)));
92
+ }
93
+
94
+ attr = rb_attr_get(self, cb_id_iv_node);
95
+ if (RTEST(attr)) {
96
+ rb_str_buf_cat2(str, " node=");
97
+ rb_str_append(str, rb_inspect(attr));
98
+ }
99
+
100
+ attr = rb_attr_get(self, cb_id_iv_from_master);
101
+ if (attr != Qnil) {
102
+ rb_str_buf_cat2(str, " from_master=");
103
+ rb_str_append(str, rb_inspect(attr));
104
+ }
105
+
106
+ attr = rb_attr_get(self, cb_id_iv_time_to_persist);
107
+ if (RTEST(attr)) {
108
+ rb_str_buf_cat2(str, " time_to_persist=");
109
+ rb_str_append(str, rb_inspect(attr));
110
+ }
111
+
112
+ attr = rb_attr_get(self, cb_id_iv_time_to_replicate);
113
+ if (RTEST(attr)) {
114
+ rb_str_buf_cat2(str, " time_to_replicate=");
115
+ rb_str_append(str, rb_inspect(attr));
116
+ }
117
+
118
+ attr = rb_attr_get(self, cb_id_iv_headers);
119
+ if (RTEST(attr)) {
120
+ rb_str_buf_cat2(str, " headers=");
121
+ rb_str_append(str, rb_inspect(attr));
122
+ }
123
+
124
+ rb_str_buf_cat2(str, ">");
125
+
126
+ return str;
127
+ }
128
+
129
+