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
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
|