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,23 @@
1
+ require 'test_helper'
2
+
3
+ class BaseDoc < Dolly::Document
4
+ typed_model
5
+ end
6
+
7
+ class BaseBaseDoc < BaseDoc
8
+ property :supertype
9
+ end
10
+
11
+ class NewBar < BaseBaseDoc
12
+ property :a, :b
13
+ end
14
+
15
+ class InheritanceTest < Test::Unit::TestCase
16
+ test 'property inheritance' do
17
+ assert_equal(BaseBaseDoc.new.properties.map(&:key), [:supertype, :type])
18
+ end
19
+
20
+ test 'deep properties inheritance' do
21
+ assert_equal(NewBar.new.properties.map(&:key), [:a, :b, :supertype, :type])
22
+ end
23
+ end
@@ -0,0 +1,64 @@
1
+ require 'test_helper'
2
+
3
+ class FooBar < BaseDolly
4
+ property :foo, :bar
5
+ property :with_default, default: 1
6
+ property :boolean, class_name: TrueClass, default: true
7
+ property :date, class_name: Date
8
+ property :time, class_name: Time
9
+ property :datetime, class_name: DateTime
10
+ property :is_nil, class_name: NilClass, default: nil
11
+
12
+ timestamps!
13
+ end
14
+
15
+ class MangoIndexTest < Test::Unit::TestCase
16
+ DB_BASE_PATH = "http://localhost:5984/test".freeze
17
+
18
+ def setup
19
+ stub_request(:get, index_base_path).
20
+ to_return(body: { indexes:[ {
21
+ ddoc: nil,
22
+ name:"_all_docs",
23
+ type:"special",
24
+ def:{ fields:[{ _id:"asc" }] }
25
+ },
26
+ {
27
+ ddoc: "_design/1",
28
+ name:"foo-index-json",
29
+ type:"json",
30
+ def:{ fields:[{ foo:"asc" }] }
31
+ }
32
+ ]}.to_json)
33
+ end
34
+
35
+ test '#delete_all' do
36
+ previous_indexes = Dolly::MangoIndex.all
37
+
38
+ stub_request(:delete, index_delete_path(previous_indexes.last)).
39
+ to_return(body: { "ok": true }.to_json)
40
+
41
+ Dolly::MangoIndex.delete_all
42
+
43
+ stub_request(:get, index_base_path).
44
+ to_return(body: { indexes:[ {
45
+ ddoc: nil,
46
+ name:"_all_docs",
47
+ type:"special",
48
+ def:{ fields:[{ _id:"asc" }] }
49
+ }
50
+ ]}.to_json)
51
+
52
+ new_indexes = Dolly::MangoIndex.all
53
+ assert_not_equal(new_indexes.length, previous_indexes.length)
54
+ assert_equal(new_indexes.length, 1)
55
+ end
56
+
57
+ def index_base_path
58
+ "#{DB_BASE_PATH}/_index"
59
+ end
60
+
61
+ def index_delete_path(doc)
62
+ "#{index_base_path}/#{doc[:ddoc]}/json/#{doc[:name]}"
63
+ end
64
+ end
@@ -0,0 +1,273 @@
1
+ require 'test_helper'
2
+
3
+ class FooBar < BaseDolly
4
+ property :foo, :bar
5
+ property :with_default, default: 1
6
+ property :boolean, class_name: TrueClass, default: true
7
+ property :date, class_name: Date
8
+ property :time, class_name: Time
9
+ property :datetime, class_name: DateTime
10
+ property :is_nil, class_name: NilClass, default: nil
11
+
12
+ timestamps!
13
+ end
14
+
15
+ class FooBarTyped < BaseDolly
16
+ typed_model
17
+ end
18
+
19
+ class MangoTest < Test::Unit::TestCase
20
+ DB_BASE_PATH = "http://localhost:5984/test".freeze
21
+
22
+ def setup
23
+ data = {foo: 'Foo', bar: 'Bar', type: 'foo_bar'}
24
+
25
+ all_docs = [ {foo: 'Foo B', bar: 'Bar B', type: 'foo_bar'}, {foo: 'Foo A', bar: 'Bar A', type: 'foo_bar'}]
26
+
27
+ view_resp = build_view_response [data]
28
+ empty_resp = build_view_response []
29
+ not_found_resp = generic_response [{ key: "foo_bar/2", error: "not_found" }]
30
+ @multi_resp = build_view_response all_docs
31
+ @multi_type_resp = build_view_collation_response all_docs
32
+
33
+ build_request [["foo_bar","1"]], view_resp
34
+ build_request [["foo_bar","2"]], empty_resp
35
+ build_request [["foo_bar","1"],["foo_bar","2"]], @multi_resp
36
+
37
+ stub_request(:get, "#{query_base_path}?startkey=%22foo_bar%2F%22&endkey=%22foo_bar%2F%EF%BF%B0%22&include_docs=true").
38
+ to_return(body: @multi_resp.to_json)
39
+
40
+ stub_request(:get, "#{all_docs_path}?key=\"index_foo\"").
41
+ to_return(body: {
42
+ total_rows: 2,
43
+ offset: 0,
44
+ rows: [{
45
+ id: '_design/index_foo',
46
+ key: '_design/index_foo',
47
+ value: { rev: '1-c5457a0d26da85f15c4ad6bd739e441d' }
48
+ }]}.to_json)
49
+
50
+ stub_request(:get, "#{all_docs_path}?key=\"index_date\"").
51
+ to_return(body: {
52
+ total_rows: 2,
53
+ offset: 0,
54
+ rows: []}.to_json)
55
+ end
56
+
57
+ test '#find_by' do
58
+ #TODO: clean up all the fake request creation
59
+ resp = { docs: [{ foo: 'bar', id: "foo_bar/1"} ] }
60
+
61
+ stub_request(:post, query_base_path).
62
+ to_return(body: resp.to_json)
63
+
64
+ key = 'foo'
65
+ stub_request(:get, "#{all_docs_path}?key=\"_design/index_#{key}\"").
66
+ to_return(body: index_response(key).to_json)
67
+
68
+ assert_equal(FooBar.find_by(foo: 'bar').class, FooBar)
69
+ end
70
+
71
+ test '#find_by for a property that does not have an index' do
72
+ #TODO: clean up all the fake request creation
73
+ resp = { docs: [{ foo: 'bar', id: "foo_bar/1"} ] }
74
+ key = 'date'
75
+
76
+ stub_request(:post, query_base_path).
77
+ to_return(body: resp.to_json)
78
+
79
+ stub_request(:get, "#{all_docs_path}?key=\"_design/index_#{key}\"").
80
+ to_return(body: { rows: [] }.to_json)
81
+
82
+ assert_raise Dolly::IndexNotFoundError do
83
+ FooBar.find_by(date: Date.today)
84
+ end
85
+ end
86
+
87
+ test '#find_by with no returned data' do
88
+ resp = { docs: [] }
89
+
90
+ stub_request(:post, query_base_path).
91
+ to_return(body: resp.to_json)
92
+
93
+ key = 'foo'
94
+ stub_request(:get, "#{all_docs_path}?key=\"_design/index_#{key}\"").
95
+ to_return(body: index_response(key).to_json)
96
+
97
+ assert_equal(FooBar.find_by(foo: 'bar'), nil)
98
+ end
99
+
100
+ test '#find_doc_by' do
101
+ #TODO: clean up all the fake request creation
102
+ resp = { docs: [{ foo: 'bar', id: "foo_bar/1"} ] }
103
+
104
+ stub_request(:post, query_base_path).
105
+ to_return(body: resp.to_json)
106
+
107
+ key = 'foo'
108
+ stub_request(:get, "#{all_docs_path}?key=\"_design/index_#{key}\"").
109
+ to_return(body: index_response(key).to_json)
110
+
111
+ assert_equal(FooBar.find_doc_by(foo: 'bar').class, Hash)
112
+ end
113
+
114
+ test '#where' do
115
+ #TODO: clean up all the fake request creation
116
+ resp = { docs: [{ foo: 'bar', id: "foo_bar/1"} ] }
117
+
118
+ stub_request(:post, query_base_path).
119
+ to_return(body: resp.to_json)
120
+
121
+ key = 'foo'
122
+ stub_request(:get, "#{all_docs_path}?key=\"_design/index_#{key}\"").
123
+ to_return(body: index_response(key).to_json)
124
+
125
+ assert_equal(FooBar.where(foo: { eq: 'bar' }).map(&:class).uniq, [FooBar])
126
+ end
127
+
128
+ test '#where for a property that does not have an index' do
129
+ #TODO: clean up all the fake request creation
130
+ resp = { docs: [{ foo: 'bar', id: "foo_bar/1"} ] }
131
+
132
+ stub_request(:post, query_base_path).
133
+ to_return(body: resp.to_json)
134
+
135
+ key = 'date'
136
+ stub_request(:get, "#{all_docs_path}?key=\"_design/index_#{key}\"").
137
+ to_return(body: { rows: [] }.to_json)
138
+
139
+ assert_raise Dolly::IndexNotFoundError do
140
+ FooBar.where(date: Date.today)
141
+ end
142
+ end
143
+
144
+ test '#where with no returned data' do
145
+ resp = { docs: [] }
146
+
147
+ stub_request(:post, query_base_path).
148
+ to_return(body: resp.to_json)
149
+
150
+ key = 'foo'
151
+ stub_request(:get, "#{all_docs_path}?key=\"_design/index_#{key}\"").
152
+ to_return(body: index_response(key).to_json)
153
+
154
+ assert_equal(FooBar.where(foo: 'bar'), [])
155
+ end
156
+
157
+ test '#docs_where' do
158
+ #TODO: clean up all the fake request creation
159
+ resp = { docs: [{ foo: 'bar', id: "foo_bar/1"} ] }
160
+
161
+ stub_request(:post, query_base_path).
162
+ to_return(body: resp.to_json)
163
+
164
+ key = 'foo'
165
+ stub_request(:get, "#{all_docs_path}?key=\"_design/index_#{key}\"").
166
+ to_return(body: index_response(key).to_json)
167
+
168
+ assert_equal(FooBar.docs_where(foo: { eq: 'bar' }).map(&:class).uniq, [Hash])
169
+ end
170
+
171
+ test '#build_query' do
172
+ query = { and: [{ _id: { eq: 'foo_bar/1' } } , { foo: { eq: 'bar'}} ] }
173
+ opts = {}
174
+ expected = {"selector"=>{"$and"=>[{:_id=>{"$eq"=>"foo_bar/1"}}, {:foo=>{"$eq"=>"bar"}}]}}
175
+
176
+ assert_equal(FooBar.send(:build_query, query, opts), expected)
177
+ end
178
+
179
+ test '#build_query with options' do
180
+ query = { and: [{ _id: { eq: 'foo_bar/1' } } , { foo: { eq: 'bar'}} ] }
181
+ opts = { limit: 1, fields: ['foo']}
182
+ expected = {"selector"=>{"$and"=>[{:_id=>{"$eq"=>"foo_bar/1"}}, {:foo=>{"$eq"=>"bar"}}]}, limit: 1, fields: ['foo']}
183
+
184
+ assert_equal(FooBar.send(:build_query, query, opts), expected)
185
+ end
186
+
187
+ test '#build_selectors with invalid operator' do
188
+ query = { _id: { eeeq: 'foo_bar/1' } }
189
+
190
+ assert_raise Dolly::InvalidMangoOperatorError do
191
+ FooBar.send(:build_selectors, query)
192
+ end
193
+ end
194
+
195
+ test '#build_selectors with type operator' do
196
+ query = { _id: { type: "user" } }
197
+
198
+ assert_nothing_raised Dolly::InvalidMangoOperatorError do
199
+ FooBarTyped.send(:build_selectors, query)
200
+ end
201
+ end
202
+
203
+ test '#build_selectors with $type operator' do
204
+ query = { _id: { "$type" => "null" } }
205
+
206
+ assert_nothing_raised Dolly::InvalidMangoOperatorError do
207
+ FooBarTyped.send(:build_selectors, query)
208
+ end
209
+ end
210
+
211
+ private
212
+
213
+ def generic_response rows, count = 1
214
+ {total_rows: count, offset:0, rows: rows}
215
+ end
216
+
217
+ def build_view_response properties
218
+ rows = properties.map.with_index do |v, i|
219
+ {
220
+ id: "foo_bar/#{i}",
221
+ key: "foo_bar",
222
+ value: 1,
223
+ doc: {_id: "foo_bar/#{i}", _rev: SecureRandom.hex}.merge!(v)
224
+ }
225
+ end
226
+ generic_response rows, properties.count
227
+ end
228
+
229
+ def build_view_collation_response properties
230
+ rows = properties.map.with_index do |v, i|
231
+ id = i.zero? ? "foo_bar/#{i}" : "baz/#{i}"
232
+ {
233
+ id: id,
234
+ key: "foo_bar",
235
+ value: 1,
236
+ doc: {_id: id, _rev: SecureRandom.hex}.merge!(v)
237
+ }
238
+ end
239
+ generic_response rows, properties.count
240
+ end
241
+
242
+
243
+ def build_request keys, body, view_name = 'foo_bar'
244
+ query = "keys=#{CGI::escape keys.to_s.gsub(' ','')}&" unless keys&.empty?
245
+ stub_request(:get, "#{query_base_path}?#{query.to_s}include_docs=true").
246
+ to_return(body: body.to_json)
247
+ end
248
+
249
+ def query_base_path
250
+ "#{DB_BASE_PATH}/_find"
251
+ end
252
+
253
+ def all_docs_path
254
+ "#{DB_BASE_PATH}/_all_docs"
255
+ end
256
+
257
+ def build_save_request(obj)
258
+ stub_request(:put, "#{DB_BASE_PATH}/#{CGI.escape(obj.id)}").
259
+ to_return(body: {ok: true, id: obj.id, rev: "FF0000" }.to_json)
260
+ end
261
+
262
+ def index_response(key)
263
+ {
264
+ rows: [
265
+ {
266
+ id: "_design/index_#{key}",
267
+ key: "_design/index_#{key}",
268
+ value: { rev: '1-c5457a0d26da85f15c4ad6bd739e441d' }
269
+ }
270
+ ]
271
+ }
272
+ end
273
+ end
@@ -1,37 +1,82 @@
1
- # Configure Rails Environment
2
- ENV["RAILS_ENV"] = "test"
1
+ require 'test/unit'
2
+ require 'webmock/test_unit'
3
+ require 'mocha/test_unit'
4
+ require 'securerandom'
3
5
 
