dolly 1.1.5 → 3.1.0

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.
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
@@ -1,42 +1,106 @@
1
- require "dolly/request"
2
- require "dolly/name_space"
3
- require "dolly/db_config"
4
- require "dolly/bulk_document"
1
+ require 'curb'
2
+ require 'oj'
3
+ require 'cgi'
4
+ require 'net/http'
5
+ require 'dolly/request_header'
6
+ require 'dolly/exceptions'
7
+ require 'dolly/configuration'
8
+ require 'refinements/string_refinements'
5
9
 
6
10
  module Dolly
7
- module Connection
8
- include Dolly::NameSpace
9
- include Dolly::DbConfig
11
+ class Connection
12
+ include Dolly::Configuration
13
+ attr_reader :db, :app_env
10
14
 
11
- @@design_doc = nil
15
+ DEFAULT_HEADER = { 'Content-Type' => 'application/json', 'Accept' => 'application/json' }
16
+ SECURE_PROTOCOL = 'https'
17
+ DEFAULT_DATABASE = :default
12
18
 
13
- def database
14
- @database ||= Request.new(env)
19
+ using StringRefinements
20
+
21
+ def initialize db = DEFAULT_DATABASE, app_env = :development
22
+ @db = db
23
+ @app_env = app_env
24
+ end
25
+
26
+ def get(resource, data = {})
27
+ query = { query: values_to_json(data) } if data
28
+ request :get, resource, query
29
+ end
30
+
31
+ def post resource, data
32
+ request :post, resource.cgi_escape, data
33
+ end
34
+
35
+ def put resource, data
36
+ request :put, resource.cgi_escape, data
37
+ end
38
+
39
+ def delete resource, rev = nil, escape: true
40
+ query = "?rev=#{rev}" if rev
41
+ resource = "#{escape ? resource.cgi_escape : resource}#{query}"
42
+ request :delete, resource
15
43
  end
16
44
 
17
- def bulk_document
18
- @bulk_document ||= BulkDocument.new(database)
45
+ def view resource, opts
46
+ request :get, resource, query: values_to_json({include_docs: true}.merge!(opts))
19
47
  end
20
48
 
21
- def bulk_save
22
- bulk_document.save
49
+ def attach resource, attachment_name, data, headers = {}
50
+ request :put, "#{resource.cgi_escape}/#{attachment_name}", { _body: data }.merge(headers: headers)
23
51
  end
24
52
 
25
- def database_name value
26
- @@database_name ||= value
53
+ def uuids opts = {}
54
+ tools("_uuids", opts)[:uuids]
27
55
  end
28
56
 
29
- def default_doc
30
- "#{design_doc}/_view/find"
57
+ def stats
58
+ get("/#{db_name}")
31
59
  end
32
60
 
33
- def design_doc
34
- "_design/#{env["design"]}"
61
+ def tools path, opts = nil
62
+ request(:get, "/#{path}", opts)
35
63
  end
36
64
 
37
- def next_id
38
- namespace database.uuids.first
65
+ def request(method, resource, data = {})
66
+ headers = Dolly::HeaderRequest.new(data&.delete(:headers))
67
+ data&.merge!(data&.delete(:query) || {})
68
+ db_resource = (resource =~ %r{^/}) ? resource : "/#{db_name}/#{resource}"
69
+ uri = URI("#{base_uri}#{db_resource}")
70
+ conn = curl_method_call(method, uri, data) do |curl|
71
+ if env['username'].present?
72
+ curl.http_auth_types = :basic
73
+ curl.username = env['username']
74
+ curl.password = env['password'].to_s
75
+ end
76
+
77
+ headers.each { |k, v| curl.headers[k] = v } if headers.present?
78
+ end
79
+ response_format(conn, method)
39
80
  end
40
81
 
