dolly 3.0.0 → 3.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|