dolly 1.1.4 → 3.0.1

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 (84) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +78 -0
  3. data/lib/dolly.rb +4 -2
  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 +43 -0
  9. data/lib/dolly/connection.rb +101 -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 +124 -0
  18. data/lib/dolly/mango_index.rb +65 -0
  19. data/lib/dolly/properties.rb +36 -0
  20. data/lib/dolly/property.rb +58 -47
  21. data/lib/dolly/property_manager.rb +47 -0
  22. data/lib/dolly/property_set.rb +23 -0
  23. data/lib/dolly/query.rb +37 -67
  24. data/lib/dolly/query_arguments.rb +35 -0
  25. data/lib/dolly/request.rb +12 -94
  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 +14 -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 +20 -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 +111132 -0
  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/test_helper.rb +63 -18
  43. data/test/view_query_test.rb +27 -0
  44. metadata +57 -141
  45. data/Rakefile +0 -11
  46. data/lib/dolly/bulk_error.rb +0 -16
  47. data/lib/dolly/db_config.rb +0 -20
  48. data/lib/dolly/interpreter.rb +0 -5
  49. data/lib/dolly/name_space.rb +0 -28
  50. data/lib/dolly/timestamps.rb +0 -21
  51. data/lib/exceptions/dolly.rb +0 -38
  52. data/test/collection_test.rb +0 -59
  53. data/test/dummy/README.rdoc +0 -28
  54. data/test/dummy/Rakefile +0 -6
  55. data/test/dummy/app/assets/javascripts/application.js +0 -13
  56. data/test/dummy/app/assets/stylesheets/application.css +0 -13
  57. data/test/dummy/app/controllers/application_controller.rb +0 -5
  58. data/test/dummy/app/helpers/application_helper.rb +0 -2
  59. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  60. data/test/dummy/bin/bundle +0 -3
  61. data/test/dummy/bin/rails +0 -4
  62. data/test/dummy/bin/rake +0 -4
  63. data/test/dummy/config.ru +0 -4
  64. data/test/dummy/config/application.rb +0 -27
  65. data/test/dummy/config/boot.rb +0 -5
  66. data/test/dummy/config/couchdb.yml +0 -13
  67. data/test/dummy/config/environment.rb +0 -5
  68. data/test/dummy/config/environments/development.rb +0 -29
  69. data/test/dummy/config/environments/production.rb +0 -80
  70. data/test/dummy/config/environments/test.rb +0 -36
  71. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  72. data/test/dummy/config/initializers/inflections.rb +0 -16
  73. data/test/dummy/config/initializers/mime_types.rb +0 -5
  74. data/test/dummy/config/initializers/secret_token.rb +0 -12
  75. data/test/dummy/config/initializers/session_store.rb +0 -3
  76. data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  77. data/test/dummy/config/locales/en.yml +0 -23
  78. data/test/dummy/config/routes.rb +0 -56
  79. data/test/dummy/lib/couch_rest_adapter/railtie.rb +0 -10
  80. data/test/dummy/public/404.html +0 -58
  81. data/test/dummy/public/422.html +0 -58
  82. data/test/dummy/public/500.html +0 -57
  83. data/test/dummy/public/favicon.ico +0 -0
  84. data/test/factories/factories.rb +0 -8
@@ -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
@@ -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