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.
@@ -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 self_klass.new(value) unless respond_to?(klass_sym)
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
- :"#{self_klass.name.underscore}_value"
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.to_sym
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 key, value
21
- value = set_property_value(key, value)
22
- instance_variable_set(:"@#{key}", value)
23
- update_doc(key, value) unless value.nil?
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? name
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
@@ -4,6 +4,11 @@ module Dolly
4
4
  keys.include?(key)
5
5
  end
6
6
 
7
+ def <<(property)
8
+ return if include?(property.key)
9
+ super(property)
10
+ end
11
+
7
12
  def [](key)
8
13
  return detect {|property| property.key == key } if key.is_a?(Symbol)
9
14
  super
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] = escape_value(v) }
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
@@ -3,7 +3,7 @@
3
3
  module Dolly
4
4
  module QueryArguments
5
5
  def last_item_in_range
6
- URI.escape("\ufff0")
6
+ "\ufff0"
7
7
  end
8
8
 
9
9
  def default_query_args
data/lib/dolly/request.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Dolly
2
2
  module Request
3
3
  def set_namespace name
4
- @namspace = name
4
+ @namespace = name
5
5
  end
6
6
 
7
7
  def set_app_env env
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Dolly
2
- VERSION = "3.0.0"
2
+ VERSION = "3.1.2"
3
3
  end
@@ -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
@@ -1,5 +1,7 @@
1
1
  require "dolly/version"
2
2
  require "dolly/document"
3
+ require "dolly/bulk_document"
4
+ require 'dolly/mango_index'
3
5
  require 'railties/railtie' if defined?(Rails)
4
6
 
5
7
  module Dolly
@@ -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('_rev')
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
 
@@ -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