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.
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