dolly 3.0.0 → 3.1.2

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