divan 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +3 -0
- data/Rakefile.rb +43 -0
- data/VERSION +1 -0
- data/init.rb +14 -0
- data/lib/divan.rb +65 -0
- data/lib/divan/base.rb +195 -0
- data/lib/divan/database.rb +95 -0
- data/lib/divan/models/base.rb +126 -0
- data/lib/divan/utils.rb +18 -0
- data/test/test_helper.rb +3 -0
- data/test/unit/divan.rb +198 -0
- metadata +81 -0
data/README
ADDED
data/Rakefile.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'init.rb'
|
3
|
+
require 'rake'
|
4
|
+
|
5
|
+
task :create_database do
|
6
|
+
if ENV['database']
|
7
|
+
Divan[ENV['database'].to_sym].create unless Divan[ENV['database'].to_sym].exists?
|
8
|
+
else
|
9
|
+
Divan.databases.each do |name, database|
|
10
|
+
database.create unless database.exists?
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
task :create_views do
|
16
|
+
if ENV['database']
|
17
|
+
if ENV['design']
|
18
|
+
Divan[ENV['design'].to_sym].create_views
|
19
|
+
else
|
20
|
+
Divan[ENV['database'].to_sym].create_views
|
21
|
+
end
|
22
|
+
else
|
23
|
+
Divan.databases.each do |name, database|
|
24
|
+
database.create_views
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
begin
|
30
|
+
require 'jeweler'
|
31
|
+
Jeweler::Tasks.new do |gem|
|
32
|
+
gem.name = "divan"
|
33
|
+
gem.summary = "A Ruby CouchDB Client for insane people"
|
34
|
+
gem.description = "This is a very simple CouchDB client that have few dependencies.\nThis client has a lot of interesting features, for example: easy access to CouchDB revisions.\n"
|
35
|
+
gem.email = "dalthon@aluno.ita.br"
|
36
|
+
gem.homepage = "http://github.com/efqdalton/divan"
|
37
|
+
gem.authors = ["Dalton Pinto"]
|
38
|
+
gem.files.exclude "config"
|
39
|
+
end
|
40
|
+
Jeweler::GemcutterTasks.new
|
41
|
+
rescue LoadError
|
42
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
43
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
data/init.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
divan_path = File.expand_path('../lib', __FILE__)
|
2
|
+
$:.unshift(divan_path) if File.directory?(divan_path) && !$:.include?(divan_path)
|
3
|
+
|
4
|
+
require 'restclient'
|
5
|
+
require 'json'
|
6
|
+
require 'divan.rb'
|
7
|
+
|
8
|
+
Divan.load_database_configuration 'config/divan_config.yml'
|
9
|
+
|
10
|
+
# Lines below are used for debug purposes only
|
11
|
+
class POC < Divan::Models::ProofOfConcept
|
12
|
+
view_by :mod
|
13
|
+
view_by :value
|
14
|
+
end
|
data/lib/divan.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'divan/models/base'
|
2
|
+
require 'divan/base'
|
3
|
+
require 'divan/database'
|
4
|
+
require 'divan/utils'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
module Divan
|
8
|
+
@@databases = {}
|
9
|
+
|
10
|
+
def self.Model(database_config_name)
|
11
|
+
Database.model_class(database_config_name)
|
12
|
+
rescue
|
13
|
+
Divan::Models::Base
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.load_database_configuration(config_path)
|
17
|
+
YAML.load(File.read config_path).each do |name, params|
|
18
|
+
@@databases[name.to_sym] = Database.new name, params
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.[](name)
|
23
|
+
@@databases[name.to_sym]
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.databases
|
27
|
+
@@databases
|
28
|
+
end
|
29
|
+
|
30
|
+
class DatabaseNotFound < RuntimeError
|
31
|
+
attr_reader :database
|
32
|
+
def initialize(database)
|
33
|
+
@database = database
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class DatabaseAlreadyCreated < RuntimeError
|
38
|
+
attr_reader :database
|
39
|
+
def initialize(database)
|
40
|
+
@database = database
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class DocumentRevisionMissing < RuntimeError
|
45
|
+
attr_reader :document
|
46
|
+
def initialize(document)
|
47
|
+
@document = document
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class DocumentNotFound < RuntimeError
|
52
|
+
attr_reader :document
|
53
|
+
def initialize(document)
|
54
|
+
@document = document
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class DocumentConflict < RuntimeError
|
59
|
+
attr_reader :new_document, :current_document
|
60
|
+
def initialize(new_document)
|
61
|
+
@new_document = new_document
|
62
|
+
@current_document = new_document.class.find new_document.id
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/divan/base.rb
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
module Divan
|
2
|
+
class Base < Models::Base
|
3
|
+
attr_accessor :id, :rev, :attributes, :last_request
|
4
|
+
|
5
|
+
def initialize(opts = {})
|
6
|
+
opts = opts.clone
|
7
|
+
@id = opts.delete(:id) || opts.delete(:_id) || Divan::Utils.uuid
|
8
|
+
@rev = opts.delete(:rev) || opts.delete(:_rev)
|
9
|
+
@attributes = opts
|
10
|
+
@attributes[self.class.type_field.to_sym] = self.class.type_name unless self.class.top_level_model?
|
11
|
+
self.class.properties.each{ |property| @attributes[property] ||= nil }
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](key)
|
15
|
+
@attributes[key]
|
16
|
+
end
|
17
|
+
|
18
|
+
def []=(key, value)
|
19
|
+
@attributes[key] = value
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate
|
23
|
+
true
|
24
|
+
end
|
25
|
+
alias :"valid?" :validate
|
26
|
+
|
27
|
+
def database
|
28
|
+
self.class.database
|
29
|
+
end
|
30
|
+
|
31
|
+
def method_missing(method, *args, &block)
|
32
|
+
method = method.to_s
|
33
|
+
if method[-1..-1] == '='
|
34
|
+
attrib = method[0..-2].to_sym
|
35
|
+
return @attributes[attrib] = args.first
|
36
|
+
end
|
37
|
+
if( @attributes.keys.include?( (attrib = method[0..-1].to_sym) ) )
|
38
|
+
return @attributes[attrib]
|
39
|
+
end
|
40
|
+
raise NoMethodError, "undefined method '#{method}' for #{self}:#{self.class}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
"\#<#{database.name.to_s.split('_').map{|str| "#{str[0..0].upcase}#{str[1..-1]}" }} #{@attributes.inspect.gsub("\\\"", "\"")}>"
|
45
|
+
end
|
46
|
+
alias :inspect :to_s
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def document_path(params = {})
|
51
|
+
Divan::Utils.formatted_path @id, params
|
52
|
+
end
|
53
|
+
|
54
|
+
def current_document_path(params = {})
|
55
|
+
params[:rev] = @rev if @rev
|
56
|
+
document_path params
|
57
|
+
end
|
58
|
+
|
59
|
+
class << self
|
60
|
+
attr_writer :type_name, :type_field
|
61
|
+
attr_reader :view_by_params, :database
|
62
|
+
attr_accessor :model_name
|
63
|
+
|
64
|
+
def inherited(subclass)
|
65
|
+
strs = subclass.name.match(/[^:]*\Z/)[0].split(/([A-Z][^A-Z]*)/)
|
66
|
+
strs.delete ''
|
67
|
+
subclass.model_name = strs.map{ |x| x.downcase }.join('_')
|
68
|
+
if database
|
69
|
+
subclass.database = database
|
70
|
+
subclass.top_level_model! if subclass.database.name == subclass.model_name
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def type_name
|
75
|
+
@type_name ||= model_name
|
76
|
+
end
|
77
|
+
|
78
|
+
def type_field
|
79
|
+
@type_field ||= 'divan_doc_type'
|
80
|
+
end
|
81
|
+
|
82
|
+
def top_level_model!(true_or_false = true)
|
83
|
+
@top_level_model = true_or_false
|
84
|
+
end
|
85
|
+
|
86
|
+
def top_level_model?
|
87
|
+
@top_level_model ||= false
|
88
|
+
end
|
89
|
+
|
90
|
+
def properties
|
91
|
+
@properties ||= ( superclass.methods.include? :properties ) ? superclass.properties.clone : []
|
92
|
+
end
|
93
|
+
|
94
|
+
def property(*args)
|
95
|
+
properties.concat args.flatten
|
96
|
+
end
|
97
|
+
|
98
|
+
def database=(database)
|
99
|
+
undefine_views if( !@database.nil? && @database != database )
|
100
|
+
@database = database
|
101
|
+
define_view_all
|
102
|
+
define_views
|
103
|
+
@database
|
104
|
+
end
|
105
|
+
|
106
|
+
def model_name
|
107
|
+
@model_name ||= ( superclass.methods.include? :model_name ) ? superclass.model_name.clone : nil
|
108
|
+
end
|
109
|
+
|
110
|
+
[:before_save, :after_save, :before_create, :after_create, :before_validate, :after_validate].each do |method|
|
111
|
+
define_method method do |param|
|
112
|
+
eval "( @#{method}_callback ||= [] ) << param"
|
113
|
+
end
|
114
|
+
|
115
|
+
define_method "execute_#{method}_callback" do |instance|
|
116
|
+
eval <<-end_txt
|
117
|
+
@#{method}_callback ||= []
|
118
|
+
!!@#{method}_callback.each do |cb|
|
119
|
+
instance.send(cb) or break false if cb.is_a?(Symbol)
|
120
|
+
cb.call(instance) or break false if cb.is_a?(Proc)
|
121
|
+
end
|
122
|
+
end_txt
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
def define_view(param, functions)
|
128
|
+
@views ||= {}
|
129
|
+
@views[param.to_sym] = functions
|
130
|
+
end
|
131
|
+
|
132
|
+
def define_view!(param, functions)
|
133
|
+
database.views[model_name] ||= {}
|
134
|
+
database.views[model_name][param.to_sym] = functions
|
135
|
+
end
|
136
|
+
|
137
|
+
def define_view_all
|
138
|
+
if database && model_name == database.name
|
139
|
+
define_view :all, :map => "function(doc){ if(doc._id.slice(0, 7) != \"_design\"){ emit(null, doc) } }"
|
140
|
+
else
|
141
|
+
define_view :all, :map => "function(doc){ if(doc.#{type_field} == \"#{type_name}\"){ emit(null, doc) } }"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def query_view(view, key=nil, args={}, special={})
|
146
|
+
if key.is_a? Hash
|
147
|
+
special = args
|
148
|
+
args = key
|
149
|
+
else
|
150
|
+
special = args
|
151
|
+
args = {:key => key}
|
152
|
+
end
|
153
|
+
|
154
|
+
args = args.inject({}){ |hash,(k,v)| hash[k] = v.to_json; hash }
|
155
|
+
view_path = Divan::Utils.formatted_path "_design/#{model_name}/_view/#{view}", args.merge(special)
|
156
|
+
last_request = database.client[view_path].get
|
157
|
+
results = JSON.parse last_request, :symbolize_names => true
|
158
|
+
results[:rows].map do |row|
|
159
|
+
obj = self.new row[:value]
|
160
|
+
obj.last_request = last_request
|
161
|
+
obj
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
protected
|
166
|
+
|
167
|
+
def view_by(param)
|
168
|
+
@view_by_params ||= []
|
169
|
+
@view_by_params << param
|
170
|
+
define_view! "by_#{param}", :map => "function(doc) { emit(doc.#{param}, doc) }"
|
171
|
+
eval <<-end_txt
|
172
|
+
class << self
|
173
|
+
def all_by_#{param}(key, args={}, special={})
|
174
|
+
query_view :by_#{param}, key, args, special
|
175
|
+
end
|
176
|
+
|
177
|
+
def by_#{param}(key)
|
178
|
+
all_by_#{param}(:key => key, :limit => 1).first
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end_txt
|
182
|
+
end
|
183
|
+
|
184
|
+
def define_views
|
185
|
+
database.views[model_name] ||= {}
|
186
|
+
database.views[model_name].merge! @views if @views
|
187
|
+
end
|
188
|
+
|
189
|
+
def undefine_views
|
190
|
+
database.views[model_name] = nil
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Divan
|
2
|
+
module Models; end
|
3
|
+
|
4
|
+
class Database
|
5
|
+
attr_accessor :name, :host, :port, :database, :user, :password, :views
|
6
|
+
def initialize(name, options = {})
|
7
|
+
@name, @user, @password = name, options['user'], options['password']
|
8
|
+
#TODO: Add user & password support
|
9
|
+
@host = options['host'] || 'http://127.0.0.1'
|
10
|
+
@port = options['port'] || 5984
|
11
|
+
@database = options['database']
|
12
|
+
@views = {}
|
13
|
+
build_model_class
|
14
|
+
end
|
15
|
+
|
16
|
+
def exists?
|
17
|
+
begin
|
18
|
+
client.get
|
19
|
+
return true
|
20
|
+
rescue RestClient::ResourceNotFound
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def stats
|
26
|
+
begin
|
27
|
+
JSON.parse client.get, :symbolize_names => true
|
28
|
+
rescue RestClient::ResourceNotFound
|
29
|
+
raise Divan::DatabaseNotFound.new(self), "Database was not found"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def create
|
34
|
+
begin
|
35
|
+
client.put Hash.new
|
36
|
+
rescue RestClient::PreconditionFailed
|
37
|
+
raise Divan::DatabaseAlreadyCreated.new(self), "Database already created"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete
|
42
|
+
begin
|
43
|
+
client.delete
|
44
|
+
rescue RestClient::ResourceNotFound
|
45
|
+
raise Divan::DatabaseNotFound.new(self), "Database was not found"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def create_views
|
50
|
+
views.each do |name, views|
|
51
|
+
create_view(name)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_view(view_name)
|
56
|
+
if view_doc = model_class.find("_design/#{view_name}")
|
57
|
+
view_doc.views = views[view_name]
|
58
|
+
view_doc.save
|
59
|
+
else
|
60
|
+
model_class.create :id => "_design/#{view_name}", :language => 'javascript', :views => views[view_name]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def [](path, params={})
|
65
|
+
client[ Divan::Utils.formatted_path(path, params) ]
|
66
|
+
end
|
67
|
+
|
68
|
+
def client
|
69
|
+
@client ||= RestClient::Resource.new( basic_url, *([@user, @password].compact) )
|
70
|
+
end
|
71
|
+
|
72
|
+
def model_class
|
73
|
+
@model_class ||= eval model_class_full_name
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
def basic_url
|
78
|
+
"#{host}:#{port}/#{database}/"
|
79
|
+
end
|
80
|
+
|
81
|
+
def model_class_full_name
|
82
|
+
"Divan::Models::#{name.to_s.split('_').map{|str| "#{str[0..0].upcase}#{str[1..-1]}" }}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def build_model_class
|
86
|
+
Divan::Base.class_eval "class #{model_class_full_name} < Divan::Base ; end"
|
87
|
+
model_class.database = self
|
88
|
+
model_class.top_level_model!
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.model_class(name)
|
92
|
+
eval "Divan::Models::#{name.to_s.split('_').map{|str| "#{str[0..0].upcase}#{str[1..-1]}" }}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module Divan
|
2
|
+
module Models
|
3
|
+
class Base
|
4
|
+
|
5
|
+
def save
|
6
|
+
self.class.execute_before_validate_callback(self) or return false
|
7
|
+
|
8
|
+
validate or return false
|
9
|
+
|
10
|
+
self.class.execute_after_validate_callback(self) or return false
|
11
|
+
self.class.execute_before_save_callback(self) or return false
|
12
|
+
|
13
|
+
execute_save
|
14
|
+
|
15
|
+
self.class.execute_after_save_callback(self)
|
16
|
+
@last_request
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete
|
20
|
+
begin
|
21
|
+
@last_request = database.client[current_document_path].delete
|
22
|
+
rescue RestClient::ResourceNotFound
|
23
|
+
@last_request = nil
|
24
|
+
return @last_request
|
25
|
+
end
|
26
|
+
@rev = JSON.parse(@last_request, :symbolize_names => true )[:rev]
|
27
|
+
@last_request
|
28
|
+
end
|
29
|
+
|
30
|
+
def revision_ids
|
31
|
+
begin
|
32
|
+
@last_request = database.client[document_path :revs_info => true].get
|
33
|
+
JSON.parse(@last_request, :symbolize_names => true )[:_revs_info].find_all{ |hash| hash[:status] == 'available' }.map{ |hash| hash[:rev] }
|
34
|
+
rescue RestClient::ResourceNotFound
|
35
|
+
return []
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def revision(index)
|
40
|
+
revision!(index)
|
41
|
+
rescue Divan::Divan::DocumentRevisionNotAvailable
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def revision!(index)
|
46
|
+
r = revision_ids.find{ |rev| rev[0..1].to_i == index}
|
47
|
+
r.nil? and raise Divan::Divan::DocumentRevisionNotAvailable.new(self), "Revision with index #{index} missing"
|
48
|
+
return self if r == @rev
|
49
|
+
self.class.find @id, :rev => r
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def execute_save
|
55
|
+
begin
|
56
|
+
save_attrs = @attributes.clone
|
57
|
+
save_attrs[:"_rev"] = @rev if @rev
|
58
|
+
@last_request = database.client[current_document_path].put save_attrs.to_json
|
59
|
+
@rev = JSON.parse(@last_request, :symbolize_names => true )[:rev]
|
60
|
+
rescue RestClient::Conflict
|
61
|
+
raise Divan::DocumentConflict.new(self), "Update race conflict"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class << self
|
66
|
+
def find(id, params = {})
|
67
|
+
begin
|
68
|
+
last_request = database.client[Divan::Utils.formatted_path id, params].get
|
69
|
+
rescue RestClient::ResourceNotFound
|
70
|
+
return nil
|
71
|
+
end
|
72
|
+
attributes = JSON.parse last_request, :symbolize_names => true
|
73
|
+
obj = self.new attributes
|
74
|
+
obj.last_request = last_request
|
75
|
+
obj
|
76
|
+
end
|
77
|
+
|
78
|
+
def find_all(params=nil)
|
79
|
+
query_view :all, params
|
80
|
+
end
|
81
|
+
|
82
|
+
def all
|
83
|
+
find_all
|
84
|
+
end
|
85
|
+
|
86
|
+
def delete_all(params = nil)
|
87
|
+
to_be_deleted = find_all(params).map do |object|
|
88
|
+
{:_id => object.id, :_rev => object.rev, :_deleted => true }
|
89
|
+
end
|
90
|
+
payload = { :docs => to_be_deleted }.to_json
|
91
|
+
database.client['_bulk_docs'].post payload, :content_type => :json, :accept => :json
|
92
|
+
to_be_deleted.size
|
93
|
+
end
|
94
|
+
|
95
|
+
def create(opts = {})
|
96
|
+
raise ArgumentError if( !opts.is_a?(Hash) && !opts.is_a?(Array) )
|
97
|
+
if opts.is_a? Hash
|
98
|
+
single_create opts
|
99
|
+
else
|
100
|
+
bulk_create opts
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
protected
|
105
|
+
|
106
|
+
def single_create(opts = {})
|
107
|
+
obj = self.new(opts)
|
108
|
+
obj.save
|
109
|
+
obj
|
110
|
+
end
|
111
|
+
|
112
|
+
def bulk_create(opts)
|
113
|
+
payload = { :docs => opts.map do |params|
|
114
|
+
params = params.clone
|
115
|
+
params[:_id] = params.delete(:id) || Divan::Utils.uuid
|
116
|
+
params[type_field.to_sym] = type_name unless top_level_model?
|
117
|
+
params
|
118
|
+
end }
|
119
|
+
last_request = database.client['_bulk_docs'].post( payload.to_json, :content_type => :json, :accept => :json )
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/lib/divan/utils.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Divan
|
2
|
+
module Utils
|
3
|
+
def self.uuid
|
4
|
+
values = [ rand(0x0010000), rand(0x0010000), rand(0x0010000), rand(0x0010000),
|
5
|
+
rand(0x0010000), rand(0x1000000), rand(0x1000000) ]
|
6
|
+
"%04x%04x%04x%04x%04x%06x%06x" % values
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.formatted_path(path = nil, opts = {})
|
10
|
+
if opts.empty?
|
11
|
+
CGI.escape path.to_s
|
12
|
+
else
|
13
|
+
formatted_opts = opts.map{|k,v| "#{CGI.escape k.to_s}=#{URI.encode v.to_s}"}.join('&')
|
14
|
+
"#{path.to_s}?#{formatted_opts}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/test/test_helper.rb
ADDED
data/test/unit/divan.rb
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/../test_helper.rb"
|
2
|
+
|
3
|
+
class InvalidatedModel < Divan::Models::ProofOfConcept
|
4
|
+
before_validate :indiferent_callback
|
5
|
+
after_validate lambda{ |obj| obj != nil }
|
6
|
+
after_validate :invalidate
|
7
|
+
|
8
|
+
def indiferent_callback
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def invalidate
|
13
|
+
false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class ViewedModel < Divan::Models::ProofOfConcept
|
18
|
+
view_by :value
|
19
|
+
view_by :mod
|
20
|
+
end
|
21
|
+
|
22
|
+
class ProofOfConcept < Divan::Models::ProofOfConcept
|
23
|
+
property :first_name
|
24
|
+
end
|
25
|
+
|
26
|
+
class TestDivan < Test::Unit::TestCase
|
27
|
+
def test_dynamic_model
|
28
|
+
m = Divan::Model(:teste)
|
29
|
+
assert m.class, Divan::Models::Teste
|
30
|
+
assert m.database.name, 'test'
|
31
|
+
assert m.database.host, '127.0.0.1'
|
32
|
+
assert m.database.port, 12345
|
33
|
+
assert m.database.user, 'admin'
|
34
|
+
assert m.database.password, 'top1secret2pass'
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_get_database_stats
|
38
|
+
database = Divan[:proof_of_concept]
|
39
|
+
assert_equal database.stats[:db_name], 'proof_of_concept'
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_create_and_delete_database
|
43
|
+
database = Divan::Database.new :created_test_database, 'host' => 'http://127.0.0.1', 'database' => 'test_database'
|
44
|
+
delete_lambda = lambda{
|
45
|
+
assert database.delete['ok']
|
46
|
+
assert !database.exists?
|
47
|
+
}
|
48
|
+
create_lambda = lambda{
|
49
|
+
assert database.create['ok']
|
50
|
+
assert_equal database.stats[:db_name], 'test_database'
|
51
|
+
}
|
52
|
+
delete_lambda.call if database.exists?
|
53
|
+
create_lambda.call
|
54
|
+
delete_lambda.call
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_database_not_found
|
58
|
+
database = Divan::Database.new :missing_database, 'host' => 'http://localhost', 'database' => 'mising_database'
|
59
|
+
assert_raise(Divan::DatabaseNotFound){ database.stats }
|
60
|
+
assert_raise(Divan::DatabaseNotFound){ database.delete }
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_database_already_created
|
64
|
+
database = Divan::Database.new :already_created, 'host' => 'http://localhost', 'database' => 'already_created'
|
65
|
+
assert database.create['ok']
|
66
|
+
assert_raise(Divan::DatabaseAlreadyCreated){ database.create }
|
67
|
+
assert database.delete['ok'] # Only to ensure that database is deleted after this test
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_saving_and_retrieving_simple_document_should_work
|
71
|
+
object = ProofOfConcept.new :simple_param => 'Working well!',
|
72
|
+
:hashed_params => { :is_a => 'Hash', :hash_size => 2 }
|
73
|
+
assert object.save
|
74
|
+
retrieved_object = ProofOfConcept.find object.id
|
75
|
+
assert retrieved_object
|
76
|
+
assert retrieved_object.rev
|
77
|
+
assert_equal object.id, retrieved_object.id
|
78
|
+
assert_equal object.attributes, retrieved_object.attributes
|
79
|
+
assert retrieved_object.delete['ok']
|
80
|
+
assert_not_equal object.rev, retrieved_object.rev
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_retrieving_non_existent_document_should_return_nil
|
84
|
+
obj = ProofOfConcept.find '0'*32 # Probably this uuid don't exists in database
|
85
|
+
assert_nil obj
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_updating_document
|
89
|
+
object = ProofOfConcept.new
|
90
|
+
object[:hashed_params] = {:is_a => 'Hash', :hash_size => 2}
|
91
|
+
object[:simple_param] = 'Working well!'
|
92
|
+
assert object.save
|
93
|
+
retrieved_object = ProofOfConcept.find object.id
|
94
|
+
assert retrieved_object
|
95
|
+
retrieved_object[:updated_attrib] = 'New attribute!'
|
96
|
+
assert retrieved_object.save['ok']
|
97
|
+
object[:lost_race] = 'I\'ll fail!'
|
98
|
+
assert_raise(Divan::DocumentConflict){ object.save }
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_retrieving_deleted_object
|
102
|
+
object = ProofOfConcept.new
|
103
|
+
object[:hashed_params] = {:is_a => 'Hash', :hash_size => 2}
|
104
|
+
object[:simple_param] = 'Working well!'
|
105
|
+
assert object.save
|
106
|
+
assert object.delete
|
107
|
+
retrieved_object = ProofOfConcept.find object.id
|
108
|
+
assert_nil retrieved_object
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_deleting_document_twice
|
112
|
+
object = ProofOfConcept.new
|
113
|
+
object[:hashed_params] = {:is_a => 'Hash', :hash_size => 2}
|
114
|
+
object[:simple_param] = 'Working well!'
|
115
|
+
assert object.save
|
116
|
+
assert object.delete
|
117
|
+
assert_nil object.delete
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_save_deleted_document
|
121
|
+
object = ProofOfConcept.new
|
122
|
+
object[:hashed_params] = {:is_a => 'Hash', :hash_size => 2}
|
123
|
+
object[:simple_param] = 'Working well!'
|
124
|
+
assert object.save
|
125
|
+
assert object.delete
|
126
|
+
assert object.save
|
127
|
+
assert object.delete
|
128
|
+
end
|
129
|
+
|
130
|
+
def test_delete_all_from_database
|
131
|
+
assert ProofOfConcept.delete_all
|
132
|
+
10.times do |n|
|
133
|
+
assert ProofOfConcept.new( :value => n ).save
|
134
|
+
end
|
135
|
+
# assert_equal Divan[:proof_of_concept].views.count, 5
|
136
|
+
assert Divan[:proof_of_concept].create_views
|
137
|
+
assert_equal ProofOfConcept.delete_all(:limit => 6), 6
|
138
|
+
assert_equal ProofOfConcept.all.first.class, ProofOfConcept
|
139
|
+
assert_equal ProofOfConcept.delete_all, 4
|
140
|
+
assert ProofOfConcept.find('_design/proof_of_concept')
|
141
|
+
assert_equal ProofOfConcept.all.count, 0
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_bulk_create
|
145
|
+
assert ProofOfConcept.delete_all
|
146
|
+
params = 10.times.map do |n|
|
147
|
+
{:number => n, :double => 2*n}
|
148
|
+
end
|
149
|
+
assert ProofOfConcept.create params
|
150
|
+
assert_equal ProofOfConcept.delete_all, 10
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_perform_view_by_query
|
154
|
+
assert ViewedModel.delete_all
|
155
|
+
assert Divan[:proof_of_concept].create_views
|
156
|
+
params = 10.times.map do |n|
|
157
|
+
{:mod => (n%2), :value => "#{n} mod 2"}
|
158
|
+
end
|
159
|
+
assert ViewedModel.create params
|
160
|
+
obj = ViewedModel.by_value '5 mod 2'
|
161
|
+
assert obj
|
162
|
+
assert_equal obj.mod, 1
|
163
|
+
assert_equal ViewedModel.all_by_mod(0).count, 5
|
164
|
+
assert_equal ViewedModel.find_all.count, 10
|
165
|
+
assert_equal ViewedModel.delete_all, 10
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_before_validate_callback_avoids_save
|
169
|
+
object = InvalidatedModel.new
|
170
|
+
assert !object.save
|
171
|
+
assert_nil object.rev
|
172
|
+
end
|
173
|
+
|
174
|
+
def test_dynamic_access_to_attributes
|
175
|
+
object = ProofOfConcept.new :dynamic_attribute => 'Working'
|
176
|
+
assert object.dynamic_attribute, 'Working'
|
177
|
+
assert_equal( (object.dynamic_setter = "Well"), 'Well')
|
178
|
+
assert_equal object.dynamic_setter, 'Well'
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_setting_properties
|
182
|
+
object = ProofOfConcept.new
|
183
|
+
assert_nil object.first_name
|
184
|
+
assert_raise(NoMethodError){ object.last_name }
|
185
|
+
end
|
186
|
+
|
187
|
+
def test_top_level_model
|
188
|
+
Divan::Models::ProofOfConcept.delete_all
|
189
|
+
ProofOfConcept.create :test => 123
|
190
|
+
Divan::Models::ProofOfConcept.create :test => 456
|
191
|
+
ViewedModel.create :test => 789
|
192
|
+
assert_equal Divan::Models::ProofOfConcept.find_all.count, 3
|
193
|
+
assert_equal ProofOfConcept.find_all.count, 3
|
194
|
+
assert_equal ViewedModel.find_all.count, 1
|
195
|
+
assert_equal ViewedModel.delete_all, 1
|
196
|
+
assert_equal ProofOfConcept.delete_all, 2
|
197
|
+
end
|
198
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: divan
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Dalton Pinto
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-18 00:00:00 -02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: |
|
23
|
+
This is a very simple CouchDB client that have few dependencies.
|
24
|
+
This client has a lot of interesting features, for example: easy access to CouchDB revisions.
|
25
|
+
|
26
|
+
email: dalthon@aluno.ita.br
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README
|
33
|
+
files:
|
34
|
+
- README
|
35
|
+
- Rakefile.rb
|
36
|
+
- VERSION
|
37
|
+
- init.rb
|
38
|
+
- lib/divan.rb
|
39
|
+
- lib/divan/base.rb
|
40
|
+
- lib/divan/database.rb
|
41
|
+
- lib/divan/models/base.rb
|
42
|
+
- lib/divan/utils.rb
|
43
|
+
- test/test_helper.rb
|
44
|
+
- test/unit/divan.rb
|
45
|
+
has_rdoc: true
|
46
|
+
homepage: http://github.com/efqdalton/divan
|
47
|
+
licenses: []
|
48
|
+
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options:
|
51
|
+
- --charset=UTF-8
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
hash: 3
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.3.7
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: A Ruby CouchDB Client for insane people
|
79
|
+
test_files:
|
80
|
+
- test/test_helper.rb
|
81
|
+
- test/unit/divan.rb
|