82
+ private
83
+
84
+ def curl_method_call(method, uri, data, &block)
85
+ return Curl::Easy.http_head(uri.to_s, &block) if method.to_sym == :head
86
+ return Curl.delete(uri.to_s, &block) if method.to_sym == :delete
87
+ return Curl.send(method, uri, data, &block) if method.to_sym == :get
88
+ Curl.send(method, uri.to_s, data.to_json, &block)
89
+ end
90
+
91
+ def response_format(res, method)
92
+ raise Dolly::ResourceNotFound if res.status.to_i == 404
93
+ raise Dolly::ServerError.new(res.status.to_i) if (400..600).include? res.status.to_i
94
+ return res.header_str if method == :head
95
+ Oj.load(res.body_str, symbol_keys: true)
96
+ end
97
+
98
+ def values_to_json hash
99
+ hash.each_with_object({}) { |(k,v), h| h[k] = v.is_a?(Numeric) ? v : v.to_json }
100
+ end
101
+
102
+ def to_query(string)
103
+ string.map { |k, v| "#{k}=#{v}" }.sort * '&'
104
+ end
41
105
  end
42
106
  end
@@ -0,0 +1,24 @@
1
+ module Dolly
2
+ module DepracatedDatabase
3
+ Database = Struct.new(:connection) do
4
+ def request *args
5
+ connection.request *args
6
+ end
7
+
8
+ def post *args
9
+ connection.post *args
10
+ end
11
+ end
12
+
13
+ def view *args
14
+ opts = args.pop if args.last.is_a? Hash
15
+ opts ||= {}
16
+ connection.view *args, opts.merge(include_docs: true)
17
+ end
18
+
19
+ def database
20
+ warn "[DEPRECATION] `database` is deprecated. Please use `connection` instead."
21
+ Database.new(connection)
22
+ end
23
+ end
24
+ end
@@ -1,213 +1,63 @@
1
- require "dolly/query"
2
- require "dolly/property"
3
- require 'dolly/timestamps'
1
+ require 'dolly/mango'
2
+ require 'dolly/mango_index'
3
+ require 'dolly/query'
4
+ require 'dolly/view_query'
5
+ require 'dolly/connection'
6
+ require 'dolly/request'
7
+ require 'dolly/depracated_database'
8
+ require 'dolly/document_state'
9
+ require 'dolly/properties'
10
+ require 'dolly/document_type'
11
+ require 'dolly/identity_properties'
12
+ require 'dolly/attachment'
13
+ require 'dolly/property_manager'
14
+ require 'dolly/timestamp'
15
+ require 'dolly/query_arguments'
16
+ require 'dolly/document_creation'
17
+ require 'dolly/class_methods_delegation'
18
+ require 'refinements/string_refinements'
4
19
 
5
20
  module Dolly
6
21
  class Document
7
- extend Dolly::Connection
8
- include Dolly::Query
9
- extend Dolly::Timestamps
22
+ extend Mango
23
+ extend Query
24
+ extend ViewQuery
25
+ extend Request
26
+ extend DepracatedDatabase
27
+ extend Properties
28
+ extend DocumentCreation
10
29
 
11
- attr_accessor :rows, :doc, :key
12
- class_attribute :properties
13
- cattr_accessor :timestamps do
14
- {}
15
- end
30
+ include DocumentType
31
+ include PropertyManager
32
+ include Timestamp
33
+ include DocumentState
34
+ include IdentityProperties
35
+ include Attachment
36
+ include QueryArguments
37
+ include ClassMethodsDelegation
16
38
 
17
- def initialize options = {}
18
- @doc ||= {}
19
- options = options.with_indifferent_access
20
- init_properties options
21
- end
39
+ attr_writer :doc
22
40
 
23
- def persisted?
24
- !doc['_rev'].blank?
41
+ def initialize(attributes = {})
42
+ init_ancestor_properties
43
+ properties.each(&build_property(attributes))
25
44
  end
26
45
 
27
- def update_properties options = {}
28
- raise InvalidProperty unless valid_properties?(options)
29
- options.each do |property, value|
30
- send(:"#{property}=", value)
31
- end
32
- end
33
-
34
- def update_properties! options = {}
35
- update_properties(options)
36
- save
37
- end
38
-
39
- def reload
40
- self.doc = self.class.find(id).doc
41
- end
42
-
43
- def id
44
- doc['_id'] ||= self.class.next_id
45
- end
46
-
47
- def id= base_value
48
- doc ||= {}
49
- doc['_id'] = self.class.namespace(base_value)
50
- end
51
-
52
- def rev
53
- doc['_rev']
54
- end
55
-
56
- def rev= value
57
- doc['_rev'] = value
58
- end
46
+ protected
59
47
 
