dolly 1.1.5 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +78 -0
  3. data/lib/dolly.rb +2 -23
  4. data/lib/dolly/attachment.rb +29 -0
  5. data/lib/dolly/bulk_document.rb +27 -26
  6. data/lib/dolly/class_methods_delegation.rb +15 -0
  7. data/lib/dolly/collection.rb +32 -65
  8. data/lib/dolly/configuration.rb +35 -10
  9. data/lib/dolly/connection.rb +86 -22
  10. data/lib/dolly/depracated_database.rb +24 -0
  11. data/lib/dolly/document.rb +48 -198
  12. data/lib/dolly/document_creation.rb +20 -0
  13. data/lib/dolly/document_state.rb +66 -0
  14. data/lib/dolly/document_type.rb +47 -0
  15. data/lib/dolly/exceptions.rb +32 -0
  16. data/lib/dolly/identity_properties.rb +29 -0
  17. data/lib/dolly/mango.rb +156 -0
  18. data/lib/dolly/mango_index.rb +73 -0
  19. data/lib/dolly/properties.rb +36 -0
  20. data/lib/dolly/property.rb +58 -47
  21. data/lib/dolly/property_manager.rb +53 -0
  22. data/lib/dolly/property_set.rb +23 -0
  23. data/lib/dolly/query.rb +63 -75
  24. data/lib/dolly/query_arguments.rb +35 -0
  25. data/lib/dolly/request.rb +12 -105
  26. data/lib/dolly/request_header.rb +26 -0
  27. data/lib/dolly/timestamp.rb +24 -0
  28. data/lib/dolly/version.rb +1 -1
  29. data/lib/dolly/view_query.rb +21 -0
  30. data/lib/{dolly → railties}/railtie.rb +2 -1
  31. data/lib/refinements/hash_refinements.rb +27 -0
  32. data/lib/refinements/string_refinements.rb +28 -0
  33. data/lib/tasks/db.rake +27 -4
  34. data/test/bulk_document_test.rb +8 -5
  35. data/test/document_test.rb +132 -95
  36. data/test/document_type_test.rb +28 -0
  37. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  38. data/test/dummy/log/test.log +55435 -33484
  39. data/test/inheritance_test.rb +23 -0
  40. data/test/mango_index_test.rb +64 -0
  41. data/test/mango_test.rb +273 -0
  42. data/test/property_manager_test.rb +18 -0
  43. data/test/test_helper.rb +63 -18
  44. data/test/view_query_test.rb +27 -0
  45. metadata +66 -138
  46. data/Rakefile +0 -11
  47. data/lib/dolly/bulk_error.rb +0 -16
  48. data/lib/dolly/db_config.rb +0 -20
  49. data/lib/dolly/interpreter.rb +0 -5
  50. data/lib/dolly/logger.rb +0 -9
  51. data/lib/dolly/name_space.rb +0 -28
  52. data/lib/dolly/timestamps.rb +0 -21
  53. data/lib/exceptions/dolly.rb +0 -38
  54. data/test/collection_test.rb +0 -59
  55. data/test/configuration_test.rb +0 -9
  56. data/test/dummy/README.rdoc +0 -28
  57. data/test/dummy/Rakefile +0 -6
  58. data/test/dummy/app/assets/javascripts/application.js +0 -13
  59. data/test/dummy/app/assets/stylesheets/application.css +0 -13
  60. data/test/dummy/app/controllers/application_controller.rb +0 -5
  61. data/test/dummy/app/helpers/application_helper.rb +0 -2
  62. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  63. data/test/dummy/bin/bundle +0 -3
  64. data/test/dummy/bin/rails +0 -4
  65. data/test/dummy/bin/rake +0 -4
  66. data/test/dummy/config.ru +0 -4
  67. data/test/dummy/config/application.rb +0 -27
  68. data/test/dummy/config/boot.rb +0 -5
  69. data/test/dummy/config/couchdb.yml +0 -13
  70. data/test/dummy/config/environment.rb +0 -5
  71. data/test/dummy/config/environments/development.rb +0 -29
  72. data/test/dummy/config/environments/production.rb +0 -80
  73. data/test/dummy/config/environments/test.rb +0 -36
  74. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  75. data/test/dummy/config/initializers/inflections.rb +0 -16
  76. data/test/dummy/config/initializers/mime_types.rb +0 -5
  77. data/test/dummy/config/initializers/secret_token.rb +0 -12
  78. data/test/dummy/config/initializers/session_store.rb +0 -3
  79. data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  80. data/test/dummy/config/locales/en.yml +0 -23
  81. data/test/dummy/config/routes.rb +0 -56
  82. data/test/dummy/lib/couch_rest_adapter/railtie.rb +0 -10
  83. data/test/dummy/public/404.html +0 -58
  84. data/test/dummy/public/422.html +0 -58
  85. data/test/dummy/public/500.html +0 -57
  86. data/test/dummy/public/favicon.ico +0 -0
  87. data/test/factories/factories.rb +0 -8
