dolly 3.0.0 → 3.1.2
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 +43 -0
- data/lib/dolly/bulk_document.rb +1 -1
- data/lib/dolly/collection.rb +17 -7
- data/lib/dolly/connection.rb +50 -34
- data/lib/dolly/document.rb +29 -2
- data/lib/dolly/document_creation.rb +9 -2
- data/lib/dolly/document_state.rb +1 -0
- data/lib/dolly/document_type.rb +21 -2
- data/lib/dolly/exceptions.rb +23 -0
- data/lib/dolly/framework_helper.rb +7 -0
- data/lib/dolly/mango.rb +156 -0
- data/lib/dolly/mango_index.rb +73 -0
- data/lib/dolly/properties.rb +7 -2
- data/lib/dolly/property.rb +21 -2
- data/lib/dolly/property_manager.rb +12 -6
- data/lib/dolly/property_set.rb +5 -0
- data/lib/dolly/query.rb +24 -8
- data/lib/dolly/query_arguments.rb +1 -1
- data/lib/dolly/request.rb +1 -1
- data/lib/dolly/request_header.rb +1 -1
- data/lib/dolly/version.rb +1 -1
- data/lib/dolly/view_query.rb +21 -0
- data/lib/dolly.rb +2 -0
- data/lib/refinements/hash_refinements.rb +27 -0
- data/lib/tasks/db.rake +23 -1
- data/test/document_test.rb +0 -49
- data/test/document_type_test.rb +28 -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/property_manager_test.rb +18 -0
- data/test/test_helper.rb +51 -0
- data/test/view_query_test.rb +27 -0
- metadata +39 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2242b051913632959c785357a5de12506673ae26c369bdaccb77763edcf5a15b
|
4
|
+
data.tar.gz: db0e1cb72dba11f0adc1184338754266dfccfe2eabd4188a23b1a313d236f2bf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '096b5264f6a0c36dc68c5c6179300fc7ca2b7fb9d8806b83929ae34180d0f900b8fb64053a6423b51d9d12b716d685cc580618290f5b80cb9041decaa217af1d'
|
7
|
+
data.tar.gz: 1220024efbc8a6bb491099f612b260f010d74bbf5d26bc96367ec676cb1095ecbf3f98194b1449f2563116770f23f5ba9bbac985a0b314ba18f3658ee85b5278
|
data/README.md
CHANGED
@@ -33,3 +33,46 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
33
33
|
## Contributing
|
34
34
|
|
35
35
|
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/dolly3.
|
36
|
+
|
37
|
+
|
38
|
+
## Migrating from couch 1.x to 2.x
|
39
|
+
|
40
|
+
[Official docs](https://docs.couchdb.org/en/2.3.1/install/index.html)
|
41
|
+
|
42
|
+
|
43
|
+
You will need to uninstall the couchdb service with brew
|
44
|
+
|
45
|
+
`brew services stop couchdb`
|
46
|
+
`brew services uninstall couchdb`
|
47
|
+
|
48
|
+
Download the application from the following source
|
49
|
+
|
50
|
+
http://couchdb.apache.org/#download
|
51
|
+
|
52
|
+
launch fauxton and check your installation
|
53
|
+
|
54
|
+
Copy [this file](https://github.com/apache/couchdb/blob/master/rel/overlay/bin/couchup) into your filesystem
|
55
|
+
|
56
|
+
|
57
|
+
make it executable
|
58
|
+
|
59
|
+
`chmod +x couchup.py`
|
60
|
+
|
61
|
+
and run it
|
62
|
+
|
63
|
+
`./couchup.py -h`
|
64
|
+
|
65
|
+
You might need to install python 3 and pip3 and the following libs
|
66
|
+
|
67
|
+
`pip3 install requests progressbar2`
|
68
|
+
|
69
|
+
move your .couch files into the specified `database_dir` in your [fauxton config](http://127.0.0.1:5984/_utils/#_config/couchdb@localhost)
|
70
|
+
|
71
|
+
|
72
|
+
```
|
73
|
+
$ ./couchup list # Shows your unmigrated 1.x databases
|
74
|
+
$ ./couchup replicate -a # Replicates your 1.x DBs to 2.x
|
75
|
+
$ ./couchup rebuild -a # Optional; starts rebuilding your views
|
76
|
+
$ ./couchup delete -a # Deletes your 1.x DBs (careful!)
|
77
|
+
$ ./couchup list # Should show no remaining databases!
|
78
|
+
```
|
data/lib/dolly/bulk_document.rb
CHANGED
@@ -80,7 +80,7 @@ module Dolly
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def response_error(item)
|
83
|
-
BulkError.new(error: 'Document saved but not local rev updated.', reason: "Document with id #{
|
83
|
+
BulkError.new(error: 'Document saved but not local rev updated.', reason: "Document with id #{item} on bulk doc was not found in payload.", obj: nil)
|
84
84
|
end
|
85
85
|
end
|
86
86
|
end
|
data/lib/dolly/collection.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
module Dolly
|
2
2
|
class Collection < DelegateClass(Array)
|
3
|
-
attr_reader :
|
3
|
+
attr_reader :options
|
4
4
|
|
5
|
-
def initialize(rows: [],
|
6
|
-
@
|
5
|
+
def initialize(rows: [], options: {})
|
6
|
+
@options = options
|
7
7
|
#TODO: We should raise an exception if one of the
|
8
8
|
# requested documents is missing
|
9
|
-
super rows.map(&collect_docs).compact
|
9
|
+
super rows[:rows].map(&collect_docs).compact
|
10
10
|
end
|
11
11
|
|
12
12
|
def first_or_all(forced_first = false)
|
@@ -23,13 +23,23 @@ module Dolly
|
|
23
23
|
def collect_docs
|
24
24
|
lambda do |row|
|
25
25
|
next unless collectable_row?(row)
|
26
|
-
klass = Object.const_get
|
26
|
+
klass = Object.const_get(doc_model(row))
|
27
27
|
klass.from_doc(row[:doc])
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
def
|
32
|
-
|
31
|
+
def doc_model(doc)
|
32
|
+
options[:doc_type] || constantize_key(doc[:doc_type]) || constantize_key(doc_type_for(doc[:id]))
|
33
|
+
end
|
34
|
+
|
35
|
+
def doc_type_for(key)
|
36
|
+
return false if key.nil?
|
37
|
+
key.match(%r{^([^/]+)/})[1]
|
38
|
+
end
|
39
|
+
|
40
|
+
def constantize_key(key)
|
41
|
+
return false if key.nil?
|
42
|
+
key.split('_').collect(&:capitalize).join
|
33
43
|
end
|
34
44
|
|
35
45
|
def collectable_row?(row)
|
data/lib/dolly/connection.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'curb'
|
1
2
|
require 'oj'
|
2
3
|
require 'cgi'
|
3
4
|
require 'net/http'
|
@@ -5,24 +6,28 @@ require 'dolly/request_header'
|
|
5
6
|
require 'dolly/exceptions'
|
6
7
|
require 'dolly/configuration'
|
7
8
|
require 'refinements/string_refinements'
|
9
|
+
require 'dolly/framework_helper'
|
8
10
|
|
9
11
|
module Dolly
|
10
12
|
class Connection
|
11
13
|
include Dolly::Configuration
|
14
|
+
include Dolly::FrameworkHelper
|
12
15
|
attr_reader :db, :app_env
|
13
16
|
|
14
|
-
DEFAULT_HEADER = { 'Content-Type' => 'application/json' }
|
17
|
+
DEFAULT_HEADER = { 'Content-Type' => 'application/json', 'Accept' => 'application/json' }
|
18
|
+
SECURE_PROTOCOL = 'https'
|
19
|
+
DEFAULT_DATABASE = :default
|
15
20
|
|
16
21
|
using StringRefinements
|
17
22
|
|
18
|
-
def initialize db =
|
23
|
+
def initialize db = DEFAULT_DATABASE, app_env = :development
|
19
24
|
@db = db
|
20
25
|
@app_env = app_env
|
21
26
|
end
|
22
27
|
|
23
28
|
def get(resource, data = {})
|
24
29
|
query = { query: values_to_json(data) } if data
|
25
|
-
request :get, resource
|
30
|
+
request :get, resource, query
|
26
31
|
end
|
27
32
|
|
28
33
|
def post resource, data
|
@@ -33,8 +38,10 @@ module Dolly
|
|
33
38
|
request :put, resource.cgi_escape, data
|
34
39
|
end
|
35
40
|
|
36
|
-
def delete resource, rev
|
37
|
-
|
41
|
+
def delete resource, rev = nil, escape: true
|
42
|
+
query = "?rev=#{rev}" if rev
|
43
|
+
resource = "#{escape ? resource.cgi_escape : resource}#{query}"
|
44
|
+
request :delete, resource
|
38
45
|
end
|
39
46
|
|
40
47
|
def view resource, opts
|
@@ -58,46 +65,55 @@ module Dolly
|
|
58
65
|
end
|
59
66
|
|
60
67
|
def request(method, resource, data = {})
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
68
|
+
db_resource = (resource =~ %r{^/}) ? resource : "/#{db_name}/#{resource}"
|
69
|
+
headers = fetch_headers(data)
|
70
|
+
body = fetch_body(data)
|
71
|
+
uri = URI("#{base_uri}#{db_resource}")
|
72
|
+
|
73
|
+
conn = curl_method_call(method, uri, body) do |curl|
|
74
|
+
if env['username'].present?
|
75
|
+
curl.http_auth_types = :basic
|
76
|
+
curl.username = env['username']
|
77
|
+
curl.password = env['password'].to_s
|
78
|
+
end
|
79
|
+
|
80
|
+
headers.each { |k, v| curl.headers[k] = v } if headers.present?
|
81
|
+
end
|
82
|
+
response_format(conn, method)
|
69
83
|
end
|
70
84
|
|
71
85
|
private
|
72
86
|
|
73
|
-
def
|
74
|
-
|
75
|
-
|
76
|
-
http.request(req)
|
77
|
-
end
|
87
|
+
def fetch_headers(data)
|
88
|
+
return unless data.is_a?(Hash)
|
89
|
+
Dolly::HeaderRequest.new(data&.delete(:headers))
|
78
90
|
end
|
79
91
|
|
80
|
-
def
|
81
|
-
|
82
|
-
raise Dolly::ServerError.new(res.body) if (400..600).include? res.code.to_i
|
83
|
-
Oj.load(res.body, symbol_keys: true)
|
84
|
-
end
|
92
|
+
def fetch_body(data)
|
93
|
+
return data unless data.is_a?(Hash)
|
85
94
|
|
86
|
-
|
87
|
-
|
88
|
-
body = data.delete(:_body) || data
|
89
|
-
is_json ? body.to_json : body
|
95
|
+
data&.delete(:headers)
|
96
|
+
data&.merge!(data&.delete(:query) || {})
|
90
97
|
end
|
91
98
|
|
92
|
-
def
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
99
|
+
def curl_method_call(method, uri, data, &block)
|
100
|
+
return Curl::Easy.http_head(uri.to_s, &block) if method.to_sym == :head
|
101
|
+
return Curl.delete(uri.to_s, &block) if method.to_sym == :delete
|
102
|
+
return Curl.send(method, uri, data, &block) if method.to_sym == :get
|
103
|
+
Curl.send(method, uri.to_s, data.to_json, &block)
|
97
104
|
end
|
98
105
|
|
99
|
-
def
|
100
|
-
|
106
|
+
def response_format(res, method)
|
107
|
+
raise Dolly::ResourceNotFound if res.status.to_i == 404
|
108
|
+
raise Dolly::ServerError.new(res.status.to_i) if (400..600).include? res.status.to_i
|
109
|
+
return res.header_str if method == :head
|
110
|
+
|
111
|
+
data = Oj.load(res.body_str, symbol_keys: true)
|
112
|
+
return data unless rails?
|
113
|
+
return data.with_indifferent_access if data.is_a?(Hash)
|
114
|
+
data
|
115
|
+
rescue Oj::ParseError
|
116
|
+
res.body_str
|
101
117
|
end
|
102
118
|
|
103
119
|
def values_to_json hash
|
data/lib/dolly/document.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
|
+
require 'dolly/mango'
|
2
|
+
require 'dolly/mango_index'
|
1
3
|
require 'dolly/query'
|
4
|
+
require 'dolly/view_query'
|
2
5
|
require 'dolly/connection'
|
3
6
|
require 'dolly/request'
|
4
7
|
require 'dolly/depracated_database'
|
5
8
|
require 'dolly/document_state'
|
6
9
|
require 'dolly/properties'
|
10
|
+
require 'dolly/document_type'
|
7
11
|
require 'dolly/identity_properties'
|
8
12
|
require 'dolly/attachment'
|
9
13
|
require 'dolly/property_manager'
|
@@ -11,15 +15,20 @@ require 'dolly/timestamp'
|
|
11
15
|
require 'dolly/query_arguments'
|
12
16
|
require 'dolly/document_creation'
|
13
17
|
require 'dolly/class_methods_delegation'
|
18
|
+
require 'dolly/framework_helper'
|
14
19
|
require 'refinements/string_refinements'
|
15
20
|
|
16
21
|
module Dolly
|
17
22
|
class Document
|
23
|
+
extend Mango
|
18
24
|
extend Query
|
25
|
+
extend ViewQuery
|
19
26
|
extend Request
|
20
27
|
extend DepracatedDatabase
|
21
28
|
extend Properties
|
22
29
|
extend DocumentCreation
|
30
|
+
|
31
|
+
include DocumentType
|
23
32
|
include PropertyManager
|
24
33
|
include Timestamp
|
25
34
|
include DocumentState
|
@@ -27,17 +36,35 @@ module Dolly
|
|
27
36
|
include Attachment
|
28
37
|
include QueryArguments
|
29
38
|
include ClassMethodsDelegation
|
39
|
+
include FrameworkHelper
|
30
40
|
|
31
41
|
attr_writer :doc
|
32
42
|
|
33
|
-
def initialize
|
43
|
+
def initialize(attributes = {})
|
44
|
+
init_ancestor_properties
|
34
45
|
properties.each(&build_property(attributes))
|
35
46
|
end
|
36
47
|
|
37
48
|
protected
|
38
49
|
|
39
50
|
def doc
|
40
|
-
@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
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def doc_for_framework
|
66
|
+
return {} unless rails?
|
67
|
+
{}.with_indifferent_access
|
41
68
|
end
|
42
69
|
end
|
43
70
|
end
|
@@ -1,16 +1,23 @@
|
|
1
1
|
require 'dolly/properties'
|
2
|
+
require 'dolly/framework_helper'
|
2
3
|
|
3
4
|
module Dolly
|
4
5
|
module DocumentCreation
|
5
6
|
include Properties
|
7
|
+
include FrameworkHelper
|
6
8
|
|
7
9
|
def from_doc(doc)
|
8
10
|
attributes = property_clean_doc(doc)
|
9
|
-
|
11
|
+
|
12
|
+
new(attributes).tap do |model|
|
13
|
+
model.send(:doc).merge!(doc)
|
14
|
+
end
|
10
15
|
end
|
11
16
|
|
12
17
|
def from_json(json)
|
13
|
-
|
18
|
+
raw_data = Oj.load(json, symbol_keys: true)
|
19
|
+
data = rails? ? data.with_indifferent_access : raw_data
|
20
|
+
from_doc(data)
|
14
21
|
end
|
15
22
|
|
16
23
|
def create(attributes)
|
data/lib/dolly/document_state.rb
CHANGED
data/lib/dolly/document_type.rb
CHANGED
@@ -13,8 +13,8 @@ module Dolly
|
|
13
13
|
"#{name_paramitized}/#{key}"
|
14
14
|
end
|
15
15
|
|
16
|
-
def base_id
|
17
|
-
id.sub(%r{^#{name_paramitized}/}, '')
|
16
|
+
def base_id
|
17
|
+
self.id.sub(%r{^#{name_paramitized}/}, '')
|
18
18
|
end
|
19
19
|
|
20
20
|
def name_paramitized
|
@@ -24,5 +24,24 @@ module Dolly
|
|
24
24
|
def class_name
|
25
25
|
is_a?(Class) ? name : self.class.name
|
26
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
|
27
46
|
end
|
28
47
|
end
|
data/lib/dolly/exceptions.rb
CHANGED
@@ -15,6 +15,29 @@ module Dolly
|
|
15
15
|
end
|
16
16
|
end
|
17
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 BulkError < RuntimeError
|
29
|
+
def intialize(error:, reason:, obj:)
|
30
|
+
@error = error
|
31
|
+
@reason = reason
|
32
|
+
@obj = obj
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
"#{@error} on #{@obj} because #{@reason}."
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class IndexNotFoundError < RuntimeError; end
|
18
41
|
class InvalidConfigFileError < RuntimeError; end
|
19
42
|
class InvalidProperty < RuntimeError; end
|
20
43
|
class DocumentInvalidError < RuntimeError; 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
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'dolly/document'
|
5
|
+
|
6
|
+
module Dolly
|
7
|
+
class MangoIndex
|
8
|
+
class << self
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
ALL_DOCS = '_all_docs'
|
12
|
+
DESIGN = '_index'
|
13
|
+
ROWS_KEY = :rows
|
14
|
+
DESIGN_PREFIX = '_design/'
|
15
|
+
|
16
|
+
def_delegators :connection, :get, :post
|
17
|
+
|
18
|
+
def all
|
19
|
+
get(DESIGN)[:indexes]
|
20
|
+
end
|
21
|
+
|
22
|
+
def create(name, fields, type = 'json')
|
23
|
+
post(DESIGN, build_index_structure(name, fields, type))
|
24
|
+
end
|
25
|
+
|
26
|
+
def create_in_database(database, name, fields, type = 'json')
|
27
|
+
connection_for_database(database).post(DESIGN, build_index_structure(name, fields, type))
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_by_fields(fields)
|
31
|
+
rows = get(ALL_DOCS, key: key_from_fields(fields))[ROWS_KEY]
|
32
|
+
(rows && rows.any?)
|
33
|
+
end
|
34
|
+
|
35
|
+
def delete_all
|
36
|
+
all.each do |index_doc|
|
37
|
+
next if index_doc[:ddoc].nil?
|
38
|
+
delete(index_doc)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def delete(index_doc)
|
43
|
+
resource = "#{DESIGN}/#{index_doc[:ddoc]}/json/#{index_doc[:name]}"
|
44
|
+
connection.delete(resource, escape: false)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def connection_for_database(database)
|
50
|
+
Dolly::Connection.new(database.to_sym, Rails.env || :development)
|
51
|
+
end
|
52
|
+
|
53
|
+
def connection
|
54
|
+
@connection ||= Dolly::Document.connection
|
55
|
+
end
|
56
|
+
|
57
|
+
def build_index_structure(name, fields, type)
|
58
|
+
{
|
59
|
+
ddoc: key_from_fields(fields).gsub(DESIGN_PREFIX, ''),
|
60
|
+
index: {
|
61
|
+
fields: fields
|
62
|
+
},
|
63
|
+
name: name,
|
64
|
+
type: type
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
def key_from_fields(fields)
|
69
|
+
"#{DESIGN_PREFIX}index_#{fields.join('_')}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/dolly/properties.rb
CHANGED
@@ -7,6 +7,7 @@ module Dolly
|
|
7
7
|
|
8
8
|
def property *opts, class_name: nil, default: nil
|
9
9
|
opts.each do |opt|
|
10
|
+
|
10
11
|
properties << (prop = Property.new(opt, class_name, default))
|
11
12
|
send(:attr_reader, opt)
|
12
13
|
|
@@ -20,12 +21,16 @@ module Dolly
|
|
20
21
|
@properties ||= PropertySet.new
|
21
22
|
end
|
22
23
|
|
24
|
+
def all_property_keys
|
25
|
+
properties.map(&:key) + SPECIAL_KEYS
|
26
|
+
end
|
27
|
+
|
23
28
|
def property_keys
|
24
|
-
|
29
|
+
all_property_keys - SPECIAL_KEYS
|
25
30
|
end
|
26
31
|
|
27
32
|
def property_clean_doc(doc)
|
28
|
-
doc.reject { |key, _value|
|
33
|
+
doc.reject { |key, _value| property_keys.exclude?(key.to_sym) }
|
29
34
|
end
|
30
35
|
end
|
31
36
|
end
|