60
- def save options = {}
61
- return false unless options[:validate] == false || valid?
62
- self.doc['_id'] = self.id if self.id.present?
63
- self.doc['_id'] = self.class.next_id if self.doc['_id'].blank?
64
- set_created_at if timestamps[self.class.name]
65
- set_updated_at if timestamps[self.class.name]
66
- response = database.put(id_as_resource, self.doc.to_json)
67
- obj = JSON::parse response.parsed_response
68
- doc['_rev'] = obj['rev'] if obj['rev']
69
- obj['ok']
70
- end
71
-
72
- def save!
73
- raise DocumentInvalidError unless valid?
74
- save
75
- end
76
-
77
- def destroy soft = false
78
- #TODO: Add soft delete support
79
- q = id_as_resource + "?rev=#{rev}"
80
- response = database.delete(q)
81
- JSON::parse response.parsed_response
82
- end
83
-
84
- def rows= col
85
- raise Dolly::ResourceNotFound if col.empty?
86
- col.each{ |r| @doc = r['doc'] }
87
- _properties.each do |p|
88
- #TODO: Refactor properties so it is not required
89
- #to be a class property. But something that doesn't
90
- #persist all the inheretence chain
91
- next unless self.respond_to? :"#{p.name}="
92
- self.send "#{p.name}=", doc[p.name]
93
- end
94
- @rows = col
95
- end
96
-
97
- def from_json string
98
- parsed = JSON::parse( string )
99
- self.rows = parsed['rows']
100
- self
101
- end
102
-
103
- def database
104
- self.class.database
105
- end
106
-
107
- def id_as_resource
108
- CGI::escape id
109
- end
110
-
111
- def attach_file! file_name, mime_type, body, opts={}
112
- attach_file file_name, mime_type, body, opts
113
- save
114
- end
115
-
116
- def attach_file file_name, mime_type, body, opts={}
117
- if opts[:inline]
118
- attach_inline_file file_name, mime_type, body
119
- else
120
- attach_standalone_file file_name, mime_type, body
121
- end
122
- end
123
-
124
- def attach_inline_file file_name, mime_type, body
125
- attachment_data = { file_name.to_s => { 'content_type' => mime_type,
126
- 'data' => Base64.encode64(body)} }
127
- doc['_attachments'] ||= {}
128
- doc['_attachments'].merge! attachment_data
129
- end
130
-
131
- def attach_standalone_file file_name, mime_type, body
132
- database.attach id_as_resource, CGI.escape(file_name), body, { 'Content-Type' => mime_type }
133
- end
134
-
135
- def self.create options = {}
136
- obj = new options
137
- obj.save
138
- obj
139
- end
140
-
141
- def self.property *ary
142
- self.properties ||= {}
143
- options = ary.pop if ary.last.kind_of? Hash
144
- options ||= {}
145
-
146
- ary.each do |name|
147
- self.properties[name] = Property.new options.merge(name: name)
148
- self.write_methods name
149
- end
150
- end
151
-
152
- private
153
- #TODO: create a PropertiesSet service object, to do all this
154
- def self.write_methods name
155
- property = properties[name]
156
- define_method(name) { read_property name }
157
- define_method("#{name}=") { |value| write_property name, value }
158
- define_method(:"#{name}?") { send name } if property.boolean?
159
- define_method("[]") {|n| send n.to_sym }
160
- end
161
-
162
- def write_property name, value
163
- instance_variable_set(:"@#{name}", value)
164
- @doc[name.to_s] = value
165
- end
166
-
167
- def read_property name
168
- if instance_variable_get(:"@#{name}").nil?
169
- write_property name, (doc[name.to_s] || self.properties[name].value)
170
- end
171
- instance_variable_get(:"@#{name}")
172
- end
173
-
174
- def _properties
175
- self.properties.values
176
- end
177
-
178
- def init_properties options = {}
179
- raise Dolly::ResourceNotFound if options['error'] == 'not_found'
180
- options.each do |k, v|
181
- next unless respond_to? :"#{k}="
182
- send(:"#{k}=", v)
183
- end
184
- initialize_default_properties options if self.properties.present?
185
- init_doc options
48
+ def doc
49
+ @doc ||= {}
186
50
  end