@@ -0,0 +1,47 @@
1
+ require 'refinements/string_refinements'
2
+
3
+ module Dolly
4
+ module DocumentType
5
+ using StringRefinements
6
+
7
+ def namespace_keys(keys)
8
+ keys.map { |key| namespace_key key }
9
+ end
10
+
11
+ def namespace_key(key)
12
+ return key if key =~ %r{^#{name_paramitized}/}
13
+ "#{name_paramitized}/#{key}"
14
+ end
15
+
16
+ def base_id
17
+ self.id.sub(%r{^#{name_paramitized}/}, '')
18
+ end
19
+
20
+ def name_paramitized
21
+ class_name.split("::").last.underscore
22
+ end
23
+
24
+ def class_name
25
+ is_a?(Class) ? name : self.class.name
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
46
+ end
47
+ end
@@ -0,0 +1,32 @@
1
+ module Dolly
2
+ class ResourceNotFound < RuntimeError
3
+ def to_s
4
+ 'The document was not found.'
5
+ end
6
+ end
7
+
8
+ class ServerError < RuntimeError
9
+ def initialize msg
10
+ @msg = msg
11
+ end
12
+
13
+ def to_s
14
+ "There has been an error on the couchdb server: #{@msg.inspect}"
15
+ end
16
+ end
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 IndexNotFoundError < RuntimeError; end
29
+ class InvalidConfigFileError < RuntimeError; end
30
+ class InvalidProperty < RuntimeError; end
31
+ class DocumentInvalidError < RuntimeError; end
32
+ end
@@ -0,0 +1,29 @@
1
+ require 'dolly/document_type'
2
+ require 'dolly/class_methods_delegation'
3
+
4
+ module Dolly
5
+ module IdentityProperties
6
+ include DocumentType
7
+ include ClassMethodsDelegation
8
+
9
+ def id
10
+ doc[:_id] ||= namespace_key(connection.uuids.last)
11
+ end
12
+
13
+ def id= value
14
+ doc[:_id] = namespace_key(value)
15
+ end
16
+
17
+ def rev
18
+ doc[:_rev]
19
+ end
20
+
21
+ def rev= value
22
+ doc[:_rev] = value
23
+ end
24
+
25
+ def id_as_resource
26
+ CGI.escape(id)
27
+ end
28
+ end
29
+ end
@@ -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
@@ -0,0 +1,36 @@
1
+ require 'dolly/property_set'
2
+ require 'dolly/property'
3
+
4
+ module Dolly
5
+ module Properties
6
+ SPECIAL_KEYS = %i[_id _rev]
7
+
8
+ def property *opts, class_name: nil, default: nil
9
+ opts.each do |opt|
10
+
11
+ properties << (prop = Property.new(opt, class_name, default))
12
+ send(:attr_reader, opt)
13
+
14
+ define_method(:"#{opt}=") { |value| write_attribute(opt, value) }
15
+ define_method(:"#{opt}?") { send(opt) } if prop.boolean?
16
+ define_method(:"[]") {|name| send(name) }
17
+ end
18
+ end
19
+
20
+ def properties
21
+ @properties ||= PropertySet.new
22
+ end
23
+
24
+ def all_property_keys
25
+ properties.map(&:key) + SPECIAL_KEYS
26
+ end
27
+
28
+ def property_keys
29
+ all_property_keys - SPECIAL_KEYS
30
+ end
31
+
32
+ def property_clean_doc(doc)
33
+ doc.reject { |key, _value| !property_keys.include?(key) }
34
+ end
35
+ end
36
+ end
@@ -1,85 +1,96 @@
1
+ require 'refinements/string_refinements'
2
+
1
3
  module Dolly
2
4
  class Property
3
- attr_writer :value
4
- attr_accessor :name
5
- attr_reader :class_name, :default
5
+ attr_reader :key, :class_name, :default
6
+ CANT_CLONE = [NilClass, TrueClass, FalseClass, Integer]
6
7
 
7
- CANT_CLONE = [NilClass, TrueClass, FalseClass, Fixnum].freeze
8
+ using StringRefinements
8
9
 
9
- def initialize opts = {}
10
- @class_name = opts.delete(:class_name) if opts.present?
11
- @name = opts.delete(:name).to_s
12
- @default = opts.delete(:default)
13
- @default = @default.clone if @default && CANT_CLONE.none? { |klass| @default.is_a? klass }
14
- @value = @default if @default
15
- warn 'There are some unprocessed options!' if opts.present?
10
+ def initialize(key, class_name, default = nil)
11
+ @key = key
12
+ @default = default
13
+ @class_name = class_name
16
14
  end
17
15
 
18
- def value
19
- #TODO: tets if this actually sets `doc[ "name" ]`
20
- return @default if @value.nil?
21
- return @value unless self_klass
22
-
23
- klass_sym = :"#{self_klass.name.underscore}_#{__method__}"
16
+ def cast_value(value)
17
+ return set_default if value.nil?
18
+ return value unless class_name
19
+ return self_klass.new(value) unless respond_to?(klass_sym)
20
+ send(klass_sym, value)
21
+ end
24
22
 
25
- return self_klass.new @value unless self.respond_to?(klass_sym)
23
+ def boolean?
24
+ [TrueClass, FalseClass].include?(class_name)
25
+ end
26
26
 
27
- self.send klass_sym
27
+ def string_value(value)
28
+ value.to_s
28
29
  end
29
30
 
30
- def array_value
31
- @value.to_a
31
+ def hash_value(value)
32
+ value.to_h
32
33
  end
33
34
 
34
- def hash_value
35
- @value.to_h
35
+ def integer_value(value)
36
+ value.to_i
36
37
  end
37
38
 
38
- def string_value
39
- @value.to_s
39
+ def float_value(value)
40
+ value.to_f
40
41
  end
41
42
 
42
- def integer_value
43
- @value.to_i
43
+ def date_value(value)
44
+ return value.to_date if value.respond_to?(:to_date)
45
+ Date.parse(value)
44
46
  end
45
47
 
46
- def float_value
47
- @value.to_f
48
+ def time_value(value)
49
+ return value.to_time if value.respond_to?(:to_time)
50
+ DateTime.parse(value).to_time
48
51
  end
49
52
 
50
- def date_value
51
- @value.to_date
53
+ def date_time_value(value)
54
+ return value.to_datetime if value.respond_to?(:to_datetime)
55
+ DateTime.parse(value)
52
56
  end
53
57
 
54
- def time_value
55
- @value.to_time
58
+ def true_class_value(value)
59
+ truthy_value?(value)
56
60
  end
57
61
 
58
- def date_time_value
59
- @value.to_datetime
62
+ def false_class_value(value)
63
+ truthy_value?(value)
60
64
  end
61
65
 
62
- def true_class_value
63
- truthy_value?
66
+ private
67
+
68
+ def truthy_value?(value)
69
+ value =~ /true/ || value === true
64
70
  end
65
71
 
66
- def false_class_value
67
- truthy_value?
72
+ def klass_sym
73
+ :"#{self_klass.name.underscore}_value"
68
74
  end
69
75
 
70
- def boolean?
71
- self_klass == TrueClass || self_klass == FalseClass
76
+ def self_klass
77
+ return unless class_name
78
+ return class_name if class_name.is_a?(Class)
79
+ Object.const_get class_name
72
80
  end
73
81
 
74
- private
75
- def truthy_value?
76
- @value =~ /true/ || @value === true
82
+ def set_default
83
+ return unless default_present?
84
+ return default unless cant_clone_default?
85
+ default.clone
77
86
  end
78
87
 
79
- def self_klass
80
- return unless @class_name
81
- @class_name.is_a?(Class)? @class_name : @class_name.constantize
88
+ def cant_clone_default?
89
+ CANT_CLONE.none? { |klass| default.is_a? klass }
82
90
  end
83
91
 
92
+ def default_present?
93
+ !default.nil?
94
+ end
84
95
  end
85
96
  end