careo-makura 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +18 -0
- data/README.md +41 -0
- data/bin/makura +12 -0
- data/example/blog.rb +53 -0
- data/example/couch/map/author_all.js +5 -0
- data/example/couch/map/author_posts.js +5 -0
- data/example/couch/map/post_all.js +5 -0
- data/example/couch/map/post_comments.js +5 -0
- data/example/couch/reduce/sum_length.js +7 -0
- data/lib/makura.rb +62 -0
- data/lib/makura/database.rb +216 -0
- data/lib/makura/design.rb +37 -0
- data/lib/makura/error.rb +14 -0
- data/lib/makura/http_methods.rb +19 -0
- data/lib/makura/layout.rb +62 -0
- data/lib/makura/model.rb +372 -0
- data/lib/makura/plugin/pager.rb +56 -0
- data/lib/makura/server.rb +203 -0
- data/lib/makura/uuid_cache.rb +23 -0
- data/makura.gemspec +41 -0
- metadata +91 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
module Makura
|
2
|
+
module Plugin
|
3
|
+
module Pager
|
4
|
+
module SingletonMethods
|
5
|
+
def pager(page, limit)
|
6
|
+
Makura::Plugin::Pager::Pagination.new(self, :pager, page, limit)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Pagination
|
11
|
+
def initialize(model, view, page, limit)
|
12
|
+
@model, @view, @page, @limit = model, view, page, limit
|
13
|
+
end
|
14
|
+
|
15
|
+
# /pager/_all_docs?count=10&group=true
|
16
|
+
# /pager/_all_docs?startkey=%224f9dca1c66121f9320a69553546db07a%22&startkey_docid=4f9dca1c66121f9320a69553546db07a&skip=1&descending=false&count=10&group=true
|
17
|
+
# /pager/_all_docs?startkey=%22_design%2FUser%22&startkey_docid=_design%2FUser&skip=1&descending=false&count=10&group=true
|
18
|
+
# /pager/_all_docs?startkey=%22d850f0801686b85035680bb6f38d5c5c%22&startkey_docid=d850f0801686b85035680bb6f38d5c5c&skip=1&descending=false&count=10&group=true
|
19
|
+
|
20
|
+
# NOTE:
|
21
|
+
# * descending should be true if you page backwards
|
22
|
+
|
23
|
+
include Enumerable
|
24
|
+
|
25
|
+
def each(start_id = nil, descending = false, &block)
|
26
|
+
opts = {
|
27
|
+
:count => @limit,
|
28
|
+
:group => true,
|
29
|
+
:descending => descending,
|
30
|
+
# :include_docs => true,
|
31
|
+
}
|
32
|
+
|
33
|
+
if start_id
|
34
|
+
opts[:skip] = 1
|
35
|
+
opts[:startkey_docid] = start_id
|
36
|
+
opts[:startkey] = start_id
|
37
|
+
end
|
38
|
+
|
39
|
+
@model.view(@view, opts).each(&block)
|
40
|
+
end
|
41
|
+
|
42
|
+
def count
|
43
|
+
end
|
44
|
+
|
45
|
+
def first_page?
|
46
|
+
end
|
47
|
+
|
48
|
+
def last_page?
|
49
|
+
end
|
50
|
+
|
51
|
+
def empty?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
module Makura
|
2
|
+
class Server
|
3
|
+
include HTTPMethods
|
4
|
+
attr_accessor :uri, :cache_ttl, :cache_tries
|
5
|
+
|
6
|
+
COUCHDB_URI = 'http://localhost:5984'
|
7
|
+
CACHE_TTL = 5
|
8
|
+
CACHE_TRIES = 2
|
9
|
+
|
10
|
+
# Usage:
|
11
|
+
# server = Makura::Server.new
|
12
|
+
# #<URI::HTTP:0xb778ce38 URL:http://localhost:5984>
|
13
|
+
# server.info
|
14
|
+
# {"couchdb"=>"Welcome", "version"=>"0.9.0a718650-incubating"}
|
15
|
+
|
16
|
+
def initialize(uri = COUCHDB_URI, cache_ttl = CACHE_TTL, cache_tries = CACHE_TRIES)
|
17
|
+
@uri = URI(uri.to_s)
|
18
|
+
@cache_ttl = cache_ttl
|
19
|
+
@cache_tries = cache_tries
|
20
|
+
@uuids = UUIDCache.new(self)
|
21
|
+
end
|
22
|
+
|
23
|
+
def inspect
|
24
|
+
@uri.inspect
|
25
|
+
end
|
26
|
+
|
27
|
+
# General queries
|
28
|
+
|
29
|
+
# Answers with general couchdb info, looks like:
|
30
|
+
#
|
31
|
+
# Usage:
|
32
|
+
# server.info
|
33
|
+
# # {'couchdb' => 'Welcome', 'version' => '0.9.0a718650-incubating'}
|
34
|
+
def info
|
35
|
+
get('/')
|
36
|
+
end
|
37
|
+
|
38
|
+
# Answers with configuration info.
|
39
|
+
#
|
40
|
+
# Usage:
|
41
|
+
# server.config
|
42
|
+
#
|
43
|
+
def config
|
44
|
+
get('/_config')
|
45
|
+
end
|
46
|
+
|
47
|
+
# Issue restart of the CouchDB daemon.
|
48
|
+
#
|
49
|
+
# Usage:
|
50
|
+
# server.restart
|
51
|
+
# # {'ok' => true}
|
52
|
+
def restart
|
53
|
+
post('/_restart')
|
54
|
+
end
|
55
|
+
|
56
|
+
# Array of names of databases on the server
|
57
|
+
#
|
58
|
+
# Usage:
|
59
|
+
# server.databases
|
60
|
+
# # ["another", "blog", "makura-spec"]
|
61
|
+
def databases
|
62
|
+
get('/_all_dbs')
|
63
|
+
end
|
64
|
+
|
65
|
+
# Return new database instance using this server instance.
|
66
|
+
#
|
67
|
+
# Usage:
|
68
|
+
# foo = server.database('foo')
|
69
|
+
# # #<Makura::Database 'http://localhost:5984/foo'>
|
70
|
+
# server.databases
|
71
|
+
# # ["another", "blog", "foo", "makura-spec"]
|
72
|
+
|
73
|
+
def database(name)
|
74
|
+
Database.new(self, name)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Answers with an uuid from the UUIDCache.
|
78
|
+
#
|
79
|
+
# Usage:
|
80
|
+
# server.next_uuid
|
81
|
+
# # "55fdca746fa5a5b56f5270875477a2cc"
|
82
|
+
|
83
|
+
def next_uuid
|
84
|
+
@uuids.next
|
85
|
+
end
|
86
|
+
|
87
|
+
def start_cache(namespace = 'makura', *servers)
|
88
|
+
servers << 'localhost:11211' if servers.empty?
|
89
|
+
@cache = MemCache.new(servers, :namespace => namespace, :multithread => true)
|
90
|
+
end
|
91
|
+
|
92
|
+
def stop_cache
|
93
|
+
@cache = nil
|
94
|
+
end
|
95
|
+
|
96
|
+
def cached(request, ttl = cache_ttl, tries = cache_tries)
|
97
|
+
key = request[:url]
|
98
|
+
|
99
|
+
unless response = @cache.get(key)
|
100
|
+
response = execute(request)
|
101
|
+
@cache.add(key, response, ttl)
|
102
|
+
end
|
103
|
+
|
104
|
+
return response
|
105
|
+
rescue MemCache::MemCacheError => error
|
106
|
+
servers = @cache.servers.map{|s| "#{s.host}:#{s.port}"}
|
107
|
+
start_cache(@cache.namespace, *servers)
|
108
|
+
tries -= 1
|
109
|
+
retry if tries > 0
|
110
|
+
warn "[makura caching disabled] #{error.message}"
|
111
|
+
@cache = nil
|
112
|
+
execute(request)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Helpers
|
116
|
+
|
117
|
+
def request(method, path, params = {})
|
118
|
+
keep_raw = params.delete(:raw)
|
119
|
+
payload = params.delete(:payload)
|
120
|
+
payload = payload.to_json if payload and not keep_raw
|
121
|
+
headers = {}
|
122
|
+
|
123
|
+
if content_type = params.delete('Content-Type')
|
124
|
+
headers['Content-Type'] = content_type
|
125
|
+
end
|
126
|
+
|
127
|
+
params.delete_if{|k,v| v.nil? }
|
128
|
+
uri = uri(path, params).to_s
|
129
|
+
|
130
|
+
request = {
|
131
|
+
:method => method,
|
132
|
+
:url => uri,
|
133
|
+
:payload => payload,
|
134
|
+
:headers => headers}
|
135
|
+
|
136
|
+
if @cache and request[:method] == :get
|
137
|
+
raw = cached(request)
|
138
|
+
else
|
139
|
+
raw = execute(request)
|
140
|
+
end
|
141
|
+
|
142
|
+
return raw if keep_raw
|
143
|
+
json = JSON.parse(raw)
|
144
|
+
rescue JSON::ParserError
|
145
|
+
return raw
|
146
|
+
rescue RestClient::RequestFailed => ex
|
147
|
+
raise appropriate_error(ex)
|
148
|
+
rescue RestClient::ResourceNotFound => ex
|
149
|
+
raise Error::ResourceNotFound, request[:url], ex.backtrace
|
150
|
+
rescue Errno::ECONNREFUSED
|
151
|
+
raise Error::ConnectionRefused, "Is CouchDB running at #{@uri}?"
|
152
|
+
end
|
153
|
+
|
154
|
+
def execute(request)
|
155
|
+
RestClient::Request.execute(request)
|
156
|
+
end
|
157
|
+
|
158
|
+
def appropriate_error(exception)
|
159
|
+
body = exception.response.body if exception.respond_to?(:response)
|
160
|
+
backtrace = exception.backtrace
|
161
|
+
|
162
|
+
raise(Error::RequestFailed, exception.message, backtrace) unless body
|
163
|
+
raise(Error::RequestFailed, exception.message, backtrace) if body.empty?
|
164
|
+
|
165
|
+
json = JSON.parse(body)
|
166
|
+
error, reason = json['error'], json['reason']
|
167
|
+
|
168
|
+
case error
|
169
|
+
when 'bad_request'
|
170
|
+
raise(Error::BadRequest, reason, backtrace)
|
171
|
+
when 'authorization'
|
172
|
+
raise(Error::Authorization, reason, backtrace)
|
173
|
+
when 'not_found'
|
174
|
+
raise(Error::NotFound, reason, backtrace)
|
175
|
+
when 'file_exists'
|
176
|
+
raise(Error::FileExists, reason, backtrace)
|
177
|
+
when 'missing_rev'
|
178
|
+
raise(Error::MissingRevision, reason, backtrace)
|
179
|
+
when 'conflict'
|
180
|
+
raise(Error::Conflict, reason, backtrace)
|
181
|
+
else
|
182
|
+
raise(Error::RequestFailed, json.inspect, backtrace)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
JSON_PARAMS = %w[key startkey endkey]
|
187
|
+
|
188
|
+
def paramify(hash)
|
189
|
+
hash.map{|k,v|
|
190
|
+
k = k.to_s
|
191
|
+
v = v.to_json if JSON_PARAMS.include?(k)
|
192
|
+
"#{Makura.escape(k)}=#{Makura.escape(v)}"
|
193
|
+
}.join('&')
|
194
|
+
end
|
195
|
+
|
196
|
+
def uri(path = '/', params = {})
|
197
|
+
uri = @uri.dup
|
198
|
+
uri.path = (path[0,1] == '/' ? path : "/#{path}").squeeze('/')
|
199
|
+
uri.query = paramify(params) unless params.empty?
|
200
|
+
uri
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Makura
|
2
|
+
class UUIDCache
|
3
|
+
attr_accessor :min, :max, :server, :pretty
|
4
|
+
|
5
|
+
def initialize(server, min = 500, max = 1500, pretty = true)
|
6
|
+
@server, @min, @max, @pretty = server, min, max, pretty
|
7
|
+
@uuids = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def next
|
11
|
+
fetch if @uuids.size < min
|
12
|
+
@uuids.shift
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch(count = 0)
|
16
|
+
todo = max - @uuids.size
|
17
|
+
count = [min, todo, max].sort[1]
|
18
|
+
uuids = @server.get('/_uuids', :count => count)['uuids']
|
19
|
+
uuids.map!{|u| Makura.pretty_from_md5(u) } if pretty
|
20
|
+
@uuids.concat(uuids)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/makura.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "makura"
|
3
|
+
s.version = "0.1"
|
4
|
+
|
5
|
+
s.summary = "Ruby wrapper around the CouchDB REST API."
|
6
|
+
s.description = "Ruby wrapper around the CouchDB REST API."
|
7
|
+
s.platform = "ruby"
|
8
|
+
s.has_rdoc = true
|
9
|
+
s.author = "Michael 'manveru' Fellinger"
|
10
|
+
s.email = "m.fellinger@gmail.com"
|
11
|
+
s.homepage = "http://github.com/manveru/makura"
|
12
|
+
s.executables = ['makura']
|
13
|
+
s.bindir = "bin"
|
14
|
+
s.require_path = "lib"
|
15
|
+
|
16
|
+
s.add_dependency('rest-client', '>= 0.8.1')
|
17
|
+
s.add_dependency('json', '>= 1.1.3')
|
18
|
+
|
19
|
+
s.files = [
|
20
|
+
"COPYING",
|
21
|
+
"README.md",
|
22
|
+
"bin/makura",
|
23
|
+
"example/blog.rb",
|
24
|
+
"example/couch/map/author_all.js",
|
25
|
+
"example/couch/map/author_posts.js",
|
26
|
+
"example/couch/map/post_all.js",
|
27
|
+
"example/couch/map/post_comments.js",
|
28
|
+
"example/couch/reduce/sum_length.js",
|
29
|
+
"lib/makura/database.rb",
|
30
|
+
"lib/makura/design.rb",
|
31
|
+
"lib/makura/error.rb",
|
32
|
+
"lib/makura/http_methods.rb",
|
33
|
+
"lib/makura/layout.rb",
|
34
|
+
"lib/makura/model.rb",
|
35
|
+
"lib/makura/plugin/pager.rb",
|
36
|
+
"lib/makura/server.rb",
|
37
|
+
"lib/makura/uuid_cache.rb",
|
38
|
+
"lib/makura.rb",
|
39
|
+
"makura.gemspec"
|
40
|
+
]
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: careo-makura
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael 'manveru' Fellinger
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-13 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rest-client
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.8.1
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: json
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.1.3
|
34
|
+
version:
|
35
|
+
description: Ruby wrapper around the CouchDB REST API.
|
36
|
+
email: m.fellinger@gmail.com
|
37
|
+
executables:
|
38
|
+
- makura
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- COPYING
|
45
|
+
- README.md
|
46
|
+
- bin/makura
|
47
|
+
- example/blog.rb
|
48
|
+
- example/couch/map/author_all.js
|
49
|
+
- example/couch/map/author_posts.js
|
50
|
+
- example/couch/map/post_all.js
|
51
|
+
- example/couch/map/post_comments.js
|
52
|
+
- example/couch/reduce/sum_length.js
|
53
|
+
- lib/makura/database.rb
|
54
|
+
- lib/makura/design.rb
|
55
|
+
- lib/makura/error.rb
|
56
|
+
- lib/makura/http_methods.rb
|
57
|
+
- lib/makura/layout.rb
|
58
|
+
- lib/makura/model.rb
|
59
|
+
- lib/makura/plugin/pager.rb
|
60
|
+
- lib/makura/server.rb
|
61
|
+
- lib/makura/uuid_cache.rb
|
62
|
+
- lib/makura.rb
|
63
|
+
- makura.gemspec
|
64
|
+
has_rdoc: true
|
65
|
+
homepage: http://github.com/manveru/makura
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: "0"
|
76
|
+
version:
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: "0"
|
82
|
+
version:
|
83
|
+
requirements: []
|
84
|
+
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 1.2.0
|
87
|
+
signing_key:
|
88
|
+
specification_version: 2
|
89
|
+
summary: Ruby wrapper around the CouchDB REST API.
|
90
|
+
test_files: []
|
91
|
+
|