dolly 1.1.7 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -0
  3. data/lib/dolly.rb +1 -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 +26 -69
  8. data/lib/dolly/configuration.rb +35 -10
  9. data/lib/dolly/connection.rb +91 -22
  10. data/lib/dolly/depracated_database.rb +24 -0
  11. data/lib/dolly/document.rb +32 -206
  12. data/lib/dolly/document_creation.rb +20 -0
  13. data/lib/dolly/document_state.rb +65 -0
  14. data/lib/dolly/document_type.rb +28 -0
  15. data/lib/dolly/exceptions.rb +21 -0
  16. data/lib/dolly/identity_properties.rb +29 -0
  17. data/lib/dolly/properties.rb +31 -0
  18. data/lib/dolly/property.rb +58 -47
  19. data/lib/dolly/property_manager.rb +47 -0
  20. data/lib/dolly/property_set.rb +18 -0
  21. data/lib/dolly/query.rb +39 -67
  22. data/lib/dolly/query_arguments.rb +35 -0
  23. data/lib/dolly/request.rb +12 -107
  24. data/lib/dolly/request_header.rb +26 -0
  25. data/lib/dolly/timestamp.rb +24 -0
  26. data/lib/dolly/version.rb +1 -1
  27. data/lib/{dolly → railties}/railtie.rb +2 -1
  28. data/lib/refinements/string_refinements.rb +28 -0
  29. data/lib/tasks/db.rake +4 -3
  30. data/test/bulk_document_test.rb +8 -5
  31. data/test/document_test.rb +137 -53
  32. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  33. data/test/dummy/log/test.log +46417 -46858
  34. data/test/test_helper.rb +14 -20
  35. metadata +42 -145
  36. data/Rakefile +0 -11
  37. data/lib/dolly/bulk_error.rb +0 -16
  38. data/lib/dolly/db_config.rb +0 -20
  39. data/lib/dolly/interpreter.rb +0 -5
  40. data/lib/dolly/logger.rb +0 -9
  41. data/lib/dolly/name_space.rb +0 -28
  42. data/lib/dolly/timestamps.rb +0 -21
  43. data/lib/exceptions/dolly.rb +0 -47
  44. data/test/collection_test.rb +0 -59
  45. data/test/configuration_test.rb +0 -9
  46. data/test/dummy/README.rdoc +0 -28
  47. data/test/dummy/Rakefile +0 -6
  48. data/test/dummy/app/assets/javascripts/application.js +0 -13
  49. data/test/dummy/app/assets/stylesheets/application.css +0 -13
  50. data/test/dummy/app/controllers/application_controller.rb +0 -5
  51. data/test/dummy/app/helpers/application_helper.rb +0 -2
  52. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  53. data/test/dummy/bin/bundle +0 -3
  54. data/test/dummy/bin/rails +0 -4
  55. data/test/dummy/bin/rake +0 -4
  56. data/test/dummy/config.ru +0 -4
  57. data/test/dummy/config/application.rb +0 -27
  58. data/test/dummy/config/boot.rb +0 -5
  59. data/test/dummy/config/couchdb.yml +0 -13
  60. data/test/dummy/config/environment.rb +0 -5
  61. data/test/dummy/config/environments/development.rb +0 -29
  62. data/test/dummy/config/environments/production.rb +0 -80
  63. data/test/dummy/config/environments/test.rb +0 -36
  64. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  65. data/test/dummy/config/initializers/inflections.rb +0 -16
  66. data/test/dummy/config/initializers/mime_types.rb +0 -5
  67. data/test/dummy/config/initializers/secret_token.rb +0 -12
  68. data/test/dummy/config/initializers/session_store.rb +0 -3
  69. data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  70. data/test/dummy/config/locales/en.yml +0 -23
  71. data/test/dummy/config/routes.rb +0 -56
  72. data/test/dummy/lib/couch_rest_adapter/railtie.rb +0 -10
  73. data/test/dummy/public/404.html +0 -58
  74. data/test/dummy/public/422.html +0 -58
  75. data/test/dummy/public/500.html +0 -57
  76. data/test/dummy/public/favicon.ico +0 -0
  77. data/test/factories/factories.rb +0 -8
  78. data/test/request_test.rb +0 -25
