dolly 1.1.7 → 3.0.0
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.
- checksums.yaml +4 -4
- data/README.md +35 -0
- data/lib/dolly.rb +1 -23
- data/lib/dolly/attachment.rb +29 -0
- data/lib/dolly/bulk_document.rb +27 -26
- data/lib/dolly/class_methods_delegation.rb +15 -0
- data/lib/dolly/collection.rb +26 -69
- data/lib/dolly/configuration.rb +35 -10
- data/lib/dolly/connection.rb +91 -22
- data/lib/dolly/depracated_database.rb +24 -0
- data/lib/dolly/document.rb +32 -206
- data/lib/dolly/document_creation.rb +20 -0
- data/lib/dolly/document_state.rb +65 -0
- data/lib/dolly/document_type.rb +28 -0
- data/lib/dolly/exceptions.rb +21 -0
- data/lib/dolly/identity_properties.rb +29 -0
- data/lib/dolly/properties.rb +31 -0
- data/lib/dolly/property.rb +58 -47
- data/lib/dolly/property_manager.rb +47 -0
- data/lib/dolly/property_set.rb +18 -0
- data/lib/dolly/query.rb +39 -67
- data/lib/dolly/query_arguments.rb +35 -0
- data/lib/dolly/request.rb +12 -107
- data/lib/dolly/request_header.rb +26 -0
- data/lib/dolly/timestamp.rb +24 -0
- data/lib/dolly/version.rb +1 -1
- data/lib/{dolly → railties}/railtie.rb +2 -1
- data/lib/refinements/string_refinements.rb +28 -0
- data/lib/tasks/db.rake +4 -3
- data/test/bulk_document_test.rb +8 -5
- data/test/document_test.rb +137 -53
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/log/test.log +46417 -46858
- data/test/test_helper.rb +14 -20
- metadata +42 -145
- data/Rakefile +0 -11
- data/lib/dolly/bulk_error.rb +0 -16
- data/lib/dolly/db_config.rb +0 -20
- data/lib/dolly/interpreter.rb +0 -5
- data/lib/dolly/logger.rb +0 -9
- data/lib/dolly/name_space.rb +0 -28
- data/lib/dolly/timestamps.rb +0 -21
- data/lib/exceptions/dolly.rb +0 -47
- data/test/collection_test.rb +0 -59
- data/test/configuration_test.rb +0 -9
- data/test/dummy/README.rdoc +0 -28
- data/test/dummy/Rakefile +0 -6
- data/test/dummy/app/assets/javascripts/application.js +0 -13
- data/test/dummy/app/assets/stylesheets/application.css +0 -13
- data/test/dummy/app/controllers/application_controller.rb +0 -5
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/bin/bundle +0 -3
- data/test/dummy/bin/rails +0 -4
- data/test/dummy/bin/rake +0 -4
- data/test/dummy/config.ru +0 -4
- data/test/dummy/config/application.rb +0 -27
- data/test/dummy/config/boot.rb +0 -5
- data/test/dummy/config/couchdb.yml +0 -13
- data/test/dummy/config/environment.rb +0 -5
- data/test/dummy/config/environments/development.rb +0 -29
- data/test/dummy/config/environments/production.rb +0 -80
- data/test/dummy/config/environments/test.rb +0 -36
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/test/dummy/config/initializers/inflections.rb +0 -16
- data/test/dummy/config/initializers/mime_types.rb +0 -5
- data/test/dummy/config/initializers/secret_token.rb +0 -12
- data/test/dummy/config/initializers/session_store.rb +0 -3
- data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/test/dummy/config/locales/en.yml +0 -23
- data/test/dummy/config/routes.rb +0 -56
- data/test/dummy/lib/couch_rest_adapter/railtie.rb +0 -10
- data/test/dummy/public/404.html +0 -58
- data/test/dummy/public/422.html +0 -58
- data/test/dummy/public/500.html +0 -57
- data/test/dummy/public/favicon.ico +0 -0
- data/test/factories/factories.rb +0 -8
- data/test/request_test.rb +0 -25
@@ -0,0 +1,24 @@
|
|
1
|
+
module Dolly
|
2
|
+
module DepracatedDatabase
|
3
|
+
Database = Struct.new(:connection) do
|
4
|
+
def request *args
|
5
|
+
connection.request *args
|
6
|
+
end
|
7
|
+
|
8
|
+
def post *args
|
9
|
+
connection.post *args
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def view *args
|
14
|
+
opts = args.pop if args.last.is_a? Hash
|
15
|
+
opts ||= {}
|
16
|
+
connection.view *args, opts.merge(include_docs: true)
|
17
|
+
end
|
18
|
+
|
19
|
+
def database
|
20
|
+
warn "[DEPRECATION] `database` is deprecated. Please use `connection` instead."
|
21
|
+
Database.new(connection)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/dolly/document.rb
CHANGED
@@ -1,217 +1,43 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require 'dolly/
|
1
|
+
require 'dolly/query'
|
2
|
+
require 'dolly/connection'
|
3
|
+
require 'dolly/request'
|
4
|
+
require 'dolly/depracated_database'
|
5
|
+
require 'dolly/document_state'
|
6
|
+
require 'dolly/properties'
|
7
|
+
require 'dolly/identity_properties'
|
8
|
+
require 'dolly/attachment'
|
9
|
+
require 'dolly/property_manager'
|
10
|
+
require 'dolly/timestamp'
|
11
|
+
require 'dolly/query_arguments'
|
12
|
+
require 'dolly/document_creation'
|
13
|
+
require 'dolly/class_methods_delegation'
|
14
|
+
require 'refinements/string_refinements'
|
4
15
|
|
5
16
|
module Dolly
|
6
17
|
class Document
|
7
|
-
extend
|
8
|
-
|
9
|
-
extend
|
18
|
+
extend Query
|
19
|
+
extend Request
|
20
|
+
extend DepracatedDatabase
|
21
|
+
extend Properties
|
22
|
+
extend DocumentCreation
|
23
|
+
include PropertyManager
|
24
|
+
include Timestamp
|
25
|
+
include DocumentState
|
26
|
+
include IdentityProperties
|
27
|
+
include Attachment
|
28
|
+
include QueryArguments
|
29
|
+
include ClassMethodsDelegation
|
10
30
|
|
11
|
-
|
12
|
-
class_attribute :properties
|
13
|
-
cattr_accessor :timestamps do
|
14
|
-
{}
|
15
|
-
end
|
16
|
-
|
17
|
-
def initialize options = {}
|
18
|
-
@doc ||= {}
|
19
|
-
options = options.with_indifferent_access
|
20
|
-
init_properties options
|
21
|
-
end
|
22
|
-
|
23
|
-
def persisted?
|
24
|
-
!doc['_rev'].blank?
|
25
|
-
end
|
26
|
-
|
27
|
-
def update_properties options = {}
|
28
|
-
raise InvalidProperty unless valid_properties?(options)
|
29
|
-
options.each do |property, value|
|
30
|
-
send(:"#{property}=", value)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def update_properties! options = {}
|
35
|
-
update_properties(options)
|
36
|
-
save
|
37
|
-
end
|
38
|
-
|
39
|
-
def reload
|
40
|
-
self.doc = self.class.find(id).doc
|
41
|
-
end
|
42
|
-
|
43
|
-
def id
|
44
|
-
doc['_id'] ||= self.class.next_id
|
45
|
-
end
|
46
|
-
|
47
|
-
def id= base_value
|
48
|
-
doc ||= {}
|
49
|
-
doc['_id'] = self.class.namespace(base_value)
|
50
|
-
end
|
51
|
-
|
52
|
-
def rev
|
53
|
-
doc['_rev']
|
54
|
-
end
|
55
|
-
|
56
|
-
def rev= value
|
57
|
-
doc['_rev'] = value
|
58
|
-
end
|
59
|
-
|
60
|
-
def save options = {}
|
61
|
-
return false unless options[:validate] == false || valid?
|
62
|
-
self.doc['_id'] = self.id if self.id.present?
|
63
|
-
self.doc['_id'] = self.class.next_id if self.doc['_id'].blank?
|
64
|
-
set_created_at if timestamps[self.class.name]
|
65
|
-
set_updated_at if timestamps[self.class.name]
|
66
|
-
response = database.put(id_as_resource, self.doc.to_json)
|
67
|
-
obj = JSON::parse response.parsed_response
|
68
|
-
doc['_rev'] = obj['rev'] if obj['rev']
|
69
|
-
obj['ok']
|
70
|
-
end
|
71
|
-
|
72
|
-
def save!
|
73
|
-
raise DocumentInvalidError unless valid?
|
74
|
-
save
|
75
|
-
end
|
76
|
-
|
77
|
-
def destroy hard = true
|
78
|
-
if hard
|
79
|
-
q = id_as_resource + "?rev=#{rev}"
|
80
|
-
response = database.delete(q)
|
81
|
-
JSON::parse response.parsed_response
|
82
|
-
else
|
83
|
-
self.doc['_deleted'] = true
|
84
|
-
self.save
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def rows= col
|
89
|
-
raise Dolly::ResourceNotFound if col.empty?
|
90
|
-
col.each{ |r| @doc = r['doc'] }
|
91
|
-
_properties.each do |p|
|
92
|
-
#TODO: Refactor properties so it is not required
|
93
|
-
#to be a class property. But something that doesn't
|
94
|
-
#persist all the inheretence chain
|
95
|
-
next unless self.respond_to? :"#{p.name}="
|
96
|
-
self.send "#{p.name}=", doc[p.name]
|
97
|
-
end
|
98
|
-
@rows = col
|
99
|
-
end
|
100
|
-
|
101
|
-
def from_json string
|
102
|
-
parsed = JSON::parse( string )
|
103
|
-
self.rows = parsed['rows']
|
104
|
-
self
|
105
|
-
end
|
106
|
-
|
107
|
-
def database
|
108
|
-
self.class.database
|
109
|
-
end
|
110
|
-
|
111
|
-
def id_as_resource
|
112
|
-
CGI::escape id
|
113
|
-
end
|
114
|
-
|
115
|
-
def attach_file! file_name, mime_type, body, opts={}
|
116
|
-
attach_file file_name, mime_type, body, opts
|
117
|
-
save
|
118
|
-
end
|
119
|
-
|
120
|
-
def attach_file file_name, mime_type, body, opts={}
|
121
|
-
if opts[:inline]
|
122
|
-
attach_inline_file file_name, mime_type, body
|
123
|
-
else
|
124
|
-
attach_standalone_file file_name, mime_type, body
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
def attach_inline_file file_name, mime_type, body
|
129
|
-
attachment_data = { file_name.to_s => { 'content_type' => mime_type,
|
130
|
-
'data' => Base64.encode64(body)} }
|
131
|
-
doc['_attachments'] ||= {}
|
132
|
-
doc['_attachments'].merge! attachment_data
|
133
|
-
end
|
134
|
-
|
135
|
-
def attach_standalone_file file_name, mime_type, body
|
136
|
-
database.attach id_as_resource, CGI.escape(file_name), body, { 'Content-Type' => mime_type }
|
137
|
-
end
|
138
|
-
|
139
|
-
def self.create options = {}
|
140
|
-
obj = new options
|
141
|
-
obj.save
|
142
|
-
obj
|
143
|
-
end
|
144
|
-
|
145
|
-
def self.property *ary
|
146
|
-
self.properties ||= {}
|
147
|
-
options = ary.pop if ary.last.kind_of? Hash
|
148
|
-
options ||= {}
|
31
|
+
attr_writer :doc
|
149
32
|
|
150
|
-
|
151
|
-
|
152
|
-
self.write_methods name
|
153
|
-
end
|
33
|
+
def initialize attributes = {}
|
34
|
+
properties.each(&build_property(attributes))
|
154
35
|
end
|
155
36
|
|
156
|
-
|
157
|
-
#TODO: create a PropertiesSet service object, to do all this
|
158
|
-
def self.write_methods name
|
159
|
-
property = properties[name]
|
160
|
-
define_method(name) { read_property name }
|
161
|
-
define_method("#{name}=") { |value| write_property name, value }
|
162
|
-
define_method(:"#{name}?") { send name } if property.boolean?
|
163
|
-
define_method("[]") {|n| send n.to_sym }
|
164
|
-
end
|
37
|
+
protected
|
165
38
|
|
166
|
-
def
|
167
|
-
|
168
|
-
@doc[name.to_s] = value
|
169
|
-
end
|
170
|
-
|
171
|
-
def read_property name
|
172
|
-
if instance_variable_get(:"@#{name}").nil?
|
173
|
-
write_property name, (doc[name.to_s] || self.properties[name].value)
|
174
|
-
end
|
175
|
-
instance_variable_get(:"@#{name}")
|
176
|
-
end
|
177
|
-
|
178
|
-
def _properties
|
179
|
-
self.properties.values
|
180
|
-
end
|
181
|
-
|
182
|
-
def init_properties options = {}
|
183
|
-
raise Dolly::ResourceNotFound if options['error'] == 'not_found'
|
184
|
-
options.each do |k, v|
|
185
|
-
next unless respond_to? :"#{k}="
|
186
|
-
send(:"#{k}=", v)
|
187
|
-
end
|
188
|
-
initialize_default_properties options if self.properties.present?
|
189
|
-
init_doc options
|
190
|
-
end
|
191
|
-
|
192
|
-
def initialize_default_properties options
|
193
|
-
_properties.reject { |property| options.keys.include? property.name }.each do |property|
|
194
|
-
property_value = property.default.clone unless Dolly::Property::CANT_CLONE.any? { |klass| property.default.is_a? klass }
|
195
|
-
property_value ||= property.default
|
196
|
-
self.doc[property.name] ||= property_value
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
def init_doc options
|
201
|
-
self.doc ||= {}
|
202
|
-
#TODO: define what will be the preference _id or id
|
203
|
-
normalized_id = options[:_id] || options[:id]
|
204
|
-
self.doc['_id'] = self.class.namespace( normalized_id ) if normalized_id
|
205
|
-
end
|
206
|
-
|
207
|
-
def valid_properties?(options)
|
208
|
-
options.keys.any?{ |option| properties_include?(option.to_s) }
|
209
|
-
end
|
210
|
-
|
211
|
-
def properties_include? property
|
212
|
-
_properties.map(&:name).include? property
|
39
|
+
def doc
|
40
|
+
@doc ||= {}
|
213
41
|
end
|
214
|
-
|
215
|
-
def valid?; true; end
|
216
42
|
end
|
217
43
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'dolly/properties'
|
2
|
+
|
3
|
+
module Dolly
|
4
|
+
module DocumentCreation
|
5
|
+
include Properties
|
6
|
+
|
7
|
+
def from_doc(doc)
|
8
|
+
attributes = property_clean_doc(doc)
|
9
|
+
new(attributes).tap { |model| model.doc = doc }
|
10
|
+
end
|
11
|
+
|
12
|
+
def from_json(json)
|
13
|
+
from_doc(Oj.load(json, symbol_keys: true))
|
14
|
+
end
|
15
|
+
|
16
|
+
def create(attributes)
|
17
|
+
new(attributes).tap { |model| model.save }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'dolly/class_methods_delegation'
|
2
|
+
|
3
|
+
module Dolly
|
4
|
+
module DocumentState
|
5
|
+
include ClassMethodsDelegation
|
6
|
+
|
7
|
+
def save(options = {})
|
8
|
+
return false unless options[:validate] == false || valid?
|
9
|
+
write_timestamps(persisted?)
|
10
|
+
after_save(connection.put(id, doc))
|
11
|
+
end
|
12
|
+
|
13
|
+
def save!
|
14
|
+
raise DocumentInvalidError unless valid?
|
15
|
+
save
|
16
|
+
end
|
17
|
+
|
18
|
+
def update_properties(properties)
|
19
|
+
properties.each(&update_attribute)
|
20
|
+
end
|
21
|
+
|
22
|
+
def update_properties!(properties)
|
23
|
+
update_properties(properties)
|
24
|
+
save!
|
25
|
+
end
|
26
|
+
|
27
|
+
def destroy is_hard = true
|
28
|
+
return connection.delete(id, rev) if is_hard
|
29
|
+
doc[:_deleted] = true
|
30
|
+
save
|
31
|
+
rescue Dolly::ResourceNotFound
|
32
|
+
nil
|
33
|
+
rescue Dolly::ServerError => error
|
34
|
+
raise error unless error.message =~ /conflict/
|
35
|
+
self.rev = self.class.safe_find(id)&.rev
|
36
|
+
return unless self.rev
|
37
|
+
destroy(is_hard)
|
38
|
+
end
|
39
|
+
|
40
|
+
def reload
|
41
|
+
reloaded_doc = self.class.find(id).send(:doc)
|
42
|
+
attributes = property_clean_doc(reloaded_doc)
|
43
|
+
|
44
|
+
attributes.each(&update_attribute)
|
45
|
+
end
|
46
|
+
|
47
|
+
def persisted?
|
48
|
+
return false unless doc[:_rev]
|
49
|
+
!doc[:_rev].empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_h
|
53
|
+
doc
|
54
|
+
end
|
55
|
+
|
56
|
+
def valid?
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
def after_save(response)
|
61
|
+
self.rev = response[:rev]
|
62
|
+
response[:ok]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'refinements/string_refinements'
|
2
|
+
|
3
|
+
module Dolly
|
4
|
+
module DocumentType
|
5
|
+
using StringRefinements
|
6
|
+
|
7
|
+
def namespace_keys(keys)
|
8
|
+
keys.map { |key| namespace_key key }
|
9
|
+
end
|
10
|
+
|
11
|
+
def namespace_key(key)
|
12
|
+
return key if key =~ %r{^#{name_paramitized}/}
|
13
|
+
"#{name_paramitized}/#{key}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def base_id(id)
|
17
|
+
id.sub(%r{^#{name_paramitized}/}, '')
|
18
|
+
end
|
19
|
+
|
20
|
+
def name_paramitized
|
21
|
+
class_name.split("::").last.underscore
|
22
|
+
end
|
23
|
+
|
24
|
+
def class_name
|
25
|
+
is_a?(Class) ? name : self.class.name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Dolly
|
2
|
+
class ResourceNotFound < RuntimeError
|
3
|
+
def to_s
|
4
|
+
'The document was not found.'
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class ServerError < RuntimeError
|
9
|
+
def initialize msg
|
10
|
+
@msg = msg
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"There has been an error on the couchdb server: #{@msg.inspect}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class InvalidConfigFileError < RuntimeError; end
|
19
|
+
class InvalidProperty < RuntimeError; end
|
20
|
+
class DocumentInvalidError < RuntimeError; end
|
21
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'dolly/document_type'
|
2
|
+
require 'dolly/class_methods_delegation'
|
3
|
+
|
4
|
+
module Dolly
|
5
|
+
module IdentityProperties
|
6
|
+
include DocumentType
|
7
|
+
include ClassMethodsDelegation
|
8
|
+
|
9
|
+
def id
|
10
|
+
doc[:_id] ||= namespace_key(connection.uuids.last)
|
11
|
+
end
|
12
|
+
|
13
|
+
def id= value
|
14
|
+
doc[:_id] = namespace_key(value)
|
15
|
+
end
|
16
|
+
|
17
|
+
def rev
|
18
|
+
doc[:_rev]
|
19
|
+
end
|
20
|
+
|
21
|
+
def rev= value
|
22
|
+
doc[:_rev] = value
|
23
|
+
end
|
24
|
+
|
25
|
+
def id_as_resource
|
26
|
+
CGI.escape(id)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'dolly/property_set'
|
2
|
+
require 'dolly/property'
|
3
|
+
|
4
|
+
module Dolly
|
5
|
+
module Properties
|
6
|
+
SPECIAL_KEYS = %i[_id _rev]
|
7
|
+
|
8
|
+
def property *opts, class_name: nil, default: nil
|
9
|
+
opts.each do |opt|
|
10
|
+
properties << (prop = Property.new(opt, class_name, default))
|
11
|
+
send(:attr_reader, opt)
|
12
|
+
|
13
|
+
define_method(:"#{opt}=") { |value| write_attribute(opt, value) }
|
14
|
+
define_method(:"#{opt}?") { send(opt) } if prop.boolean?
|
15
|
+
define_method(:"[]") {|name| send(name) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def properties
|
20
|
+
@properties ||= PropertySet.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def property_keys
|
24
|
+
properties.map(&:key) - SPECIAL_KEYS
|
25
|
+
end
|
26
|
+
|
27
|
+
def property_clean_doc(doc)
|
28
|
+
doc.reject { |key, _value| !property_keys.include?(key) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|