187
51
 
188
- def initialize_default_properties options
189
- _properties.reject { |property| options.keys.include? property.name }.each do |property|
190
- property_value = property.default.clone unless Dolly::Property::CANT_CLONE.any? { |klass| property.default.is_a? klass }
191
- property_value ||= property.default
192
- self.doc[property.name] ||= property_value
52
+ def init_ancestor_properties
53
+ self.class.ancestors.map do |ancestor|
54
+ begin
55
+ ancestor.properties.entries.each do |property|
56
+ properties << property
57
+ end
58
+ rescue NoMethodError => e
59
+ end
193
60
  end
194
61
  end
195
-
196
- def init_doc options
197
- self.doc ||= {}
198
- #TODO: define what will be the preference _id or id
199
- normalized_id = options[:_id] || options[:id]
200
- self.doc['_id'] = self.class.namespace( normalized_id ) if normalized_id
201
- end
202
-
203
- def valid_properties?(options)
204
- options.keys.any?{ |option| properties_include?(option.to_s) }
205
- end
206
-
207
- def properties_include? property
208
- _properties.map(&:name).include? property
209
- end
210
-
211
- def valid?; true; end
212
62
  end
213
63
  end
@@ -0,0 +1,20 @@
1
+ require 'dolly/properties'
2
+
3
+ module Dolly
4
+ module DocumentCreation
5
+ include Properties
6
+
7
+ def from_doc(doc)
8
+ attributes = property_clean_doc(doc)
9
+ new(attributes).tap { |model| model.doc = doc }
10
+ end
11
+
12
+ def from_json(json)
13
+ from_doc(Oj.load(json, symbol_keys: true))
14
+ end
15
+
16
+ def create(attributes)
17
+ new(attributes).tap { |model| model.save }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,66 @@
1
+ require 'dolly/class_methods_delegation'
2
+
3
+ module Dolly
4
+ module DocumentState
5
+ include ClassMethodsDelegation
6
+
7
+ def save(options = {})
8
+ return false unless options[:validate] == false || valid?
9
+ set_type if typed? && type.nil?
10
+ write_timestamps(persisted?)
11
+ after_save(connection.put(id, doc))
12
+ end
13
+
14
+ def save!
15
+ raise DocumentInvalidError unless valid?
16
+ save
17
+ end
18
+
19
+ def update_properties(properties)
20
+ properties.each(&update_attribute)
21
+ end
22
+
23
+ def update_properties!(properties)
24
+ update_properties(properties)
25
+ save!
26
+ end
27
+
28
+ def destroy is_hard = true
29
+ return connection.delete(id, rev) if is_hard
30
+ doc[:_deleted] = true
31
+ save
32
+ rescue Dolly::ResourceNotFound
33
+ nil
34
+ rescue Dolly::ServerError => error
35
+ raise error unless error.message =~ /conflict/
36
+ self.rev = self.class.safe_find(id)&.rev
37
+ return unless self.rev
38
+ destroy(is_hard)
39
+ end
40
+
41
+ def reload
42
+ reloaded_doc = self.class.find(id).send(:doc)
43
+ attributes = property_clean_doc(reloaded_doc)
44
+
45
+ attributes.each(&update_attribute)
46
+ end
47
+
48
+ def persisted?
49
+ return false unless doc[:_rev]
50
+ !doc[:_rev].empty?
51
+ end
52
+
53
+ def to_h
54
+ doc
55
+ end
56
+
57
+ def valid?
58
+ true
59
+ end
60
+
61
+ def after_save(response)
62
+ self.rev = response[:rev]
63
+ response[:ok]
64
+ end
65
+ end
66
+ end