@@ -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,217 +1,43 @@
1
- require "dolly/query"
2
- require "dolly/property"
3
- require 'dolly/timestamps'
1
+ require 'dolly/query'
2
+ require 'dolly/connection'
3
+ require 'dolly/request'
4
+ require 'dolly/depracated_database'
5
+ require 'dolly/document_state'
6
+ require 'dolly/properties'
7
+ require 'dolly/identity_properties'
8
+ require 'dolly/attachment'
9
+ require 'dolly/property_manager'
10
+ require 'dolly/timestamp'
11
+ require 'dolly/query_arguments'
12
+ require 'dolly/document_creation'
13
+ require 'dolly/class_methods_delegation'
14
+ require 'refinements/string_refinements'
4
15
 
5
16
  module Dolly
6
17
  class Document
7
- extend Dolly::Connection
8
- include Dolly::Query
9
- extend Dolly::Timestamps
18
+ extend Query
19
+ extend Request
20
+ extend DepracatedDatabase
21
+ extend Properties
22
+ extend DocumentCreation
23
+ include PropertyManager
24
+ include Timestamp
25
+ include DocumentState
26
+ include IdentityProperties
27
+ include Attachment
28
+ include QueryArguments
29
+ include ClassMethodsDelegation
10
30
 
11
- attr_accessor :rows, :doc, :key
12
- class_attribute :properties
13
- cattr_accessor :timestamps do
14
- {}
15
- end
16
-
17
- def initialize options = {}
18
- @doc ||= {}
19
- options = options.with_indifferent_access
20
- init_properties options
21
- end
22
-
23
- def persisted?
24
- !doc['_rev'].blank?
25
- end
26
-
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
59
-
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 hard = true
78
- if hard
79
- q = id_as_resource + "?rev=#{rev}"
80
- response = database.delete(q)
81
- JSON::parse response.parsed_response
82
- else
83
- self.doc['_deleted'] = true
84
- self.save
85
- end
86
- end
87
-
88
- def rows= col
89
- raise Dolly::ResourceNotFound if col.empty?
90
- col.each{ |r| @doc = r['doc'] }
91
- _properties.each do |p|
92
- #TODO: Refactor properties so it is not required
93
- #to be a class property. But something that doesn't
94
- #persist all the inheretence chain
95
- next unless self.respond_to? :"#{p.name}="
96
- self.send "#{p.name}=", doc[p.name]
97
- end
98
- @rows = col
99
- end
100
-
101
- def from_json string
102
- parsed = JSON::parse( string )
103
- self.rows = parsed['rows']
104
- self
105
- end
106
-
107
- def database
108
- self.class.database
109
- end
110
-
111
- def id_as_resource
112
- CGI::escape id
113
- end
114
-
115
- def attach_file! file_name, mime_type, body, opts={}
116
- attach_file file_name, mime_type, body, opts
117
- save
118
- end
119
-
120
- def attach_file file_name, mime_type, body, opts={}
121
- if opts[:inline]
122
- attach_inline_file file_name, mime_type, body
123
- else
124
- attach_standalone_file file_name, mime_type, body
125
- end
126
- end
127
-
128
- def attach_inline_file file_name, mime_type, body
129
- attachment_data = { file_name.to_s => { 'content_type' => mime_type,
130
- 'data' => Base64.encode64(body)} }
131
- doc['_attachments'] ||= {}
132
- doc['_attachments'].merge! attachment_data
133
- end
134
-
135
- def attach_standalone_file file_name, mime_type, body
136
- database.attach id_as_resource, CGI.escape(file_name), body, { 'Content-Type' => mime_type }
137
- end
138
-
139
- def self.create options = {}
140
- obj = new options
141
- obj.save
142
- obj
143
- end
144
-
145
- def self.property *ary
146
- self.properties ||= {}
147
- options = ary.pop if ary.last.kind_of? Hash
148
- options ||= {}
31
+ attr_writer :doc
149
32
 
150
- ary.each do |name|
151
- self.properties[name] = Property.new options.merge(name: name)
152
- self.write_methods name
153
- end
33
+ def initialize attributes = {}
34
+ properties.each(&build_property(attributes))
154
35
  end
155
36
 
156
- private
157
- #TODO: create a PropertiesSet service object, to do all this
158
- def self.write_methods name
159
- property = properties[name]
160
- define_method(name) { read_property name }
161
- define_method("#{name}=") { |value| write_property name, value }
162
- define_method(:"#{name}?") { send name } if property.boolean?
163
- define_method("[]") {|n| send n.to_sym }
164
- end
37
+ protected
165
38
 
