divan 0.1.1
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/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
|