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
data/lib/dolly/property.rb
CHANGED
@@ -16,10 +16,15 @@ module Dolly
|
|
16
16
|
def cast_value(value)
|
17
17
|
return set_default if value.nil?
|
18
18
|
return value unless class_name
|
19
|
-
return
|
19
|
+
return custom_class(value) unless respond_to?(klass_sym)
|
20
20
|
send(klass_sym, value)
|
21
21
|
end
|
22
22
|
|
23
|
+
def custom_class(value)
|
24
|
+
value = value.is_a?(Hash) ? value.symbolize_keys : value
|
25
|
+
self_klass.new(value)
|
26
|
+
end
|
27
|
+
|
23
28
|
def boolean?
|
24
29
|
[TrueClass, FalseClass].include?(class_name)
|
25
30
|
end
|
@@ -32,6 +37,14 @@ module Dolly
|
|
32
37
|
value.to_h
|
33
38
|
end
|
34
39
|
|
40
|
+
def hash_with_indifferent_access_value(value)
|
41
|
+
if defined?(Rails)
|
42
|
+
value.to_h.with_indifferent_access
|
43
|
+
else
|
44
|
+
value.to_h
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
35
48
|
def integer_value(value)
|
36
49
|
value.to_i
|
37
50
|
end
|
@@ -70,7 +83,13 @@ module Dolly
|
|
70
83
|
end
|
71
84
|
|
72
85
|
def klass_sym
|
73
|
-
|
86
|
+
klass_name = self_klass.
|
87
|
+
name.
|
88
|
+
split('::').
|
89
|
+
last.
|
90
|
+
underscore
|
91
|
+
|
92
|
+
:"#{klass_name}_value"
|
74
93
|
end
|
75
94
|
|
76
95
|
def self_klass
|
@@ -2,9 +2,10 @@ module Dolly
|
|
2
2
|
module PropertyManager
|
3
3
|
def build_property(attributes)
|
4
4
|
assign_identity_properties(attributes)
|
5
|
+
assign_rev_properties(attributes)
|
5
6
|
|
6
7
|
lambda do |property|
|
7
|
-
name = property.key
|
8
|
+
name = property.key
|
8
9
|
next unless doc[name].nil?
|
9
10
|
write_attribute(name, attributes[name])
|
10
11
|
end
|
@@ -17,14 +18,14 @@ module Dolly
|
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
20
|
-
def write_attribute
|
21
|
-
|
22
|
-
instance_variable_set(:"@#{key}",
|
23
|
-
update_doc(key,
|
21
|
+
def write_attribute(key, value)
|
22
|
+
casted_value = set_property_value(key, value)
|
23
|
+
instance_variable_set(:"@#{key}", casted_value)
|
24
|
+
update_doc(key, casted_value)
|
24
25
|
end
|
25
26
|
|
26
27
|
def valid_property?(name)
|
27
|
-
properties.include?
|
28
|
+
properties.include?(name)
|
28
29
|
end
|
29
30
|
|
30
31
|
def update_doc(key, value)
|
@@ -43,5 +44,10 @@ module Dolly
|
|
43
44
|
id_presence = opts[:id] || opts[:_id] || opts['id'] || opts['_id']
|
44
45
|
self.id = id_presence if id_presence
|
45
46
|
end
|
47
|
+
|
48
|
+
def assign_rev_properties(opts = {})
|
49
|
+
rev_presence = opts[:rev] || opts [:_rev] || opts['rev'] || opts['_rev']
|
50
|
+
self.rev = rev_presence if rev_presence
|
51
|
+
end
|
46
52
|
end
|
47
53
|
end
|
data/lib/dolly/property_set.rb
CHANGED
data/lib/dolly/query.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
require 'dolly/collection'
|
2
2
|
require 'dolly/query_arguments'
|
3
3
|
require 'dolly/document_type'
|
4
|
+
require 'refinements/string_refinements'
|
4
5
|
|
5
6
|
module Dolly
|
6
7
|
module Query
|
7
8
|
include QueryArguments
|
8
9
|
include DocumentType
|
9
10
|
|
11
|
+
using StringRefinements
|
12
|
+
|
10
13
|
def find *keys
|
11
14
|
query_hash = { keys: namespace_keys(keys) }
|
12
15
|
|
@@ -14,6 +17,24 @@ module Dolly
|
|
14
17
|
raise(Dolly::ResourceNotFound)
|
15
18
|
end
|
16
19
|
|
20
|
+
def bulk_find(*keys_to_find)
|
21
|
+
data = {
|
22
|
+
query: { include_docs: true },
|
23
|
+
keys: keys_to_find.map { |key| namespace_key(key) }
|
24
|
+
}
|
25
|
+
|
26
|
+
res = connection.post('_all_docs', data)
|
27
|
+
Collection.new(rows: res, options: { doc_type: self.class_name })
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_all(*keys)
|
31
|
+
query_hash = { keys: namespace_keys(keys) }
|
32
|
+
return [] if query_hash[:keys].none?
|
33
|
+
|
34
|
+
keys_to_find_counter = query_hash[:keys].length
|
35
|
+
build_collection(query_hash).first_or_all(true)&.itself
|
36
|
+
end
|
37
|
+
|
17
38
|
def safe_find *keys
|
18
39
|
find *keys
|
19
40
|
rescue Dolly::ResourceNotFound
|
@@ -35,19 +56,14 @@ module Dolly
|
|
35
56
|
end
|
36
57
|
|
37
58
|
def find_with doc, view_name, opts = {}
|
38
|
-
opts = opts.each_with_object({}) { |(k, v), h| h[k] =
|
59
|
+
opts = opts.each_with_object({}) { |(k, v), h| h[k] = v }
|
39
60
|
query_results = raw_view(doc, view_name, opts)
|
40
61
|
|
41
|
-
Collection.new(query_results).first_or_all
|
42
|
-
end
|
43
|
-
|
44
|
-
def raw_view doc, view_name, opts = {}
|
45
|
-
design = "_design/#{doc}/_view/#{view_name}"
|
46
|
-
connection.view(design, opts)
|
62
|
+
Collection.new({ rows: query_results, options: {} }).first_or_all
|
47
63
|
end
|
48
64
|
|
49
65
|
def build_collection(query)
|
50
|
-
Collection.new(connection.get('_all_docs', query.merge(include_docs: true)))
|
66
|
+
Collection.new({ rows: connection.get('_all_docs', query.merge(include_docs: true)), options: { doc_type: self.class_name }})
|
51
67
|
end
|
52
68
|
|
53
69
|
def bulk_document
|
data/lib/dolly/request.rb
CHANGED
data/lib/dolly/request_header.rb
CHANGED
@@ -7,7 +7,7 @@ module Dolly
|
|
7
7
|
CONTENT_TYPE_KEY = 'Content-Type'
|
8
8
|
JSON_CONTENT = 'application/json'
|
9
9
|
|
10
|
-
def_delegators :@collection, :[], :[]=, :keys, :each
|
10
|
+
def_delegators :@collection, :[], :[]=, :keys, :each, :present?, :merge!
|
11
11
|
|
12
12
|
def initialize hash = nil
|
13
13
|
@collection = hash || default_value
|
data/lib/dolly/version.rb
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'dolly/class_methods_delegation'
|
2
|
+
|
3
|
+
module Dolly
|
4
|
+
module ViewQuery
|
5
|
+
def raw_view(design, view_name, opts = {})
|
6
|
+
design = "_design/#{design}/_view/#{view_name}"
|
7
|
+
connection.view(design, opts)
|
8
|
+
end
|
9
|
+
|
10
|
+
def view_value(doc, view_name, opts = {})
|
11
|
+
raw_view(doc, view_name, opts)[:rows].flat_map { |result| result[:value] }
|
12
|
+
end
|
13
|
+
|
14
|
+
def collection_view(design, view_name, opts = {})
|
15
|
+
opts.delete(:include_docs)
|
16
|
+
design = "_design/#{design}/_view/#{view_name}"
|
17
|
+
response = connection.view(design, opts)
|
18
|
+
Dolly::Collection.new(rows: response, options: opts)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/dolly.rb
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
module HashRefinements
|
2
|
+
refine Hash do
|
3
|
+
# File activesupport/lib/active_support/core_ext/hash/keys.rb, line 82
|
4
|
+
def deep_transform_keys(&block)
|
5
|
+
_deep_transform_keys_in_object(self, &block)
|
6
|
+
end
|
7
|
+
|
8
|
+
def slice(*keys)
|
9
|
+
keys.each_with_object(Hash.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def _deep_transform_keys_in_object(object, &block)
|
15
|
+
case object
|
16
|
+
when Hash
|
17
|
+
object.each_with_object({}) do |(key, value), result|
|
18
|
+
result[yield(key)] = _deep_transform_keys_in_object(value, &block)
|
19
|
+
end
|
20
|
+
when Array
|
21
|
+
object.map { |e| _deep_transform_keys_in_object(e, &block) }
|
22
|
+
else
|
23
|
+
object
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/tasks/db.rake
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
namespace :db do
|
2
4
|
desc "Will create if missing database and add default views"
|
3
5
|
task setup: :environment do
|
@@ -40,7 +42,7 @@ namespace :db do
|
|
40
42
|
begin
|
41
43
|
hash_doc = Dolly::Document.connection.request(:get, view_doc["_id"])
|
42
44
|
|
43
|
-
rev = hash_doc.delete(
|
45
|
+
rev = hash_doc.delete(:_rev)
|
44
46
|
|
45
47
|
if hash_doc == view_doc
|
46
48
|
puts 'everything up to date'
|
@@ -59,5 +61,25 @@ namespace :db do
|
|
59
61
|
end
|
60
62
|
end
|
61
63
|
|
64
|
+
namespace :index do
|
65
|
+
desc 'Creates indexes for mango querys located in db/indexes/*.json'
|
66
|
+
task create: :environment do
|
67
|
+
indexes_dir = Rails.root.join('db', 'indexes')
|
68
|
+
files = Dir.glob File.join(indexes_dir, '**', '*.json')
|
69
|
+
|
70
|
+
files.each do |file|
|
71
|
+
index_data = JSON.parse(File.read(file))
|
72
|
+
database = index_data.fetch('db', 'default').to_sym
|
73
|
+
puts "*" * 100
|
74
|
+
puts "Creating index: #{index_data["name"]} for database: #{database}"
|
75
|
+
|
76
|
+
if database == Dolly::Connection::DEFAULT_DATABASE
|
77
|
+
puts Dolly::MangoIndex.create(index_data['name'], index_data['fields'])
|
78
|
+
else
|
79
|
+
puts Dolly::MangoIndex.create_in_database(database, index_data['name'], index_data['fields'])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
62
84
|
end
|
63
85
|
|
data/test/document_test.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class BaseDolly < Dolly::Document; end
|
4
|
-
|
5
3
|
class BarFoo < BaseDolly
|
6
4
|
property :a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n, :persist
|
7
5
|
end
|
@@ -54,7 +52,6 @@ class Bar < FooBar
|
|
54
52
|
end
|
55
53
|
|
56
54
|
class DocumentTest < Test::Unit::TestCase
|
57
|
-
DB_BASE_PATH = "http://localhost:5984/test".freeze
|
58
55
|
|
59
56
|
def setup
|
60
57
|
data = {foo: 'Foo', bar: 'Bar', type: 'foo_bar'}
|
@@ -578,50 +575,4 @@ class DocumentTest < Test::Unit::TestCase
|
|
578
575
|
assert bar = Bar.new(a: 1)
|
579
576
|
assert_equal 1, bar.a
|
580
577
|
end
|
581
|
-
|
582
|
-
private
|
583
|
-
def generic_response rows, count = 1
|
584
|
-
{total_rows: count, offset:0, rows: rows}
|
585
|
-
end
|
586
|
-
|
587
|
-
def build_view_response properties
|
588
|
-
rows = properties.map.with_index do |v, i|
|
589
|
-
{
|
590
|
-
id: "foo_bar/#{i}",
|
591
|
-
key: "foo_bar",
|
592
|
-
value: 1,
|
593
|
-
doc: {_id: "foo_bar/#{i}", _rev: SecureRandom.hex}.merge!(v)
|
594
|
-
}
|
595
|
-
end
|
596
|
-
generic_response rows, properties.count
|
597
|
-
end
|
598
|
-
|
599
|
-
def build_view_collation_response properties
|
600
|
-
rows = properties.map.with_index do |v, i|
|
601
|
-
id = i.zero? ? "foo_bar/#{i}" : "baz/#{i}"
|
602
|
-
{
|
603
|
-
id: id,
|
604
|
-
key: "foo_bar",
|
605
|
-
value: 1,
|
606
|
-
doc: {_id: id, _rev: SecureRandom.hex}.merge!(v)
|
607
|
-
}
|
608
|
-
end
|
609
|
-
generic_response rows, properties.count
|
610
|
-
end
|
611
|
-
|
612
|
-
|
613
|
-
def build_request keys, body, view_name = 'foo_bar'
|
614
|
-
query = "keys=#{CGI::escape keys.to_s.gsub(' ','')}&" unless keys&.empty?
|
615
|
-
stub_request(:get, "#{query_base_path}?#{query.to_s}include_docs=true").
|
616
|
-
to_return(body: body.to_json)
|
617
|
-
end
|
618
|
-
|
619
|
-
def query_base_path
|
620
|
-
"#{DB_BASE_PATH}/_all_docs"
|
621
|
-
end
|
622
|
-
|
623
|
-
def build_save_request(obj)
|
624
|
-
stub_request(:put, "#{DB_BASE_PATH}/#{CGI.escape(obj.id)}").
|
625
|
-
to_return(body: {ok: true, id: obj.id, rev: "FF0000" }.to_json)
|
626
|
-
end
|
627
578
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TypedDoc < Dolly::Document
|
4
|
+
typed_model
|
5
|
+
end
|
6
|
+
|
7
|
+
class UntypedDoc < Dolly::Document
|
8
|
+
end
|
9
|
+
|
10
|
+
class DocumentTypeTest < Test::Unit::TestCase
|
11
|
+
test 'typed?' do
|
12
|
+
assert_equal(TypedDoc.new.typed?, true)
|
13
|
+
assert_equal(UntypedDoc.new.typed?, false)
|
14
|
+
end
|
15
|
+
|
16
|
+
test 'typed_model' do
|
17
|
+
assert_equal(TypedDoc.new.type, nil)
|
18
|
+
assert_equal(UntypedDoc.new.respond_to?(:type), false)
|
19
|
+
assert_raise NoMethodError do
|
20
|
+
UntypedDoc.new.type
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
test 'set_type' do
|
25
|
+
assert_equal(TypedDoc.new.set_type, TypedDoc.name_paramitized)
|
26
|
+
assert_equal(UntypedDoc.new.set_type, nil)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class BaseDoc < Dolly::Document
|
4
|
+
typed_model
|
5
|
+
end
|
6
|
+
|
7
|
+
class BaseBaseDoc < BaseDoc
|
8
|
+
property :supertype
|
9
|
+
end
|
10
|
+
|
11
|
+
class NewBar < BaseBaseDoc
|
12
|
+
property :a, :b
|
13
|
+
end
|
14
|
+
|
15
|
+
class InheritanceTest < Test::Unit::TestCase
|
16
|
+
test 'property inheritance' do
|
17
|
+
assert_equal(BaseBaseDoc.new.properties.map(&:key), [:supertype, :type])
|
18
|
+
end
|
19
|
+
|
20
|
+
test 'deep properties inheritance' do
|
21
|
+
assert_equal(NewBar.new.properties.map(&:key), [:a, :b, :supertype, :type])
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class FooBar < BaseDolly
|
4
|
+
property :foo, :bar
|
5
|
+
property :with_default, default: 1
|
6
|
+
property :boolean, class_name: TrueClass, default: true
|
7
|
+
property :date, class_name: Date
|
8
|
+
property :time, class_name: Time
|
9
|
+
property :datetime, class_name: DateTime
|
10
|
+
property :is_nil, class_name: NilClass, default: nil
|
11
|
+
|
12
|
+
timestamps!
|
13
|
+
end
|
14
|
+
|
15
|
+
class MangoIndexTest < Test::Unit::TestCase
|
16
|
+
DB_BASE_PATH = "http://localhost:5984/test".freeze
|
17
|
+
|
18
|
+
def setup
|
19
|
+
stub_request(:get, index_base_path).
|
20
|
+
to_return(body: { indexes:[ {
|
21
|
+
ddoc: nil,
|
22
|
+
name:"_all_docs",
|
23
|
+
type:"special",
|
24
|
+
def:{ fields:[{ _id:"asc" }] }
|
25
|
+
},
|
26
|
+
{
|
27
|
+
ddoc: "_design/1",
|
28
|
+
name:"foo-index-json",
|
29
|
+
type:"json",
|
30
|
+
def:{ fields:[{ foo:"asc" }] }
|
31
|
+
}
|
32
|
+
]}.to_json)
|
33
|
+
end
|
34
|
+
|
35
|
+
test '#delete_all' do
|
36
|
+
previous_indexes = Dolly::MangoIndex.all
|
37
|
+
|
38
|
+
stub_request(:delete, index_delete_path(previous_indexes.last)).
|
39
|
+
to_return(body: { "ok": true }.to_json)
|
40
|
+
|
41
|
+
Dolly::MangoIndex.delete_all
|
42
|
+
|
43
|
+
stub_request(:get, index_base_path).
|
44
|
+
to_return(body: { indexes:[ {
|
45
|
+
ddoc: nil,
|
46
|
+
name:"_all_docs",
|
47
|
+
type:"special",
|
48
|
+
def:{ fields:[{ _id:"asc" }] }
|
49
|
+
}
|
50
|
+
]}.to_json)
|
51
|
+
|
52
|
+
new_indexes = Dolly::MangoIndex.all
|
53
|
+
assert_not_equal(new_indexes.length, previous_indexes.length)
|
54
|
+
assert_equal(new_indexes.length, 1)
|
55
|
+
end
|
56
|
+
|
57
|
+
def index_base_path
|
58
|
+
"#{DB_BASE_PATH}/_index"
|
59
|
+
end
|
60
|
+
|
61
|
+
def index_delete_path(doc)
|
62
|
+
"#{index_base_path}/#{doc[:ddoc]}/json/#{doc[:name]}"
|
63
|
+
end
|
64
|
+
end
|