relaxdb 0.3.5
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/LICENSE +20 -0
- data/README.textile +200 -0
- data/Rakefile +63 -0
- data/docs/spec_results.html +1059 -0
- data/lib/more/atomic_bulk_save_support.rb +18 -0
- data/lib/more/grapher.rb +48 -0
- data/lib/relaxdb.rb +50 -0
- data/lib/relaxdb/all_delegator.rb +44 -0
- data/lib/relaxdb/belongs_to_proxy.rb +29 -0
- data/lib/relaxdb/design_doc.rb +57 -0
- data/lib/relaxdb/document.rb +600 -0
- data/lib/relaxdb/extlib.rb +24 -0
- data/lib/relaxdb/has_many_proxy.rb +101 -0
- data/lib/relaxdb/has_one_proxy.rb +42 -0
- data/lib/relaxdb/migration.rb +40 -0
- data/lib/relaxdb/migration_version.rb +21 -0
- data/lib/relaxdb/net_http_server.rb +61 -0
- data/lib/relaxdb/paginate_params.rb +53 -0
- data/lib/relaxdb/paginator.rb +88 -0
- data/lib/relaxdb/query.rb +76 -0
- data/lib/relaxdb/references_many_proxy.rb +97 -0
- data/lib/relaxdb/relaxdb.rb +250 -0
- data/lib/relaxdb/server.rb +109 -0
- data/lib/relaxdb/taf2_curb_server.rb +63 -0
- data/lib/relaxdb/uuid_generator.rb +21 -0
- data/lib/relaxdb/validators.rb +11 -0
- data/lib/relaxdb/view_object.rb +34 -0
- data/lib/relaxdb/view_result.rb +18 -0
- data/lib/relaxdb/view_uploader.rb +49 -0
- data/lib/relaxdb/views.rb +114 -0
- data/readme.rb +80 -0
- data/spec/belongs_to_spec.rb +124 -0
- data/spec/callbacks_spec.rb +80 -0
- data/spec/derived_properties_spec.rb +112 -0
- data/spec/design_doc_spec.rb +34 -0
- data/spec/doc_inheritable_spec.rb +100 -0
- data/spec/document_spec.rb +545 -0
- data/spec/has_many_spec.rb +202 -0
- data/spec/has_one_spec.rb +123 -0
- data/spec/migration_spec.rb +97 -0
- data/spec/migration_version_spec.rb +28 -0
- data/spec/paginate_params_spec.rb +15 -0
- data/spec/paginate_spec.rb +360 -0
- data/spec/query_spec.rb +90 -0
- data/spec/references_many_spec.rb +173 -0
- data/spec/relaxdb_spec.rb +364 -0
- data/spec/server_spec.rb +32 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +65 -0
- data/spec/spec_models.rb +199 -0
- data/spec/view_by_spec.rb +76 -0
- data/spec/view_object_spec.rb +47 -0
- data/spec/view_spec.rb +23 -0
- metadata +137 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
class Errors < Hash
|
2
|
+
alias_method :on, :[]
|
3
|
+
alias_method :count, :size
|
4
|
+
|
5
|
+
def full_messages
|
6
|
+
full_messages = []
|
7
|
+
each_key do |attr|
|
8
|
+
full_messages << attr.to_s + ": " + self[attr]
|
9
|
+
end
|
10
|
+
full_messages
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Time
|
15
|
+
|
16
|
+
# Ensure that all Times are stored as UTC
|
17
|
+
# Times in the following format may be passed directly to
|
18
|
+
# Date.new in a JavaScript runtime
|
19
|
+
def to_json
|
20
|
+
utc
|
21
|
+
%Q("#{strftime "%Y/%m/%d %H:%M:%S +0000"}")
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module RelaxDB
|
2
|
+
|
3
|
+
class HasManyProxy
|
4
|
+
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
attr_reader :children
|
8
|
+
|
9
|
+
def initialize(client, relationship, opts)
|
10
|
+
@client = client
|
11
|
+
@relationship = relationship
|
12
|
+
@opts = opts
|
13
|
+
|
14
|
+
@target_class = opts[:class]
|
15
|
+
@relationship_as_viewed_by_target = (opts[:known_as] || client.class.name.snake_case).to_s
|
16
|
+
|
17
|
+
@children = load_children
|
18
|
+
end
|
19
|
+
|
20
|
+
def <<(obj)
|
21
|
+
return false if @children.include?(obj)
|
22
|
+
|
23
|
+
obj.send("#{@relationship_as_viewed_by_target}=".to_sym, @client)
|
24
|
+
if obj.save
|
25
|
+
@children << obj
|
26
|
+
self
|
27
|
+
else
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def clear
|
33
|
+
@children.each do |c|
|
34
|
+
break_back_link c
|
35
|
+
end
|
36
|
+
@children.clear
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete(obj)
|
40
|
+
obj = @children.delete(obj)
|
41
|
+
break_back_link(obj) if obj
|
42
|
+
end
|
43
|
+
|
44
|
+
def break_back_link(obj)
|
45
|
+
if obj
|
46
|
+
obj.send("#{@relationship_as_viewed_by_target}=".to_sym, nil)
|
47
|
+
obj.save
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def empty?
|
52
|
+
@children.empty?
|
53
|
+
end
|
54
|
+
|
55
|
+
def size
|
56
|
+
@children.size
|
57
|
+
end
|
58
|
+
|
59
|
+
def [](*args)
|
60
|
+
@children[*args]
|
61
|
+
end
|
62
|
+
|
63
|
+
def first
|
64
|
+
@children[0]
|
65
|
+
end
|
66
|
+
|
67
|
+
def last
|
68
|
+
@children[size-1]
|
69
|
+
end
|
70
|
+
|
71
|
+
def each(&blk)
|
72
|
+
@children.each(&blk)
|
73
|
+
end
|
74
|
+
|
75
|
+
def reload
|
76
|
+
@children = load_children
|
77
|
+
end
|
78
|
+
|
79
|
+
def load_children
|
80
|
+
view_name = "#{@client.class}_#{@relationship}"
|
81
|
+
@children = RelaxDB.view(view_name, :key => @client._id)
|
82
|
+
end
|
83
|
+
|
84
|
+
def children=(children)
|
85
|
+
children.each do |obj|
|
86
|
+
obj.send("#{@relationship_as_viewed_by_target}=".to_sym, @client)
|
87
|
+
end
|
88
|
+
@children = children
|
89
|
+
end
|
90
|
+
|
91
|
+
def inspect
|
92
|
+
@children.inspect
|
93
|
+
end
|
94
|
+
|
95
|
+
# Play nice with Merb partials - [ obj ].flatten invokes
|
96
|
+
# obj.to_ary if it responds to to_ary
|
97
|
+
alias_method :to_ary, :to_a
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RelaxDB
|
2
|
+
|
3
|
+
class HasOneProxy
|
4
|
+
|
5
|
+
def initialize(client, relationship)
|
6
|
+
@client = client
|
7
|
+
@relationship = relationship
|
8
|
+
@target_class = @relationship.to_s.camel_case
|
9
|
+
@relationship_as_viewed_by_target = client.class.name.snake_case
|
10
|
+
|
11
|
+
@target = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def target
|
15
|
+
return @target if @target
|
16
|
+
@target = load_target
|
17
|
+
end
|
18
|
+
|
19
|
+
# All database changes performed by this method would ideally be done in a transaction
|
20
|
+
def target=(new_target)
|
21
|
+
# Nullify any existing relationship on assignment
|
22
|
+
old_target = target
|
23
|
+
if old_target
|
24
|
+
old_target.send("#{@relationship_as_viewed_by_target}=".to_sym, nil)
|
25
|
+
old_target.save
|
26
|
+
end
|
27
|
+
|
28
|
+
@target = new_target
|
29
|
+
unless @target.nil?
|
30
|
+
@target.send("#{@relationship_as_viewed_by_target}=".to_sym, @client)
|
31
|
+
@target.save
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def load_target
|
36
|
+
view_name = "#{@client.class}_#{@relationship}"
|
37
|
+
RelaxDB.view(view_name, :key => @client._id).first
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module RelaxDB
|
2
|
+
|
3
|
+
class Migration
|
4
|
+
|
5
|
+
def self.run klass, limit = 1000
|
6
|
+
query = lambda do |page_params|
|
7
|
+
RelaxDB.paginate_view "#{klass}_all", :startkey => nil, :endkey => {}, :attributes => [:_id],
|
8
|
+
:page_params => page_params, :limit => limit
|
9
|
+
end
|
10
|
+
|
11
|
+
objs = query.call({})
|
12
|
+
until objs.empty?
|
13
|
+
migrated = objs.map { |o| yield o }.flatten.reject { |o| o.nil? }
|
14
|
+
RelaxDB.bulk_save! *migrated
|
15
|
+
objs = objs.next_params ? query.call(objs.next_params) : []
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Runs all outstanding migrations in a given directory
|
21
|
+
#
|
22
|
+
# ==== Example
|
23
|
+
# RelaxDB::Migration.run_all Dir["couchdb/migrations/**/*.rb"]
|
24
|
+
#
|
25
|
+
def self.run_all file_names, action = lambda { |fn| require fn }
|
26
|
+
v = RelaxDB::MigrationVersion.version
|
27
|
+
file_names.select { |fn| fv(fn) > v }.each do |fn|
|
28
|
+
RelaxDB.logger.info "Applying #{fn}"
|
29
|
+
action.call fn
|
30
|
+
RelaxDB::MigrationVersion.update fv(fn)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.fv file_name
|
35
|
+
File.basename(file_name).split("_")[0].to_i
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class RelaxDB::MigrationVersion < RelaxDB::Document
|
2
|
+
|
3
|
+
DOC_ID = "relaxdb_migration_version"
|
4
|
+
|
5
|
+
property :version, :default => 0
|
6
|
+
|
7
|
+
def self.version
|
8
|
+
retrieve.version
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.update v
|
12
|
+
mv = retrieve
|
13
|
+
mv.version = v
|
14
|
+
mv.save!
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.retrieve
|
18
|
+
(v = RelaxDB.load(DOC_ID)) ? v : new(:_id => DOC_ID).save!
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module RelaxDB
|
2
|
+
|
3
|
+
class Server
|
4
|
+
|
5
|
+
def initialize(host, port)
|
6
|
+
@host = host
|
7
|
+
@port = port
|
8
|
+
end
|
9
|
+
|
10
|
+
def delete(uri)
|
11
|
+
request(Net::HTTP::Delete.new(uri))
|
12
|
+
end
|
13
|
+
|
14
|
+
def get(uri)
|
15
|
+
request(Net::HTTP::Get.new(uri))
|
16
|
+
end
|
17
|
+
|
18
|
+
def put(uri, json)
|
19
|
+
req = Net::HTTP::Put.new(uri)
|
20
|
+
req["content-type"] = "application/json"
|
21
|
+
req.body = json
|
22
|
+
request(req)
|
23
|
+
end
|
24
|
+
|
25
|
+
def post(uri, json)
|
26
|
+
req = Net::HTTP::Post.new(uri)
|
27
|
+
req["content-type"] = "application/json"
|
28
|
+
req.body = json
|
29
|
+
request(req)
|
30
|
+
end
|
31
|
+
|
32
|
+
def request(req)
|
33
|
+
res = Net::HTTP.start(@host, @port) {|http|
|
34
|
+
http.request(req)
|
35
|
+
}
|
36
|
+
if (not res.kind_of?(Net::HTTPSuccess))
|
37
|
+
handle_error(req, res)
|
38
|
+
end
|
39
|
+
res
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
"http://#{@host}:#{@port}/"
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def handle_error(req, res)
|
49
|
+
msg = "#{res.code}:#{res.message}\nMETHOD:#{req.method}\nURI:#{req.path}\n#{res.body}"
|
50
|
+
begin
|
51
|
+
klass = RelaxDB.const_get("HTTP_#{res.code}")
|
52
|
+
e = klass.new(msg)
|
53
|
+
rescue
|
54
|
+
e = RuntimeError.new(msg)
|
55
|
+
end
|
56
|
+
|
57
|
+
raise e
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module RelaxDB
|
2
|
+
|
3
|
+
class PaginateParams
|
4
|
+
|
5
|
+
@@params = %w(key startkey startkey_docid endkey endkey_docid limit update descending group reduce include_docs)
|
6
|
+
|
7
|
+
@@params.each do |param|
|
8
|
+
define_method(param.to_sym) do |*val|
|
9
|
+
if val.empty?
|
10
|
+
instance_variable_get("@#{param}")
|
11
|
+
else
|
12
|
+
instance_variable_set("@#{param}", val[0])
|
13
|
+
# null is meaningful to CouchDB. _set allows us to know that a param has been set, even to nil
|
14
|
+
instance_variable_set("@#{param}_set", true)
|
15
|
+
self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(params)
|
21
|
+
params.each { |k, v| send(k, v) }
|
22
|
+
|
23
|
+
# If a client hasn't explicitly set descending, set it to the CouchDB default
|
24
|
+
@descending = false if @descending.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
def update(params)
|
28
|
+
@order_inverted = params[:descending].nil? ? false : @descending ^ params[:descending]
|
29
|
+
@descending = !@descending if @order_inverted
|
30
|
+
|
31
|
+
@endkey = @startkey if @order_inverted
|
32
|
+
|
33
|
+
@startkey = params[:startkey] || @startkey
|
34
|
+
|
35
|
+
@skip = 1 if params[:startkey]
|
36
|
+
|
37
|
+
@startkey_docid = params[:startkey_docid] if params[:startkey_docid]
|
38
|
+
@endkey_docid = params[:endkey_docid] if params[:endkey_docid]
|
39
|
+
end
|
40
|
+
|
41
|
+
def order_inverted?
|
42
|
+
@order_inverted
|
43
|
+
end
|
44
|
+
|
45
|
+
def invalid?
|
46
|
+
# Simply because allowing either to be omitted increases the complexity of the paginator
|
47
|
+
@startkey_set && @endkey_set ? nil : "Both startkey and endkey must be set"
|
48
|
+
end
|
49
|
+
alias error_msg invalid?
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module RelaxDB
|
2
|
+
|
3
|
+
class Paginator
|
4
|
+
|
5
|
+
attr_reader :paginate_params
|
6
|
+
|
7
|
+
def initialize(paginate_params, page_params)
|
8
|
+
@paginate_params = paginate_params
|
9
|
+
@orig_paginate_params = @paginate_params.clone
|
10
|
+
|
11
|
+
page_params = page_params.is_a?(String) ? JSON.parse(page_params).to_mash : page_params
|
12
|
+
# Where the magic happens - the original params are updated with the page specific params
|
13
|
+
@paginate_params.update(page_params)
|
14
|
+
end
|
15
|
+
|
16
|
+
def total_doc_count(view_name)
|
17
|
+
RelaxDB.view view_name, :startkey => @orig_paginate_params.startkey, :endkey => @orig_paginate_params.endkey,
|
18
|
+
:descending => @orig_paginate_params.descending, :reduce => true
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# view_keys are used to determine the params for the prev and next links. If a view_key is a symbol
|
23
|
+
# the key value will be the result of invoking the method named by the symbol on the first / last
|
24
|
+
# element in the result set. If a view_key is not a symbol, its value will be used directly.
|
25
|
+
#
|
26
|
+
def add_next_and_prev(docs, view_name, view_keys)
|
27
|
+
unless docs.empty?
|
28
|
+
no_docs = docs.size
|
29
|
+
offset = docs.offset
|
30
|
+
orig_offset = orig_offset(view_name)
|
31
|
+
total_doc_count = total_doc_count(view_name)
|
32
|
+
|
33
|
+
next_exists = !@paginate_params.order_inverted? ? (offset - orig_offset + no_docs < total_doc_count) : true
|
34
|
+
next_params = create_next(docs.last, view_keys) if next_exists
|
35
|
+
|
36
|
+
prev_exists = @paginate_params.order_inverted? ? (offset - orig_offset + no_docs < total_doc_count) :
|
37
|
+
(offset - orig_offset == 0 ? false : true)
|
38
|
+
prev_params = create_prev(docs.first, view_keys) if prev_exists
|
39
|
+
else
|
40
|
+
next_exists = prev_exists = false
|
41
|
+
end
|
42
|
+
|
43
|
+
docs.meta_class.instance_eval do
|
44
|
+
define_method(:next_params) { next_exists ? next_params : false }
|
45
|
+
define_method(:next_query) { next_exists ? "page_params=#{::CGI::escape(next_params.to_json)}" : false }
|
46
|
+
|
47
|
+
define_method(:prev_params) { prev_exists ? prev_params : false }
|
48
|
+
define_method(:prev_query) { prev_exists ? "page_params=#{::CGI::escape(prev_params.to_json)}" : false }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_next(doc, view_keys)
|
53
|
+
next_key = view_keys_to_vals doc, view_keys
|
54
|
+
next_key = next_key.length == 1 ? next_key[0] : next_key
|
55
|
+
next_key_docid = doc._id
|
56
|
+
{ :startkey => next_key, :startkey_docid => next_key_docid, :descending => @orig_paginate_params.descending }
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_prev(doc, view_keys)
|
60
|
+
prev_key = view_keys_to_vals doc, view_keys
|
61
|
+
prev_key = prev_key.length == 1 ? prev_key[0] : prev_key
|
62
|
+
prev_key_docid = doc._id
|
63
|
+
prev_params = { :startkey => prev_key, :startkey_docid => prev_key_docid, :descending => !@orig_paginate_params.descending }
|
64
|
+
end
|
65
|
+
|
66
|
+
def view_keys_to_vals doc, view_keys
|
67
|
+
view_keys.map do |k|
|
68
|
+
if k.is_a?(Symbol) then doc.send(k)
|
69
|
+
elsif k.is_a?(Proc) then k.call(doc)
|
70
|
+
else k
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def orig_offset(view_name)
|
76
|
+
if @paginate_params.order_inverted?
|
77
|
+
params = {:startkey => @orig_paginate_params.endkey, :descending => !@orig_paginate_params.descending}
|
78
|
+
else
|
79
|
+
params = {:startkey => @orig_paginate_params.startkey, :descending => @orig_paginate_params.descending}
|
80
|
+
end
|
81
|
+
params[:limit] = 1
|
82
|
+
|
83
|
+
RelaxDB.rf_view(view_name, params).offset
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module RelaxDB
|
2
|
+
|
3
|
+
# A Query is used to build the query string made against a view
|
4
|
+
# All parameter values are first JSON encoded and then URL encoded
|
5
|
+
# Nil values are set to the empty string
|
6
|
+
|
7
|
+
class Query
|
8
|
+
|
9
|
+
# keys is not included in the standard param as it is significantly different from the others
|
10
|
+
@@params = %w(key startkey startkey_docid endkey endkey_docid limit update
|
11
|
+
descending skip group group_level reduce include_docs batch)
|
12
|
+
|
13
|
+
@@params.each do |param|
|
14
|
+
define_method(param.to_sym) do |*val|
|
15
|
+
if val.empty?
|
16
|
+
instance_variable_get("@#{param}")
|
17
|
+
else
|
18
|
+
instance_variable_set("@#{param}", val[0])
|
19
|
+
# null is meaningful to CouchDB. _set allows us to know that a param has been set, even to nil
|
20
|
+
instance_variable_set("@#{param}_set", true)
|
21
|
+
self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize view_name, params = {}
|
27
|
+
@view_name = view_name
|
28
|
+
params.each { |k, v| send k, v }
|
29
|
+
end
|
30
|
+
|
31
|
+
def keys(keys=nil)
|
32
|
+
if keys.nil?
|
33
|
+
@keys
|
34
|
+
else
|
35
|
+
@keys = { :keys => keys }.to_json
|
36
|
+
self
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# If set to true RelaxDB.view will return unprocessed data
|
41
|
+
def raw(val = nil)
|
42
|
+
if val.nil?
|
43
|
+
@raw
|
44
|
+
else
|
45
|
+
@raw = val
|
46
|
+
self
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def view_path
|
51
|
+
uri = (@view_name =~ /^_/) ? @view_name : "_design/#{RelaxDB.dd}/_view/#{@view_name}"
|
52
|
+
|
53
|
+
query = ""
|
54
|
+
@@params.each do |param|
|
55
|
+
val_set = instance_variable_get("@#{param}_set")
|
56
|
+
if val_set
|
57
|
+
val = instance_variable_get("@#{param}")
|
58
|
+
val = val.to_json unless ["startkey_docid", "endkey_docid"].include?(param)
|
59
|
+
query << "&#{param}=#{::CGI::escape(val)}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
uri << query.sub(/^&/, "?")
|
64
|
+
end
|
65
|
+
|
66
|
+
def merge(paginate_params)
|
67
|
+
paginate_params.instance_variables.each do |pp|
|
68
|
+
val = paginate_params.instance_variable_get(pp)
|
69
|
+
method_name = pp[1, pp.length]
|
70
|
+
send(method_name, val) if methods.include? method_name
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|