dolly 1.1.7 → 3.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.
- checksums.yaml +5 -5
- data/README.md +78 -0
- 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 +35 -10
- data/lib/dolly/connection.rb +93 -22
- data/lib/dolly/depracated_database.rb +24 -0
- data/lib/dolly/document.rb +61 -208
- data/lib/dolly/document_creation.rb +27 -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/framework_helper.rb +7 -0
- data/lib/dolly/identity_properties.rb +29 -0
- data/lib/dolly/mango.rb +156 -0
- data/lib/dolly/mango_index.rb +73 -0
- data/lib/dolly/properties.rb +36 -0
- data/lib/dolly/property.rb +76 -46
- data/lib/dolly/property_manager.rb +53 -0
- data/lib/dolly/property_set.rb +23 -0
- data/lib/dolly/query.rb +63 -75
- 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/view_query.rb +21 -0
- data/lib/dolly.rb +2 -23
- 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 +27 -4
- data/test/bulk_document_test.rb +8 -5
- data/test/document_test.rb +130 -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 +46417 -46858
- data/test/inheritance_test.rb +23 -0
- data/test/mango_index_test.rb +64 -0
- data/test/mango_test.rb +273 -0
- data/test/property_manager_test.rb +18 -0
- data/test/test_helper.rb +63 -18
- data/test/view_query_test.rb +27 -0
- metadata +67 -140
- 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/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/config.ru +0 -4
- 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
data/lib/dolly/document.rb
CHANGED
@@ -1,217 +1,70 @@
|
|
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 'dolly/framework_helper'
|
19
|
+
require 'refinements/string_refinements'
|
4
20
|
|
5
21
|
module Dolly
|
6
22
|
class Document
|
7
|
-
extend
|
8
|
-
|
9
|
-
extend
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
def
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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 ||= {}
|
149
|
-
|
150
|
-
ary.each do |name|
|
151
|
-
self.properties[name] = Property.new options.merge(name: name)
|
152
|
-
self.write_methods name
|
23
|
+
extend Mango
|
24
|
+
extend Query
|
25
|
+
extend ViewQuery
|
26
|
+
extend Request
|
27
|
+
extend DepracatedDatabase
|
28
|
+
extend Properties
|
29
|
+
extend DocumentCreation
|
30
|
+
|
31
|
+
include DocumentType
|
32
|
+
include PropertyManager
|
33
|
+
include Timestamp
|
34
|
+
include DocumentState
|
35
|
+
include IdentityProperties
|
36
|
+
include Attachment
|
37
|
+
include QueryArguments
|
38
|
+
include ClassMethodsDelegation
|
39
|
+
include FrameworkHelper
|
40
|
+
|
41
|
+
attr_writer :doc
|
42
|
+
|
43
|
+
def initialize(attributes = {})
|
44
|
+
init_ancestor_properties
|
45
|
+
properties.each(&build_property(attributes))
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def doc
|
51
|
+
@doc ||= doc_for_framework
|
52
|
+
end
|
53
|
+
|
54
|
+
def init_ancestor_properties
|
55
|
+
self.class.ancestors.map do |ancestor|
|
56
|
+
begin
|
57
|
+
ancestor.properties.entries.each do |property|
|
58
|
+
properties << property
|
59
|
+
end
|
60
|
+
rescue NoMethodError => e
|
61
|
+
end
|
153
62
|
end
|
154
63
|
end
|
155
64
|
|
156
|
-
|
157
|
-
|
158
|
-
|
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
|
165
|
-
|
166
|
-
def write_property name, value
|
167
|
-
instance_variable_set(:"@#{name}", value)
|
168
|
-
@doc[name.to_s] = value
|
65
|
+
def doc_for_framework
|
66
|
+
return {} unless rails?
|
67
|
+
{}.with_indifferent_access
|
169
68
|
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
|
213
|
-
end
|
214
|
-
|
215
|
-
def valid?; true; end
|
216
69
|
end
|
217
70
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'dolly/properties'
|
2
|
+
require 'dolly/framework_helper'
|
3
|
+
|
4
|
+
module Dolly
|
5
|
+
module DocumentCreation
|
6
|
+
include Properties
|
7
|
+
include FrameworkHelper
|
8
|
+
|
9
|
+
def from_doc(doc)
|
10
|
+
attributes = property_clean_doc(doc)
|
11
|
+
|
12
|
+
new(attributes).tap do |model|
|
13
|
+
model.send(:doc).merge!(doc)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def from_json(json)
|
18
|
+
raw_data = Oj.load(json, symbol_keys: true)
|
19
|
+
data = rails? ? data.with_indifferent_access : raw_data
|
20
|
+
from_doc(data)
|
21
|
+
end
|
22
|
+
|
23
|
+
def create(attributes)
|
24
|
+
new(attributes).tap { |model| model.save }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
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
|
@@ -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
|
data/lib/dolly/mango.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'refinements/hash_refinements'
|
4
|
+
|
5
|
+
module Dolly
|
6
|
+
module Mango
|
7
|
+
using HashRefinements
|
8
|
+
|
9
|
+
SELECTOR_SYMBOL = '$'
|
10
|
+
|
11
|
+
COMBINATION_OPERATORS = %I[
|
12
|
+
and
|
13
|
+
or
|
14
|
+
not
|
15
|
+
nor
|
16
|
+
all
|
17
|
+
elemMatch
|
18
|
+
allMath
|
19
|
+
].freeze
|
20
|
+
|
21
|
+
CONDITION_OPERATORS = %I[
|
22
|
+
lt
|
23
|
+
lte
|
24
|
+
eq
|
25
|
+
ne
|
26
|
+
gte
|
27
|
+
gt
|
28
|
+
exists
|
29
|
+
in
|
30
|
+
nin
|
31
|
+
size
|
32
|
+
mod
|
33
|
+
regex
|
34
|
+
].freeze
|
35
|
+
|
36
|
+
TYPE_OPERATOR = %I[
|
37
|
+
type
|
38
|
+
$type
|
39
|
+
]
|
40
|
+
|
41
|
+
ALL_OPERATORS = COMBINATION_OPERATORS + CONDITION_OPERATORS
|
42
|
+
|
43
|
+
DESIGN = '_find'
|
44
|
+
|
45
|
+
def find_by(query, opts = {})
|
46
|
+
build_model_from_doc(find_doc_by(query, opts))
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_doc_by(query, opts = {})
|
50
|
+
opts.merge!(limit: 1)
|
51
|
+
response = perform_query(build_query(query, opts))
|
52
|
+
print_index_warning(query) if response.fetch(:warning, nil)
|
53
|
+
response[:docs].first
|
54
|
+
end
|
55
|
+
|
56
|
+
def where(query, opts = {})
|
57
|
+
docs_where(query, opts).map do |doc|
|
58
|
+
build_model_from_doc(doc)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def docs_where(query, opts = {})
|
63
|
+
response = perform_query(build_query(query, opts))
|
64
|
+
print_index_warning(query) if response.fetch(:warning, nil)
|
65
|
+
response[:docs]
|
66
|
+
end
|
67
|
+
|
68
|
+
def find_bare(id, fields, options = {})
|
69
|
+
q = { _id: id }
|
70
|
+
opts = { fields: fields }.merge(options)
|
71
|
+
query = build_query(q, opts)
|
72
|
+
response = perform_query(query)
|
73
|
+
response[:docs]
|
74
|
+
end
|
75
|
+
|
76
|
+
def where_bare(selector, fields, options = {})
|
77
|
+
opts = { fields: fields }.merge(options)
|
78
|
+
query = build_query(selector, opts)
|
79
|
+
response = perform_query(query)
|
80
|
+
response[:docs]
|
81
|
+
end
|
82
|
+
|
83
|
+
def find_with_metadata(query, options = {})
|
84
|
+
opts = options.merge!(limit: 1)
|
85
|
+
perform_query(build_query(query, opts))
|
86
|
+
end
|
87
|
+
|
88
|
+
def where_with_metadata(query, options = {})
|
89
|
+
perform_query(build_query(query, options))
|
90
|
+
end
|
91
|
+
|
92
|
+
def perform_query(structured_query)
|
93
|
+
connection.post(DESIGN, structured_query)
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def print_index_warning(query)
|
99
|
+
message = "Index not found for #{query.inspect}"
|
100
|
+
if (defined?(Rails.logger) && Rails&.env&.development?)
|
101
|
+
Rails.logger.info(message)
|
102
|
+
else
|
103
|
+
puts message
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def build_model_from_doc(doc)
|
108
|
+
return nil if doc.nil?
|
109
|
+
new(doc.slice(*all_property_keys)).tap { |d| d.rev = doc[:_rev] }
|
110
|
+
end
|
111
|
+
|
112
|
+
def build_query(query, opts)
|
113
|
+
{ 'selector' => build_selectors(query) }.merge(opts)
|
114
|
+
end
|
115
|
+
|
116
|
+
def build_selectors(query)
|
117
|
+
query.deep_transform_keys do |key|
|
118
|
+
next build_key(key) if is_operator?(key)
|
119
|
+
next key if is_type_operator?(key)
|
120
|
+
raise Dolly::InvalidMangoOperatorError.new(key) unless has_property?(key)
|
121
|
+
key
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def build_key(key)
|
126
|
+
return key if key.to_s.starts_with?(SELECTOR_SYMBOL)
|
127
|
+
"#{SELECTOR_SYMBOL}#{key}"
|
128
|
+
end
|
129
|
+
|
130
|
+
def is_operator?(key)
|
131
|
+
ALL_OPERATORS.include?(key) || key.to_s.starts_with?(SELECTOR_SYMBOL)
|
132
|
+
end
|
133
|
+
|
134
|
+
def fetch_fields(query)
|
135
|
+
deep_keys(query).reject do |key|
|
136
|
+
is_operator?(key) || is_type_operator?(key)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def has_property?(key)
|
141
|
+
self.all_property_keys.include?(key)
|
142
|
+
end
|
143
|
+
|
144
|
+
def is_type_operator?(key)
|
145
|
+
TYPE_OPERATOR.include?(key.to_sym)
|
146
|
+
end
|
147
|
+
|
148
|
+
def deep_keys(obj)
|
149
|
+
case obj
|
150
|
+
when Hash then obj.keys + obj.values.flat_map { |v| deep_keys(v) }
|
151
|
+
when Array then obj.flat_map { |i| deep_keys(i) }
|
152
|
+
else []
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|