relaxdb 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/LICENSE +20 -0
  2. data/README.textile +200 -0
  3. data/Rakefile +63 -0
  4. data/docs/spec_results.html +1059 -0
  5. data/lib/more/atomic_bulk_save_support.rb +18 -0
  6. data/lib/more/grapher.rb +48 -0
  7. data/lib/relaxdb.rb +50 -0
  8. data/lib/relaxdb/all_delegator.rb +44 -0
  9. data/lib/relaxdb/belongs_to_proxy.rb +29 -0
  10. data/lib/relaxdb/design_doc.rb +57 -0
  11. data/lib/relaxdb/document.rb +600 -0
  12. data/lib/relaxdb/extlib.rb +24 -0
  13. data/lib/relaxdb/has_many_proxy.rb +101 -0
  14. data/lib/relaxdb/has_one_proxy.rb +42 -0
  15. data/lib/relaxdb/migration.rb +40 -0
  16. data/lib/relaxdb/migration_version.rb +21 -0
  17. data/lib/relaxdb/net_http_server.rb +61 -0
  18. data/lib/relaxdb/paginate_params.rb +53 -0
  19. data/lib/relaxdb/paginator.rb +88 -0
  20. data/lib/relaxdb/query.rb +76 -0
  21. data/lib/relaxdb/references_many_proxy.rb +97 -0
  22. data/lib/relaxdb/relaxdb.rb +250 -0
  23. data/lib/relaxdb/server.rb +109 -0
  24. data/lib/relaxdb/taf2_curb_server.rb +63 -0
  25. data/lib/relaxdb/uuid_generator.rb +21 -0
  26. data/lib/relaxdb/validators.rb +11 -0
  27. data/lib/relaxdb/view_object.rb +34 -0
  28. data/lib/relaxdb/view_result.rb +18 -0
  29. data/lib/relaxdb/view_uploader.rb +49 -0
  30. data/lib/relaxdb/views.rb +114 -0
  31. data/readme.rb +80 -0
  32. data/spec/belongs_to_spec.rb +124 -0
  33. data/spec/callbacks_spec.rb +80 -0
  34. data/spec/derived_properties_spec.rb +112 -0
  35. data/spec/design_doc_spec.rb +34 -0
  36. data/spec/doc_inheritable_spec.rb +100 -0
  37. data/spec/document_spec.rb +545 -0
  38. data/spec/has_many_spec.rb +202 -0
  39. data/spec/has_one_spec.rb +123 -0
  40. data/spec/migration_spec.rb +97 -0
  41. data/spec/migration_version_spec.rb +28 -0
  42. data/spec/paginate_params_spec.rb +15 -0
  43. data/spec/paginate_spec.rb +360 -0
  44. data/spec/query_spec.rb +90 -0
  45. data/spec/references_many_spec.rb +173 -0
  46. data/spec/relaxdb_spec.rb +364 -0
  47. data/spec/server_spec.rb +32 -0
  48. data/spec/spec.opts +1 -0
  49. data/spec/spec_helper.rb +65 -0
  50. data/spec/spec_models.rb +199 -0
  51. data/spec/view_by_spec.rb +76 -0
  52. data/spec/view_object_spec.rb +47 -0
  53. data/spec/view_spec.rb +23 -0
  54. 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