166
- def write_property name, value
167
- instance_variable_set(:"@#{name}", value)
168
- @doc[name.to_s] = value
169
- end
170
-
171
- def read_property name
172
- if instance_variable_get(:"@#{name}").nil?
173
- write_property name, (doc[name.to_s] || self.properties[name].value)
174
- end
175
- instance_variable_get(:"@#{name}")
176
- end
177
-
178
- def _properties
179
- self.properties.values
180
- end
181
-
182
- def init_properties options = {}
183
- raise Dolly::ResourceNotFound if options['error'] == 'not_found'
184
- options.each do |k, v|
185
- next unless respond_to? :"#{k}="
186
- send(:"#{k}=", v)
187
- end
188
- initialize_default_properties options if self.properties.present?
189
- init_doc options
190
- end
191
-
192
- def initialize_default_properties options
193
- _properties.reject { |property| options.keys.include? property.name }.each do |property|
194
- property_value = property.default.clone unless Dolly::Property::CANT_CLONE.any? { |klass| property.default.is_a? klass }
195
- property_value ||= property.default
196
- self.doc[property.name] ||= property_value
197
- end
198
- end
199
-
200
- def init_doc options
201
- self.doc ||= {}
202
- #TODO: define what will be the preference _id or id
203
- normalized_id = options[:_id] || options[:id]
204
- self.doc['_id'] = self.class.namespace( normalized_id ) if normalized_id
205
- end
206
-
207
- def valid_properties?(options)
208
- options.keys.any?{ |option| properties_include?(option.to_s) }
209
- end
210
-
211
- def properties_include? property
212
- _properties.map(&:name).include? property
39
+ def doc
40
+ @doc ||= {}
213
41
  end
214
-
215
- def valid?; true; end
216
42
  end
217
43
  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,65 @@
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
+ write_timestamps(persisted?)
10
+ after_save(connection.put(id, doc))
11
+ end
12
+
13
+ def save!
14
+ raise DocumentInvalidError unless valid?
15
+ save
16
+ end
17
+
18
+ def update_properties(properties)
19
+ properties.each(&update_attribute)
20
+ end
21
+
22
+ def update_properties!(properties)
23
+ update_properties(properties)
24
+ save!
25
+ end
26
+
27
+ def destroy is_hard = true
28
+ return connection.delete(id, rev) if is_hard
29
+ doc[:_deleted] = true
30
+ save
31
+ rescue Dolly::ResourceNotFound
32
+ nil
33
+ rescue Dolly::ServerError => error
34
+ raise error unless error.message =~ /conflict/
35
+ self.rev = self.class.safe_find(id)&.rev
36
+ return unless self.rev
37
+ destroy(is_hard)
38
+ end
39
+
40
+ def reload
41
+ reloaded_doc = self.class.find(id).send(:doc)
42
+ attributes = property_clean_doc(reloaded_doc)
43
+
44
+ attributes.each(&update_attribute)
45
+ end
46
+
47
+ def persisted?
48
+ return false unless doc[:_rev]
49
+ !doc[:_rev].empty?
50
+ end
51
+
52
+ def to_h
53
+ doc
54
+ end
55
+
56
+ def valid?
57
+ true
58
+ end
59
+
60
+ def after_save(response)
61
+ self.rev = response[:rev]
62
+ response[:ok]
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,28 @@
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(id)
17
+ 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
+ end
28
+ end
@@ -0,0 +1,21 @@
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 InvalidConfigFileError < RuntimeError; end
19
+ class InvalidProperty < RuntimeError; end
20
+ class DocumentInvalidError < RuntimeError; end
21
+ 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,31 @@
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
+ properties << (prop = Property.new(opt, class_name, default))
11
+ send(:attr_reader, opt)
12
+
13
+ define_method(:"#{opt}=") { |value| write_attribute(opt, value) }
14
+ define_method(:"#{opt}?") { send(opt) } if prop.boolean?
15
+ define_method(:"[]") {|name| send(name) }
16
+ end
17
+ end
18
+
19
+ def properties
20
+ @properties ||= PropertySet.new
21
+ end
22
+
23
+ def property_keys
24
+ properties.map(&:key) - SPECIAL_KEYS
25
+ end
26
+
27
+ def property_clean_doc(doc)
28
+ doc.reject { |key, _value| !property_keys.include?(key) }
29
+ end
30
+ end
31
+ end