dolly 1.1.4 → 3.0.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.
- checksums.yaml +5 -5
- data/README.md +78 -0
- data/lib/dolly.rb +4 -2
- 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 +32 -65
- data/lib/dolly/configuration.rb +43 -0
- data/lib/dolly/connection.rb +101 -22
- data/lib/dolly/depracated_database.rb +24 -0
- data/lib/dolly/document.rb +48 -198
- data/lib/dolly/document_creation.rb +20 -0
- data/lib/dolly/document_state.rb +66 -0
- data/lib/dolly/document_type.rb +47 -0
- data/lib/dolly/exceptions.rb +32 -0
- data/lib/dolly/identity_properties.rb +29 -0
- data/lib/dolly/mango.rb +124 -0
- data/lib/dolly/mango_index.rb +65 -0
- data/lib/dolly/properties.rb +36 -0
- data/lib/dolly/property.rb +58 -47
- data/lib/dolly/property_manager.rb +47 -0
- data/lib/dolly/property_set.rb +23 -0
- data/lib/dolly/query.rb +37 -67
- data/lib/dolly/query_arguments.rb +35 -0
- data/lib/dolly/request.rb +12 -94
- 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/view_query.rb +14 -0
- data/lib/{dolly → railties}/railtie.rb +2 -1
- data/lib/refinements/hash_refinements.rb +27 -0
- data/lib/refinements/string_refinements.rb +28 -0
- data/lib/tasks/db.rake +20 -4
- data/test/bulk_document_test.rb +8 -5
- data/test/document_test.rb +132 -95
- data/test/document_type_test.rb +28 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/log/test.log +111132 -0
- data/test/inheritance_test.rb +23 -0
- data/test/mango_index_test.rb +64 -0
- data/test/mango_test.rb +273 -0
- data/test/test_helper.rb +63 -18
- data/test/view_query_test.rb +27 -0
- metadata +57 -141
- 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/name_space.rb +0 -28
- data/lib/dolly/timestamps.rb +0 -21
- data/lib/exceptions/dolly.rb +0 -38
- data/test/collection_test.rb +0 -59
- 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
@@ -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,213 +1,63 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require 'dolly/
|
1
|
+
require 'dolly/mango'
|
2
|
+
require 'dolly/mango_index'
|
3
|
+
require 'dolly/query'
|
4
|
+
require 'dolly/view_query'
|
5
|
+
require 'dolly/connection'
|
6
|
+
require 'dolly/request'
|
7
|
+
require 'dolly/depracated_database'
|
8
|
+
require 'dolly/document_state'
|
9
|
+
require 'dolly/properties'
|
10
|
+
require 'dolly/document_type'
|
11
|
+
require 'dolly/identity_properties'
|
12
|
+
require 'dolly/attachment'
|
13
|
+
require 'dolly/property_manager'
|
14
|
+
require 'dolly/timestamp'
|
15
|
+
require 'dolly/query_arguments'
|
16
|
+
require 'dolly/document_creation'
|
17
|
+
require 'dolly/class_methods_delegation'
|
18
|
+
require 'refinements/string_refinements'
|
4
19
|
|
5
20
|
module Dolly
|
6
21
|
class Document
|
7
|
-
extend
|
8
|
-
|
9
|
-
extend
|
22
|
+
extend Mango
|
23
|
+
extend Query
|
24
|
+
extend ViewQuery
|
25
|
+
extend Request
|
26
|
+
extend DepracatedDatabase
|
27
|
+
extend Properties
|
28
|
+
extend DocumentCreation
|
10
29
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
30
|
+
include DocumentType
|
31
|
+
include PropertyManager
|
32
|
+
include Timestamp
|
33
|
+
include DocumentState
|
34
|
+
include IdentityProperties
|
35
|
+
include Attachment
|
36
|
+
include QueryArguments
|
37
|
+
include ClassMethodsDelegation
|
16
38
|
|
17
|
-
|
18
|
-
@doc ||= {}
|
19
|
-
options = options.with_indifferent_access
|
20
|
-
init_properties options
|
21
|
-
end
|
39
|
+
attr_writer :doc
|
22
40
|
|
23
|
-
def
|
24
|
-
|
41
|
+
def initialize(attributes = {})
|
42
|
+
init_ancestor_properties
|
43
|
+
properties.each(&build_property(attributes))
|
25
44
|
end
|
26
45
|
|
27
|
-
|
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
|
46
|
+
protected
|
59
47
|
|
60
|
-
def
|
61
|
-
|
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 soft = false
|
78
|
-
#TODO: Add soft delete support
|
79
|
-
q = id_as_resource + "?rev=#{rev}"
|
80
|
-
response = database.delete(q)
|
81
|
-
JSON::parse response.parsed_response
|
82
|
-
end
|
83
|
-
|
84
|
-
def rows= col
|
85
|
-
raise Dolly::ResourceNotFound if col.empty?
|
86
|
-
col.each{ |r| @doc = r['doc'] }
|
87
|
-
_properties.each do |p|
|
88
|
-
#TODO: Refactor properties so it is not required
|
89
|
-
#to be a class property. But something that doesn't
|
90
|
-
#persist all the inheretence chain
|
91
|
-
next unless self.respond_to? :"#{p.name}="
|
92
|
-
self.send "#{p.name}=", doc[p.name]
|
93
|
-
end
|
94
|
-
@rows = col
|
95
|
-
end
|
96
|
-
|
97
|
-
def from_json string
|
98
|
-
parsed = JSON::parse( string )
|
99
|
-
self.rows = parsed['rows']
|
100
|
-
self
|
101
|
-
end
|
102
|
-
|
103
|
-
def database
|
104
|
-
self.class.database
|
105
|
-
end
|
106
|
-
|
107
|
-
def id_as_resource
|
108
|
-
CGI::escape id
|
109
|
-
end
|
110
|
-
|
111
|
-
def attach_file! file_name, mime_type, body, opts={}
|
112
|
-
attach_file file_name, mime_type, body, opts
|
113
|
-
save
|
114
|
-
end
|
115
|
-
|
116
|
-
def attach_file file_name, mime_type, body, opts={}
|
117
|
-
if opts[:inline]
|
118
|
-
attach_inline_file file_name, mime_type, body
|
119
|
-
else
|
120
|
-
attach_standalone_file file_name, mime_type, body
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
def attach_inline_file file_name, mime_type, body
|
125
|
-
attachment_data = { file_name.to_s => { 'content_type' => mime_type,
|
126
|
-
'data' => Base64.encode64(body)} }
|
127
|
-
doc['_attachments'] ||= {}
|
128
|
-
doc['_attachments'].merge! attachment_data
|
129
|
-
end
|
130
|
-
|
131
|
-
def attach_standalone_file file_name, mime_type, body
|
132
|
-
database.attach id_as_resource, CGI.escape(file_name), body, { 'Content-Type' => mime_type }
|
133
|
-
end
|
134
|
-
|
135
|
-
def self.create options = {}
|
136
|
-
obj = new options
|
137
|
-
obj.save
|
138
|
-
obj
|
139
|
-
end
|
140
|
-
|
141
|
-
def self.property *ary
|
142
|
-
self.properties ||= {}
|
143
|
-
options = ary.pop if ary.last.kind_of? Hash
|
144
|
-
options ||= {}
|
145
|
-
|
146
|
-
ary.each do |name|
|
147
|
-
self.properties[name] = Property.new options.merge(name: name)
|
148
|
-
self.write_methods name
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
private
|
153
|
-
#TODO: create a PropertiesSet service object, to do all this
|
154
|
-
def self.write_methods name
|
155
|
-
property = properties[name]
|
156
|
-
define_method(name) { read_property name }
|
157
|
-
define_method("#{name}=") { |value| write_property name, value }
|
158
|
-
define_method(:"#{name}?") { send name } if property.boolean?
|
159
|
-
define_method("[]") {|n| send n.to_sym }
|
160
|
-
end
|
161
|
-
|
162
|
-
def write_property name, value
|
163
|
-
instance_variable_set(:"@#{name}", value)
|
164
|
-
@doc[name.to_s] = value
|
165
|
-
end
|
166
|
-
|
167
|
-
def read_property name
|
168
|
-
if instance_variable_get(:"@#{name}").nil?
|
169
|
-
write_property name, (doc[name.to_s] || self.properties[name].value)
|
170
|
-
end
|
171
|
-
instance_variable_get(:"@#{name}")
|
172
|
-
end
|
173
|
-
|
174
|
-
def _properties
|
175
|
-
self.properties.values
|
176
|
-
end
|
177
|
-
|
178
|
-
def init_properties options = {}
|
179
|
-
raise Dolly::ResourceNotFound if options['error'] == 'not_found'
|
180
|
-
options.each do |k, v|
|
181
|
-
next unless respond_to? :"#{k}="
|
182
|
-
send(:"#{k}=", v)
|
183
|
-
end
|
184
|
-
initialize_default_properties options if self.properties.present?
|
185
|
-
init_doc options
|
48
|
+
def doc
|
49
|
+
@doc ||= {}
|
186
50
|
end
|
187
51
|
|
188
|
-
def
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
52
|
+
def init_ancestor_properties
|
53
|
+
self.class.ancestors.map do |ancestor|
|
54
|
+
begin
|
55
|
+
ancestor.properties.entries.each do |property|
|
56
|
+
properties << property
|
57
|
+
end
|
58
|
+
rescue NoMethodError => e
|
59
|
+
end
|
193
60
|
end
|
194
61
|
end
|
195
|
-
|
196
|
-
def init_doc options
|
197
|
-
self.doc ||= {}
|
198
|
-
#TODO: define what will be the preference _id or id
|
199
|
-
normalized_id = options[:_id] || options[:id]
|
200
|
-
self.doc['_id'] = self.class.namespace( normalized_id ) if normalized_id
|
201
|
-
end
|
202
|
-
|
203
|
-
def valid_properties?(options)
|
204
|
-
options.keys.any?{ |option| properties_include?(option.to_s) }
|
205
|
-
end
|
206
|
-
|
207
|
-
def properties_include? property
|
208
|
-
_properties.map(&:name).include? property
|
209
|
-
end
|
210
|
-
|
211
|
-
def valid?; true; end
|
212
62
|
end
|
213
63
|
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,66 @@
|
|
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
|
+
set_type if typed? && type.nil?
|
10
|
+
write_timestamps(persisted?)
|
11
|
+
after_save(connection.put(id, doc))
|
12
|
+
end
|
13
|
+
|
14
|
+
def save!
|
15
|
+
raise DocumentInvalidError unless valid?
|
16
|
+
save
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_properties(properties)
|
20
|
+
properties.each(&update_attribute)
|
21
|
+
end
|
22
|
+
|
23
|
+
def update_properties!(properties)
|
24
|
+
update_properties(properties)
|
25
|
+
save!
|
26
|
+
end
|
27
|
+
|
28
|
+
def destroy is_hard = true
|
29
|
+
return connection.delete(id, rev) if is_hard
|
30
|
+
doc[:_deleted] = true
|
31
|
+
save
|
32
|
+
rescue Dolly::ResourceNotFound
|
33
|
+
nil
|
34
|
+
rescue Dolly::ServerError => error
|
35
|
+
raise error unless error.message =~ /conflict/
|
36
|
+
self.rev = self.class.safe_find(id)&.rev
|
37
|
+
return unless self.rev
|
38
|
+
destroy(is_hard)
|
39
|
+
end
|
40
|
+
|
41
|
+
def reload
|
42
|
+
reloaded_doc = self.class.find(id).send(:doc)
|
43
|
+
attributes = property_clean_doc(reloaded_doc)
|
44
|
+
|
45
|
+
attributes.each(&update_attribute)
|
46
|
+
end
|
47
|
+
|
48
|
+
def persisted?
|
49
|
+
return false unless doc[:_rev]
|
50
|
+
!doc[:_rev].empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_h
|
54
|
+
doc
|
55
|
+
end
|
56
|
+
|
57
|
+
def valid?
|
58
|
+
true
|
59
|
+
end
|
60
|
+
|
61
|
+
def after_save(response)
|
62
|
+
self.rev = response[:rev]
|
63
|
+
response[:ok]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,47 @@
|
|
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
|
17
|
+
self.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
|
+
|
28
|
+
def typed?
|
29
|
+
respond_to?(:type)
|
30
|
+
end
|
31
|
+
|
32
|
+
def set_type
|
33
|
+
return unless typed?
|
34
|
+
write_attribute(:type, name_paramitized)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.included(base)
|
38
|
+
base.extend(ClassMethods)
|
39
|
+
end
|
40
|
+
|
41
|
+
module ClassMethods
|
42
|
+
def typed_model
|
43
|
+
property :type, class_name: String
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,32 @@
|
|
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 InvalidMangoOperatorError < RuntimeError
|
19
|
+
def initialize msg
|
20
|
+
@msg = msg
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"Invalid Mango operator: #{@msg.inspect}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class IndexNotFoundError < RuntimeError; end
|
29
|
+
class InvalidConfigFileError < RuntimeError; end
|
30
|
+
class InvalidProperty < RuntimeError; end
|
31
|
+
class DocumentInvalidError < RuntimeError; end
|
32
|
+
end
|