couch_potato 1.7.1 → 1.9.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.
- checksums.yaml +5 -5
- data/.github/workflows/ruby.yml +38 -0
- data/.gitignore +3 -0
- data/CHANGES.md +169 -131
- data/Gemfile +4 -0
- data/README.md +55 -75
- data/Rakefile +11 -10
- data/couch_potato-rspec.gemspec +2 -1
- data/couch_potato.gemspec +8 -6
- data/gemfiles/active_support_5_0 +1 -5
- data/gemfiles/active_support_5_1 +7 -0
- data/gemfiles/active_support_5_2 +7 -0
- data/gemfiles/active_support_6_0 +7 -0
- data/gemfiles/active_support_6_1 +7 -0
- data/lib/couch_potato/database.rb +165 -70
- data/lib/couch_potato/persistence/dirty_attributes.rb +3 -21
- data/lib/couch_potato/persistence/magic_timestamps.rb +3 -3
- data/lib/couch_potato/persistence/properties.rb +15 -10
- data/lib/couch_potato/persistence/simple_property.rb +0 -4
- data/lib/couch_potato/persistence/type_caster.rb +9 -6
- data/lib/couch_potato/persistence.rb +0 -1
- data/lib/couch_potato/railtie.rb +6 -11
- data/lib/couch_potato/validation.rb +8 -0
- data/lib/couch_potato/version.rb +1 -1
- data/lib/couch_potato/view/base_view_spec.rb +8 -32
- data/lib/couch_potato/view/custom_views.rb +4 -3
- data/lib/couch_potato/view/flex_view_spec.rb +121 -0
- data/lib/couch_potato/view/view_parameters.rb +34 -0
- data/lib/couch_potato.rb +32 -9
- data/spec/callbacks_spec.rb +45 -19
- data/spec/conflict_handling_spec.rb +0 -1
- data/spec/property_spec.rb +2 -2
- data/spec/railtie_spec.rb +10 -0
- data/spec/spec_helper.rb +4 -3
- data/spec/unit/active_model_compliance_spec.rb +7 -3
- data/spec/unit/attributes_spec.rb +1 -1
- data/spec/unit/caching_spec.rb +105 -0
- data/spec/unit/couch_potato_spec.rb +70 -5
- data/spec/unit/create_spec.rb +5 -4
- data/spec/unit/database_spec.rb +235 -135
- data/spec/unit/dirty_attributes_spec.rb +5 -26
- data/spec/unit/flex_view_spec_spec.rb +17 -0
- data/spec/unit/model_view_spec_spec.rb +1 -1
- data/spec/unit/rspec_stub_db_spec.rb +31 -0
- data/spec/unit/validation_spec.rb +42 -2
- data/spec/views_spec.rb +214 -103
- data/vendor/pouchdb-collate/LICENSE +202 -0
- data/vendor/pouchdb-collate/pouchdb-collate.js +430 -0
- metadata +46 -42
- data/.ruby-version +0 -1
- data/.travis.yml +0 -21
- data/gemfiles/active_support_4_0 +0 -11
- data/gemfiles/active_support_4_1 +0 -11
- data/gemfiles/active_support_4_2 +0 -11
- data/lib/couch_potato/persistence/deep_dirty_attributes.rb +0 -180
- data/spec/unit/deep_dirty_attributes_spec.rb +0 -434
data/README.md
CHANGED
@@ -8,7 +8,6 @@
|
|
8
8
|
|
9
9
|
[](https://codeclimate.com/github/langalex/couch_potato)
|
10
10
|
|
11
|
-
|
12
11
|
### Mission
|
13
12
|
|
14
13
|
The goal of Couch Potato is to create a minimal framework in order to store and retrieve Ruby objects to/from CouchDB and create and query views.
|
@@ -21,9 +20,9 @@ Lastly Couch Potato aims to provide a seamless integration with Ruby on Rails, e
|
|
21
20
|
|
22
21
|
### Core Features
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
- persisting objects by including the CouchPotato::Persistence module
|
24
|
+
- declarative views with either custom or generated map/reduce functions
|
25
|
+
- extensive spec suite
|
27
26
|
|
28
27
|
### Supported Environments
|
29
28
|
|
@@ -81,6 +80,13 @@ Another switch allows you to store each CouchDB view in its own design document.
|
|
81
80
|
CouchPotato::Config.split_design_documents_per_view = true
|
82
81
|
```
|
83
82
|
|
83
|
+
If you are using more than one database from your app, you can create aliases:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
CouchPotato::Config.additional_databases = {'db1' => 'db1_production', 'db2' => 'https://db2.example.com/db'}
|
87
|
+
db1 = CouchPotato.use 'db1'
|
88
|
+
```
|
89
|
+
|
84
90
|
#### Using with Rails
|
85
91
|
|
86
92
|
Create a `config/couchdb.yml`:
|
@@ -90,6 +96,7 @@ default: &default
|
|
90
96
|
split_design_documents_per_view: true # optional, default is false
|
91
97
|
digest_view_names: true # optional, default is false
|
92
98
|
default_language: :erlang # optional, default is javascript
|
99
|
+
database_host: "http://127.0.0.1:5984"
|
93
100
|
|
94
101
|
development:
|
95
102
|
<<: *default
|
@@ -100,9 +107,12 @@ test:
|
|
100
107
|
production:
|
101
108
|
<<: *default
|
102
109
|
database: <%= ENV['DB_NAME'] %>
|
110
|
+
additional_databases:
|
111
|
+
db1: db1_production
|
112
|
+
db2: https://db2.example.com/db
|
103
113
|
```
|
104
114
|
|
105
|
-
#### Rails
|
115
|
+
#### Rails
|
106
116
|
|
107
117
|
Add to your `Gemfile`:
|
108
118
|
|
@@ -173,7 +183,6 @@ end
|
|
173
183
|
|
174
184
|
With this in place when you set the user's age as a String (e.g. using an HTML form) it will be converted into a `Integer` automatically.
|
175
185
|
|
176
|
-
|
177
186
|
Properties can have a default value:
|
178
187
|
|
179
188
|
```ruby
|
@@ -210,6 +219,20 @@ end
|
|
210
219
|
|
211
220
|
When a conflict occurs Couch Potato automatically reloads the document, runs the block and tries to save it again. Note that the block is also run before initally saving the document.
|
212
221
|
|
222
|
+
#### Caching load reqeusts
|
223
|
+
|
224
|
+
You can add a cache to a database instance to enable caching subsequent `#load` calls to the same id.
|
225
|
+
Any write operation will completely clear the cache.
|
226
|
+
|
227
|
+
```ruby
|
228
|
+
db = CouchPotato.database
|
229
|
+
db.cache = {}
|
230
|
+
db.load '1'
|
231
|
+
db.load '1' # goes to the cache instead of to the database
|
232
|
+
```
|
233
|
+
|
234
|
+
In web apps, the idea is to use a per request cache, i.e. set a new cache for every request.
|
235
|
+
|
213
236
|
#### Operations on multiple documents
|
214
237
|
|
215
238
|
You can also load a bunch of documents with one request.
|
@@ -252,7 +275,7 @@ end
|
|
252
275
|
|
253
276
|
#### Dirty tracking
|
254
277
|
|
255
|
-
CouchPotato tracks the dirty state of attributes in the same way ActiveRecord does
|
278
|
+
CouchPotato tracks the dirty state of attributes in the same way ActiveRecord does. Models are always saved though to avoid missing updates when multiple processes update the same documents concurrently.
|
256
279
|
|
257
280
|
```ruby
|
258
281
|
user = User.create :name => 'joe'
|
@@ -261,68 +284,6 @@ user.name_changed? # => false
|
|
261
284
|
user.name_was # => nil
|
262
285
|
```
|
263
286
|
|
264
|
-
You can also force a dirty state:
|
265
|
-
|
266
|
-
```ruby
|
267
|
-
user.name = 'jane'
|
268
|
-
user.name_changed? # => true
|
269
|
-
user.name_not_changed
|
270
|
-
user.name_changed? # => false
|
271
|
-
CouchPotato.database.save_document user # does nothing as no attributes are dirty
|
272
|
-
```
|
273
|
-
|
274
|
-
#### Optional Deep Dirty Tracking
|
275
|
-
|
276
|
-
In addition to standard dirty tracking, you can opt-in to more advanced dirty tracking for deeply structured documents by including the `CouchPotato::DeepDirtyAttributes` module in your models. This provides two benefits:
|
277
|
-
|
278
|
-
1. Dirty checking for array and embedded document properties is more reliable, such that modifying elements in an array (by any means) or changing a property of an embedded document will make the root document be `changed?`. With standard dirty checking, the `#{property}=` method must be called on the root document for it to be `changed?`.
|
279
|
-
2. It gives more useful and detailed change tracking for embedded documents, arrays of simple values, and arrays of embedded documents.
|
280
|
-
|
281
|
-
The `#{property}_changed?` and `#{property}_was` methods work the same as basic dirty checking, and the `_was` values are always deep clones of the original/previous value. The `#{property}_change` and `changes` methods differ from basic dirty checking for embedded documents and arrays, giving richer details of the changes instead of just the previous and current values. This makes generating detailed, human friendly audit trails of documents easy.
|
282
|
-
|
283
|
-
Tracking changes in embedded documents gives easy access to the changes in that document:
|
284
|
-
|
285
|
-
```ruby
|
286
|
-
book = Book.new(:cover => Cover.new(:color => "red"))
|
287
|
-
book.cover.color = "blue"
|
288
|
-
book.cover_changed? # => true
|
289
|
-
book.cover_was # => <deep clone of original state of book.cover>
|
290
|
-
book.cover_change # => [<deep clone of original state of book.cover>, {:color => ["red", "blue"]}]
|
291
|
-
```
|
292
|
-
|
293
|
-
Tracking changes in arrays of simple properties gives easy access to added and removed items:
|
294
|
-
|
295
|
-
```ruby
|
296
|
-
book = Book.new(:authors => ["Sarah", "Jane"])
|
297
|
-
book.authors.delete "Jane"
|
298
|
-
book.authors << "Sue"
|
299
|
-
book.authors_changed? # => true
|
300
|
-
book.authors_was # => ["Sarah", "Jane"]
|
301
|
-
book.authors_change # => [["Sarah", "Jane"], {:added => ["Sue"], :removed => ["Jane"]}]
|
302
|
-
```
|
303
|
-
|
304
|
-
Tracking changes in an array of embedded documents also gives changed items:
|
305
|
-
|
306
|
-
```ruby
|
307
|
-
book = Book.new(:pages => [Page.new(:number => 1), Page.new(:number => 2)]
|
308
|
-
book.pages[0].title = "New title"
|
309
|
-
book.pages.delete_at 1
|
310
|
-
book.pages << Page.new(:number => 3)
|
311
|
-
book.pages_changed? # => true
|
312
|
-
book.pages_was # => <deep clone of original pages array>
|
313
|
-
book.pages_change[0] # => <deep clone of original pages array>
|
314
|
-
book.pages_change[1] # => {:added => [<page 3>], :removed => [<page 2>], :changed => [[<deep clone of original page 1>, {:title => [nil, "New title"]}]]}
|
315
|
-
```
|
316
|
-
|
317
|
-
For change tracking in nested documents and document arrays to work, the embedded documents **must** have unique `_id` values. This can be accomplished easily in your embedded CouchPotato models by overriding `initialize`:
|
318
|
-
|
319
|
-
```ruby
|
320
|
-
def initialize(*args)
|
321
|
-
self._id = SecureRandom.uuid
|
322
|
-
super
|
323
|
-
end
|
324
|
-
```
|
325
|
-
|
326
287
|
#### Object validations
|
327
288
|
|
328
289
|
Couch Potato by default uses ActiveModel for validation
|
@@ -359,6 +320,16 @@ CouchPotato.database.view User.all
|
|
359
320
|
|
360
321
|
This will load all user documents in your database sorted by `created_at`.
|
361
322
|
|
323
|
+
For large data sets, use batches:
|
324
|
+
|
325
|
+
```ruby
|
326
|
+
CouchPotato.database.view_in_batches(User.all, batch_size: 100) do |users|
|
327
|
+
...
|
328
|
+
end
|
329
|
+
```
|
330
|
+
|
331
|
+
This will query CouchDB with skip/limit until all documents have been yielded.
|
332
|
+
|
362
333
|
```ruby
|
363
334
|
CouchPotato.database.view User.all(:key => (Time.now- 10)..(Time.now), :descending => true)
|
364
335
|
```
|
@@ -451,7 +422,16 @@ end
|
|
451
422
|
When querying this view you will get the raw data returned by CouchDB which looks something like this:
|
452
423
|
|
453
424
|
```json
|
454
|
-
{
|
425
|
+
{
|
426
|
+
"total_entries": 2,
|
427
|
+
"rows": [
|
428
|
+
{
|
429
|
+
"value": "alex",
|
430
|
+
"key": "2009-01-03 00:02:34 +000",
|
431
|
+
"id": "75976rgi7546gi02a"
|
432
|
+
}
|
433
|
+
]
|
434
|
+
}
|
455
435
|
```
|
456
436
|
|
457
437
|
To process this raw data you can also pass in a results filter:
|
@@ -464,7 +444,7 @@ end
|
|
464
444
|
|
465
445
|
In this case querying the view would only return the emitted value for each row.
|
466
446
|
|
467
|
-
You can pass in your own view specifications by passing in `:type => MyViewSpecClass`. Take a look at the CouchPotato::View
|
447
|
+
You can pass in your own view specifications by passing in `:type => MyViewSpecClass`. Take a look at the CouchPotato::View::\*ViewSpec classes to get an idea of how this works.
|
468
448
|
|
469
449
|
##### Digest view names
|
470
450
|
|
@@ -513,7 +493,6 @@ And you can pass parameters to the list:
|
|
513
493
|
CouchPotato.database.view(User.all(list: :add_last_name, list_params: {filter: '*'}))
|
514
494
|
```
|
515
495
|
|
516
|
-
|
517
496
|
#### Associations
|
518
497
|
|
519
498
|
Not supported. Not sure if they ever will be. You can implement those yourself using views and custom methods on your models.
|
@@ -598,9 +577,9 @@ describe 'save a user' do
|
|
598
577
|
end
|
599
578
|
```
|
600
579
|
|
601
|
-
By creating
|
580
|
+
By creating your own instances of `CouchPotato::Database` and passing them a fake CouchRest database instance you can completely disconnect your unit tests/spec from the database.
|
602
581
|
|
603
|
-
For stubbing out the database couch potato offers some helpers via the `couch_potato-rspec` gem. Use version 2.x of the gem
|
582
|
+
For stubbing out the database couch potato offers some helpers via the `couch_potato-rspec` gem. Use version 2.x of the gem if you are on RSpec 2, use 3.x for RSpec 3.
|
604
583
|
|
605
584
|
```ruby
|
606
585
|
class Comment
|
@@ -613,7 +592,8 @@ require 'couch_potato/rspec'
|
|
613
592
|
db = stub_db # stubs CouchPotato.database
|
614
593
|
db.stub_view(Comment, :by_commenter_id).with('23').and_return([:comment1, :comment2])
|
615
594
|
|
616
|
-
CouchPotato.database.view(Comment.by_commenter_id('23)) # => [:comment1, :comment2]
|
595
|
+
CouchPotato.database.view(Comment.by_commenter_id('23')) # => [:comment1, :comment2]
|
596
|
+
CouchPotato.database.view_in_batches(Comment.by_commenter_id('23'), batch_size: 1) # => yields [:comment1] and [:comment2]
|
617
597
|
CouchPotato.database.first(Comment.by_commenter_id('23)) # => :comment1
|
618
598
|
```
|
619
599
|
|
data/Rakefile
CHANGED
@@ -19,17 +19,18 @@ RSpec::Core::RakeTask.new(:spec_unit) do |spec|
|
|
19
19
|
end
|
20
20
|
|
21
21
|
desc 'Run all specs'
|
22
|
+
task :spec_ci do
|
23
|
+
Rake::Task[:spec_unit].execute
|
24
|
+
Rake::Task[:spec_functional].execute
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'Run all specs for all gemfiles'
|
22
28
|
task :spec do
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
Bundler.with_clean_env do
|
29
|
-
puts "Running tests with ActiveSupport #{version.sub('_', '.')}"
|
30
|
-
sh "env BUNDLE_GEMFILE=gemfiles/active_support_#{version} bundle install"
|
31
|
-
sh "env BUNDLE_GEMFILE=gemfiles/active_support_#{version} bundle exec rake spec_unit spec_functional"
|
32
|
-
end
|
29
|
+
%w(6_1 6_0 5_2 5_1 5_0).each do |version|
|
30
|
+
Bundler.with_clean_env do
|
31
|
+
puts "Running tests with ActiveSupport #{version.sub('_', '.')}"
|
32
|
+
sh "env BUNDLE_GEMFILE=gemfiles/active_support_#{version} bundle install"
|
33
|
+
sh "env BUNDLE_GEMFILE=gemfiles/active_support_#{version} bundle exec rake spec_unit spec_functional"
|
33
34
|
end
|
34
35
|
end
|
35
36
|
end
|
data/couch_potato-rspec.gemspec
CHANGED
@@ -13,8 +13,9 @@ Gem::Specification.new do |s|
|
|
13
13
|
|
14
14
|
s.add_dependency 'rspec', '~>3.4'
|
15
15
|
s.add_development_dependency 'rake'
|
16
|
+
s.add_dependency 'execjs', '~>2.7.0'
|
16
17
|
|
17
|
-
s.files = `git ls-files | grep "lib/couch_potato/rspec"`.split("\n")
|
18
|
+
s.files = `git ls-files | grep "lib/couch_potato/rspec\|vendor/pouchdb-collate"`.split("\n")
|
18
19
|
s.test_files = `git ls-files -- {test,spec,features}/* | grep rspec_matchers`.split("\n")
|
19
20
|
s.require_paths = ['lib']
|
20
21
|
end
|
data/couch_potato.gemspec
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
2
4
|
require 'couch_potato/version'
|
3
5
|
|
4
6
|
Gem::Specification.new do |s|
|
@@ -11,17 +13,17 @@ Gem::Specification.new do |s|
|
|
11
13
|
s.version = CouchPotato::VERSION
|
12
14
|
s.platform = Gem::Platform::RUBY
|
13
15
|
|
14
|
-
s.add_dependency '
|
16
|
+
s.add_dependency 'activemodel', ['>= 5.0', '< 7.0']
|
15
17
|
s.add_dependency 'couchrest', '~>2.0.0'
|
16
|
-
s.add_dependency '
|
18
|
+
s.add_dependency 'json', '~> 2.3'
|
17
19
|
|
18
|
-
s.add_development_dependency '
|
20
|
+
s.add_development_dependency 'rake', '~>12.0'
|
21
|
+
s.add_development_dependency 'rspec', '~>3.5.0'
|
19
22
|
s.add_development_dependency 'timecop'
|
20
23
|
s.add_development_dependency 'tzinfo'
|
21
|
-
s.add_development_dependency 'rake', '< 11.0'
|
22
24
|
|
23
25
|
s.files = `git ls-files | grep -v "lib/couch_potato/rspec"`.split("\n")
|
24
26
|
s.test_files = `git ls-files -- {test,spec,features}/* | grep -v rspec_matchers`.split("\n")
|
25
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map {|f| File.basename(f) }
|
27
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
26
28
|
s.require_paths = ['lib']
|
27
29
|
end
|
data/gemfiles/active_support_5_0
CHANGED