4
- require 'fakeweb'
5
- require File.expand_path("../dummy/config/environment.rb", __FILE__)
6
- require "rails/test_help"
7
-
8
- Rails.backtrace_cleaner.remove_silencers!
6
+ # Load gem files
7
+ Dir["#{File.dirname(__FILE__)}/../lib/dolly/**/*.rb"].each { |f| require f }
8
+ Dir["#{File.dirname(__FILE__)}/../lib/refinements/**/*.rb"].each { |f| require f }
9
+ Dir["#{File.dirname(__FILE__)}/../dolly.rb"].each { |f| require f }
9
10
 
10
11
  # Load support files
11
12
  Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
12
13
 
13
- # Load fixtures from the engine
14
- if ActiveSupport::TestCase.method_defined?(:fixture_path=)
15
- ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
16
- end
17
-
18
-
19
- class ActiveSupport::TestCase
14
+ class Test::Unit::TestCase
20
15
  DEFAULT_DB = 'test'
16
+ DB_BASE_PATH = "http://localhost:5984/test".freeze
21
17
 
22
18
  setup :global_setup
23
19
 
24
20
  def global_setup
25
- FakeWeb.allow_net_connect = false
21
+ response = { uuids: [SecureRandom.uuid] }.to_json
26
22
 
27
- response = { uuids: [SecureRandom.uuid] }
28
- FakeWeb.register_uri(:get, %r|http://.*:5984/_uuids.*|, body: response.to_json)
23
+ stub_request(:get, %r{http://.*:5984/_uuids.*}).
24
+ to_return(status: 200, body: response, headers: { 'Content-Type': 'application/json'})
29
25
  end
30
26
 
31
27
  protected
28
+
32
29
  def base_path
33
- %r|http://.*:5984/#{DEFAULT_DB}|
30
+ %r{http://.*:5984/#{DEFAULT_DB}}
31
+ end
32
+
33
+ def generic_response rows, count = 1
34
+ {total_rows: count, offset:0, rows: rows}
35
+ end
36
+
37
+ def build_view_response properties
38
+ rows = properties.map.with_index do |v, i|
39
+ {
40
+ id: "foo_bar/#{i}",
41
+ key: "foo_bar",
42
+ value: 1,
43
+ doc: {_id: "foo_bar/#{i}", _rev: SecureRandom.hex}.merge!(v)
44
+ }
45
+ end
46
+ generic_response rows, properties.count
34
47
  end
35
48
 
49
+ def build_view_collation_response properties
50
+ rows = properties.map.with_index do |v, i|
51
+ id = i.zero? ? "foo_bar/#{i}" : "baz/#{i}"
52
+ {
53
+ id: id,
54
+ key: "foo_bar",
55
+ value: 1,
56
+ doc_type: id.split("/").first,
57
+ doc: {
58
+ _id: id, _rev: SecureRandom.hex
59
+ }.merge!(v)
60
+ }
61
+ end
62
+ generic_response rows, properties.count
63
+ end
64
+
65
+
66
+ def build_request keys, body, view_name = 'foo_bar'
67
+ query = "keys=#{CGI::escape keys.to_s.gsub(' ','')}&" unless keys&.empty?
68
+ stub_request(:get, "#{query_base_path}?#{query.to_s}include_docs=true").
69
+ to_return(body: body.to_json)
70
+ end
71
+
72
+ def query_base_path
73
+ "#{DB_BASE_PATH}/_all_docs"
74
+ end
75
+
76
+ def build_save_request(obj)
77
+ stub_request(:put, "#{DB_BASE_PATH}/#{CGI.escape(obj.id)}").
78
+ to_return(body: {ok: true, id: obj.id, rev: "FF0000" }.to_json)
79
+ end
36
80
  end
37
81
 
82
+ class BaseDolly < Dolly::Document; end