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