couch_potato 1.4.0 → 1.6.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/.travis.yml +12 -8
- data/CHANGES.md +4 -0
- data/Gemfile +1 -1
- data/README.md +396 -276
- data/Rakefile +9 -9
- data/couch_potato-rspec.gemspec +20 -0
- data/couch_potato.gemspec +15 -16
- data/{active_support_4_0 → gemfiles/active_support_4_0} +3 -3
- data/{active_support_3_2 → gemfiles/active_support_4_1} +3 -2
- data/gemfiles/active_support_4_2 +11 -0
- data/lib/couch_potato-rspec.rb +3 -0
- data/lib/couch_potato.rb +3 -1
- data/lib/couch_potato/database.rb +42 -39
- data/lib/couch_potato/persistence/magic_timestamps.rb +5 -5
- data/lib/couch_potato/persistence/properties.rb +8 -2
- data/lib/couch_potato/persistence/simple_property.rb +11 -9
- data/lib/couch_potato/persistence/type_caster.rb +1 -1
- data/lib/couch_potato/railtie.rb +2 -0
- data/lib/couch_potato/version.rb +2 -1
- data/lib/couch_potato/view/base_view_spec.rb +18 -8
- data/lib/couch_potato/view/view_query.rb +2 -3
- data/spec/attachments_spec.rb +3 -3
- data/spec/callbacks_spec.rb +193 -113
- data/spec/conflict_handling_spec.rb +4 -4
- data/spec/create_spec.rb +5 -5
- data/spec/default_property_spec.rb +6 -6
- data/spec/destroy_spec.rb +5 -5
- data/spec/property_spec.rb +71 -61
- data/spec/rails_spec.rb +3 -3
- data/spec/railtie_spec.rb +12 -13
- data/spec/spec_helper.rb +3 -3
- data/spec/unit/active_model_compliance_spec.rb +16 -16
- data/spec/unit/attributes_spec.rb +36 -34
- data/spec/unit/base_view_spec_spec.rb +82 -35
- data/spec/unit/callbacks_spec.rb +2 -2
- data/spec/unit/couch_potato_spec.rb +3 -3
- data/spec/unit/create_spec.rb +12 -12
- data/spec/unit/custom_views_spec.rb +1 -1
- data/spec/unit/database_spec.rb +95 -95
- data/spec/unit/date_spec.rb +3 -3
- data/spec/unit/deep_dirty_attributes_spec.rb +104 -104
- data/spec/unit/dirty_attributes_spec.rb +19 -19
- data/spec/unit/forbidden_attributes_protection_spec.rb +4 -4
- data/spec/unit/initialize_spec.rb +37 -19
- data/spec/unit/json_spec.rb +4 -4
- data/spec/unit/lists_spec.rb +8 -8
- data/spec/unit/model_view_spec_spec.rb +14 -14
- data/spec/unit/persistence_spec.rb +6 -6
- data/spec/unit/properties_view_spec_spec.rb +4 -4
- data/spec/unit/rspec_matchers_spec.rb +73 -73
- data/spec/unit/rspec_stub_db_spec.rb +43 -42
- data/spec/unit/string_spec.rb +1 -1
- data/spec/unit/time_spec.rb +2 -2
- data/spec/unit/validation_spec.rb +1 -1
- data/spec/unit/view_query_spec.rb +54 -59
- data/spec/update_spec.rb +5 -5
- data/spec/view_updates_spec.rb +4 -4
- data/spec/views_spec.rb +43 -43
- metadata +18 -22
- data/lib/couch_potato/rspec.rb +0 -2
- data/lib/couch_potato/rspec/matchers.rb +0 -56
- data/lib/couch_potato/rspec/matchers/json2.js +0 -482
- data/lib/couch_potato/rspec/matchers/list_as_matcher.rb +0 -53
- data/lib/couch_potato/rspec/matchers/map_reduce_to_matcher.rb +0 -166
- data/lib/couch_potato/rspec/matchers/map_to_matcher.rb +0 -61
- data/lib/couch_potato/rspec/matchers/reduce_to_matcher.rb +0 -48
- data/lib/couch_potato/rspec/stub_db.rb +0 -57
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8581d86c34cc798e082cfd4e02bb53f042fa7751
|
4
|
+
data.tar.gz: b2f1a4996b05581a519706cb0efb10db43aa942f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aead6991131097d92510834befd4feb441c477a761472d43b72c3422cf0371eef35c7acfd52e2711483812c48f508ed706a72c8194875d00ebdd233fad774c38
|
7
|
+
data.tar.gz: f15626c508f062dbfd99d6672e0ed94b8364ec85d5030b77935656a42e3d27923b9bbc2e2bb8028f8f985de8c3967e55ff7754a410d97844f6836b410b8d05ba
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.2
|
data/.travis.yml
CHANGED
@@ -1,16 +1,20 @@
|
|
1
1
|
rvm:
|
2
|
-
- 1.9.3
|
3
2
|
- 2.0.0
|
4
3
|
- 2.1.2
|
5
|
-
-
|
6
|
-
-
|
4
|
+
- 2.2.2
|
5
|
+
- jruby-9.0.1.0
|
6
|
+
- rbx-2.5.8
|
7
|
+
services:
|
8
|
+
- couchdb
|
7
9
|
gemfile:
|
8
|
-
-
|
9
|
-
-
|
10
|
+
- gemfiles/active_support_4_0
|
11
|
+
- gemfiles/active_support_4_1
|
12
|
+
- gemfiles/active_support_4_2
|
13
|
+
before_install:
|
14
|
+
gem install bundler --version 1.11.2
|
10
15
|
before_script:
|
11
16
|
- sudo sh -c 'echo "[native_query_servers]" >> /etc/couchdb/local.ini'
|
12
17
|
- sudo sh -c 'echo "erlang = {couch_native_process, start_link, []}" >> /etc/couchdb/local.ini'
|
13
18
|
- sudo /etc/init.d/couchdb restart
|
14
|
-
|
15
|
-
|
16
|
-
- rvm: jruby-19mode
|
19
|
+
- bundle
|
20
|
+
script: bundle exec rake
|
data/CHANGES.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
## Changes
|
2
2
|
|
3
|
+
### 1.5.0
|
4
|
+
|
5
|
+
* Moved RSpec matchers into couch_potato-rspec gem. This way, people not using RSpec don't need to install the helpers, plus we can release separate matchers for RSpec 2 and 3.
|
6
|
+
|
3
7
|
### 1.4.0
|
4
8
|
|
5
9
|
* Added support for passing the model to blocks for computing the default value of a property (Alexander Lang)
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -27,9 +27,9 @@ Lastly Couch Potato aims to provide a seamless integration with Ruby on Rails, e
|
|
27
27
|
|
28
28
|
### Supported Environments
|
29
29
|
|
30
|
-
* Ruby
|
31
|
-
* CouchDB 1.
|
32
|
-
* ActiveSupport
|
30
|
+
* Ruby 2.0, 2.1, 2.2, Rubinius
|
31
|
+
* CouchDB 1.6.0
|
32
|
+
* ActiveSupport 4.0, 4.1, 4.2
|
33
33
|
|
34
34
|
(Supported means I run the specs against those before releasing a new gem.)
|
35
35
|
|
@@ -37,69 +37,90 @@ Lastly Couch Potato aims to provide a seamless integration with Ruby on Rails, e
|
|
37
37
|
|
38
38
|
Couch Potato is hosted as a gem which you can install like this:
|
39
39
|
|
40
|
-
|
40
|
+
```bash
|
41
|
+
gem install couch_potato
|
42
|
+
```
|
41
43
|
|
42
44
|
#### Using with your ruby application:
|
43
45
|
|
44
|
-
|
45
|
-
|
46
|
+
```ruby
|
47
|
+
require 'rubygems'
|
48
|
+
require 'couch_potato'
|
49
|
+
```
|
46
50
|
|
47
51
|
After that you configure the name of the database:
|
48
52
|
|
49
|
-
|
53
|
+
```ruby
|
54
|
+
CouchPotato::Config.database_name = 'name_of_the_db'
|
55
|
+
```
|
50
56
|
|
51
|
-
The server URL will default to http://localhost:5984
|
57
|
+
The server URL will default to `http://localhost:5984/` unless specified:
|
52
58
|
|
53
|
-
|
59
|
+
```ruby
|
60
|
+
CouchPotato::Config.database_name = "http://example.com:5984/name_of_the_db"
|
61
|
+
```
|
54
62
|
|
55
63
|
But you can also specify the database host separately from the database name:
|
56
64
|
|
57
|
-
|
58
|
-
|
65
|
+
```ruby
|
66
|
+
CouchPotato::Config.database_host = "http://example.com:5984"
|
67
|
+
CouchPotato::Config.database_name = "name_of_the_db"
|
68
|
+
```
|
59
69
|
|
60
70
|
Or with authentication
|
61
71
|
|
62
|
-
|
72
|
+
```ruby
|
73
|
+
CouchPotato::Config.database_name = "http://username:password@example.com:5984/name_of_the_db"
|
74
|
+
```
|
63
75
|
|
64
|
-
Optionally you can configure the default language for design documents (
|
76
|
+
Optionally you can configure the default language for design documents (`:javascript` (default) or `:erlang`).
|
65
77
|
|
66
|
-
|
78
|
+
```ruby
|
79
|
+
CouchPotato::Config.default_language = :javascript | :erlang
|
80
|
+
```
|
67
81
|
|
68
82
|
Another switch allows you to store each CouchDB view in its own design document. Otherwise views are grouped by model.
|
69
83
|
|
70
|
-
|
84
|
+
```ruby
|
85
|
+
CouchPotato::Config.split_design_documents_per_view = true
|
86
|
+
```
|
71
87
|
|
72
88
|
#### Using with Rails
|
73
89
|
|
74
|
-
Create a config/couchdb.yml
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
90
|
+
Create a `config/couchdb.yml`:
|
91
|
+
|
92
|
+
```yml
|
93
|
+
default: &default
|
94
|
+
split_design_documents_per_view: true # optional, default is false
|
95
|
+
digest_view_names: true # optional, default is false
|
96
|
+
default_language: :erlang # optional, default is javascript
|
97
|
+
|
98
|
+
development:
|
99
|
+
<<: *default
|
100
|
+
database: development_db_name
|
101
|
+
test:
|
102
|
+
<<: *default
|
103
|
+
database: test_db_name
|
104
|
+
production:
|
105
|
+
<<: *default
|
106
|
+
database: <%= ENV['DB_NAME'] %>
|
107
|
+
```
|
89
108
|
|
90
109
|
#### Rails 3.x
|
91
110
|
|
92
|
-
Add to your
|
111
|
+
Add to your `Gemfile`:
|
93
112
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
113
|
+
```ruby
|
114
|
+
# gem 'rails' # we don't want to load activerecord so we can't require rails
|
115
|
+
gem 'railties'
|
116
|
+
gem 'actionpack'
|
117
|
+
gem 'actionmailer'
|
118
|
+
gem 'activemodel'
|
119
|
+
gem "couch_potato"
|
120
|
+
gem 'tzinfo'
|
121
|
+
```
|
101
122
|
|
102
|
-
Note: please make sure that when you run `Date.today.as_json` in the Rails console it returns something like `2010/12/10` and not `2010-12-10` - if it does another gem has overwritten Couch Potato's Date patches - in this case move Couch Potato further down in your Gemfile or whereever you load it.
|
123
|
+
Note: please make sure that when you run `Date.today.as_json` in the Rails console it returns something like `2010/12/10` and not `2010-12-10` - if it does another gem has overwritten Couch Potato's Date patches - in this case move Couch Potato further down in your `Gemfile` or whereever you load it.
|
103
124
|
|
104
125
|
### Introduction
|
105
126
|
|
@@ -109,69 +130,87 @@ This is a basic tutorial on how to use Couch Potato. If you want to know all the
|
|
109
130
|
|
110
131
|
First you need a class.
|
111
132
|
|
112
|
-
|
113
|
-
|
133
|
+
```ruby
|
134
|
+
class User
|
135
|
+
end
|
136
|
+
```
|
114
137
|
|
115
138
|
To make instances of this class persistent include the persistence module:
|
116
139
|
|
117
|
-
|
118
|
-
|
119
|
-
|
140
|
+
```ruby
|
141
|
+
class User
|
142
|
+
include CouchPotato::Persistence
|
143
|
+
end
|
144
|
+
```
|
120
145
|
|
121
146
|
If you want to store any properties you have to declare them:
|
122
147
|
|
123
|
-
|
124
|
-
|
148
|
+
```ruby
|
149
|
+
class User
|
150
|
+
include CouchPotato::Persistence
|
125
151
|
|
126
|
-
|
127
|
-
|
152
|
+
property :name
|
153
|
+
end
|
154
|
+
```
|
128
155
|
|
129
156
|
Properties can be typed:
|
130
157
|
|
131
|
-
|
132
|
-
|
158
|
+
```ruby
|
159
|
+
class User
|
160
|
+
include CouchPotato::Persistence
|
133
161
|
|
134
|
-
|
135
|
-
|
162
|
+
property :address, :type => Address
|
163
|
+
end
|
164
|
+
```
|
136
165
|
|
137
|
-
In this case Address also implements CouchPotato::Persistence which means its JSON representation will be added to the user document.
|
138
|
-
Couch Potato also has support for the basic types (right now Fixnum
|
166
|
+
In this case `Address` also implements `CouchPotato::Persistence` which means its JSON representation will be added to the user document.
|
167
|
+
Couch Potato also has support for the basic types (right now `Fixnum`, `Date`, `Time` and `:boolean` are supported):
|
139
168
|
|
140
|
-
|
141
|
-
|
169
|
+
```ruby
|
170
|
+
class User
|
171
|
+
include CouchPotato::Persistence
|
142
172
|
|
143
|
-
|
144
|
-
|
145
|
-
|
173
|
+
property :age, :type => Fixnum
|
174
|
+
property :receive_newsletter, :type => :boolean
|
175
|
+
end
|
176
|
+
```
|
146
177
|
|
147
|
-
With this in place when you set the user's age as a String (e.g. using an
|
178
|
+
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 `Fixnum` automatically.
|
148
179
|
|
149
180
|
|
150
181
|
Properties can have a default value:
|
151
182
|
|
152
|
-
|
153
|
-
|
183
|
+
```ruby
|
184
|
+
class User
|
185
|
+
include CouchPotato::Persistence
|
154
186
|
|
155
|
-
|
156
|
-
|
157
|
-
|
187
|
+
property :active, :default => true
|
188
|
+
property :signed_up, :default => Proc.new { Time.now }
|
189
|
+
end
|
190
|
+
```
|
158
191
|
|
159
|
-
Now you can save your objects. All database operations are encapsulated in the CouchPotato::Database class. This separates your domain logic from the database access logic which makes it easier to write tests and also keeps you models smaller and cleaner.
|
192
|
+
Now you can save your objects. All database operations are encapsulated in the `CouchPotato::Database` class. This separates your domain logic from the database access logic which makes it easier to write tests and also keeps you models smaller and cleaner.
|
160
193
|
|
161
|
-
|
162
|
-
|
194
|
+
```ruby
|
195
|
+
user = User.new :name => 'joe'
|
196
|
+
CouchPotato.database.save_document user # or save_document!
|
197
|
+
```
|
163
198
|
|
164
199
|
You can of course also retrieve your instance:
|
165
200
|
|
166
|
-
|
201
|
+
```ruby
|
202
|
+
CouchPotato.database.load_document "id_of_the_user_document" # => <#User 0x3075>
|
203
|
+
```
|
167
204
|
|
168
205
|
#### Handling conflicts
|
169
206
|
|
170
207
|
CouchDB uses MVCC to detect write conflicts. If a conflict occurs when trying to update a document CouchDB returns an error. To handle conflicts easily you can save documents like this:
|
171
208
|
|
172
|
-
|
173
|
-
|
174
|
-
|
209
|
+
```ruby
|
210
|
+
CouchPotato.database.save_document user do |user|
|
211
|
+
user.name = 'joe'
|
212
|
+
end
|
213
|
+
```
|
175
214
|
|
176
215
|
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.
|
177
216
|
|
@@ -179,207 +218,261 @@ When a conflict occurs Couch Potato automatically reloads the document, runs the
|
|
179
218
|
|
180
219
|
You can also load a bunch of documents with one request.
|
181
220
|
|
182
|
-
|
221
|
+
```ruby
|
222
|
+
CouchPotato.database.load ['user1', 'user2', 'user3'] # => [<#User 0x3075>, <#User 0x3076>, <#User 0x3077>]
|
223
|
+
```
|
183
224
|
|
184
225
|
#### Properties
|
185
226
|
|
186
227
|
You can access the properties you declared above through normal attribute accessors.
|
187
228
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
229
|
+
```ruby
|
230
|
+
user.name # => 'joe'
|
231
|
+
user.name = {:first => ['joe', 'joey'], :last => 'doe', :middle => 'J'} # you can set any ruby object that responds_to :to_json (includes all core objects)
|
232
|
+
user._id # => "02097f33a0046123f1ebc0ebb6937269"
|
233
|
+
user._rev # => "2769180384"
|
234
|
+
user.created_at # => Fri Oct 24 19:05:54 +0200 2008
|
235
|
+
user.updated_at # => Fri Oct 24 19:05:54 +0200 2008
|
236
|
+
user.new? # => false
|
237
|
+
```
|
195
238
|
|
196
|
-
If you want to have properties that don't map to any JSON type, i.e. other than String
|
239
|
+
If you want to have properties that don't map to any JSON type, i.e. other than `String`, `Number`, `Boolean`, `Hash` or `Array` you have to define the type like this:
|
197
240
|
|
198
|
-
|
199
|
-
|
200
|
-
|
241
|
+
```ruby
|
242
|
+
class User
|
243
|
+
property :date_of_birth, :type => Date
|
244
|
+
end
|
245
|
+
```
|
201
246
|
|
202
|
-
The date_of_birth property is now automatically serialized to JSON and back when storing/retrieving objects.
|
247
|
+
The `date_of_birth` property is now automatically serialized to JSON and back when storing/retrieving objects.
|
203
248
|
|
204
249
|
If you want to store an Array of objects, just pass the definiton as an Array of Dates:
|
205
250
|
|
206
|
-
|
207
|
-
|
208
|
-
|
251
|
+
```ruby
|
252
|
+
class User
|
253
|
+
property :birthdays, :type => [Date]
|
254
|
+
end
|
255
|
+
```
|
209
256
|
|
210
257
|
#### Dirty tracking
|
211
258
|
|
212
259
|
CouchPotato tracks the dirty state of attributes in the same way ActiveRecord does:
|
213
260
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
261
|
+
```ruby
|
262
|
+
user = User.create :name => 'joe'
|
263
|
+
user.name # => 'joe'
|
264
|
+
user.name_changed? # => false
|
265
|
+
user.name_was # => nil
|
266
|
+
```
|
218
267
|
|
219
268
|
You can also force a dirty state:
|
220
269
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
270
|
+
```ruby
|
271
|
+
user.name = 'jane'
|
272
|
+
user.name_changed? # => true
|
273
|
+
user.name_not_changed
|
274
|
+
user.name_changed? # => false
|
275
|
+
CouchPotato.database.save_document user # does nothing as no attributes are dirty
|
276
|
+
```
|
226
277
|
|
227
278
|
#### Optional Deep Dirty Tracking
|
228
279
|
|
229
|
-
In addition to standard dirty tracking, you can opt-in to more advanced dirty tracking for deeply structured documents by including the
|
280
|
+
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:
|
230
281
|
|
231
|
-
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
|
282
|
+
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?`.
|
232
283
|
2. It gives more useful and detailed change tracking for embedded documents, arrays of simple values, and arrays of embedded documents.
|
233
284
|
|
234
|
-
The
|
285
|
+
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.
|
235
286
|
|
236
287
|
Tracking changes in embedded documents gives easy access to the changes in that document:
|
237
288
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
289
|
+
```ruby
|
290
|
+
book = Book.new(:cover => Cover.new(:color => "red"))
|
291
|
+
book.cover.color = "blue"
|
292
|
+
book.cover_changed? # => true
|
293
|
+
book.cover_was # => <deep clone of original state of book.cover>
|
294
|
+
book.cover_change # => [<deep clone of original state of book.cover>, {:color => ["red", "blue"]}]
|
295
|
+
```
|
243
296
|
|
244
297
|
Tracking changes in arrays of simple properties gives easy access to added and removed items:
|
245
298
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
299
|
+
```ruby
|
300
|
+
book = Book.new(:authors => ["Sarah", "Jane"])
|
301
|
+
book.authors.delete "Jane"
|
302
|
+
book.authors << "Sue"
|
303
|
+
book.authors_changed? # => true
|
304
|
+
book.authors_was # => ["Sarah", "Jane"]
|
305
|
+
book.authors_change # => [["Sarah", "Jane"], {:added => ["Sue"], :removed => ["Jane"]}]
|
306
|
+
```
|
252
307
|
|
253
308
|
Tracking changes in an array of embedded documents also gives changed items:
|
254
309
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
310
|
+
```ruby
|
311
|
+
book = Book.new(:pages => [Page.new(:number => 1), Page.new(:number => 2)]
|
312
|
+
book.pages[0].title = "New title"
|
313
|
+
book.pages.delete_at 1
|
314
|
+
book.pages << Page.new(:number => 3)
|
315
|
+
book.pages_changed? # => true
|
316
|
+
book.pages_was # => <deep clone of original pages array>
|
317
|
+
book.pages_change[0] # => <deep clone of original pages array>
|
318
|
+
book.pages_change[1] # => {:added => [<page 3>], :removed => [<page 2>], :changed => [[<deep clone of original page 1>, {:title => [nil, "New title"]}]]}
|
319
|
+
```
|
320
|
+
|
321
|
+
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`:
|
322
|
+
|
323
|
+
```ruby
|
324
|
+
def initialize(*args)
|
325
|
+
self._id = SecureRandom.uuid
|
326
|
+
super
|
327
|
+
end
|
328
|
+
```
|
270
329
|
|
271
330
|
#### Object validations
|
272
331
|
|
273
332
|
Couch Potato by default uses ActiveModel for validation
|
274
333
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
334
|
+
```ruby
|
335
|
+
class User
|
336
|
+
property :name
|
337
|
+
validates_presence_of :name
|
338
|
+
end
|
279
339
|
|
280
|
-
|
281
|
-
|
282
|
-
|
340
|
+
user = User.new
|
341
|
+
user.valid? # => false
|
342
|
+
user.errors[:name] # => ['can't be blank']
|
343
|
+
```
|
283
344
|
|
284
345
|
#### Finding stuff / views / lists
|
285
346
|
|
286
347
|
In order to find data in your CouchDB you have to create a [view](http://books.couchdb.org/relax/design-documents/views) first. Couch Potato offers you to create and manage those views for you. All you have to do is declare them in your classes:
|
287
348
|
|
288
|
-
|
289
|
-
|
290
|
-
|
349
|
+
```ruby
|
350
|
+
class User
|
351
|
+
include CouchPotato::Persistence
|
352
|
+
property :name
|
291
353
|
|
292
|
-
|
293
|
-
|
354
|
+
view :all, :key => :created_at
|
355
|
+
end
|
356
|
+
```
|
294
357
|
|
295
358
|
This will create a view called "all" in the "user" design document with a map function that emits "created_at" for every user document.
|
296
359
|
|
297
|
-
|
360
|
+
```ruby
|
361
|
+
CouchPotato.database.view User.all
|
362
|
+
```
|
298
363
|
|
299
|
-
This will load all user documents in your database sorted by created_at
|
364
|
+
This will load all user documents in your database sorted by `created_at`.
|
300
365
|
|
301
|
-
|
366
|
+
```ruby
|
367
|
+
CouchPotato.database.view User.all(:key => (Time.now- 10)..(Time.now), :descending => true)
|
368
|
+
```
|
302
369
|
|
303
370
|
Any options you pass in will be passed onto CouchDB.
|
304
371
|
|
305
372
|
Composite keys are also possible:
|
306
373
|
|
307
|
-
|
308
|
-
|
374
|
+
```ruby
|
375
|
+
class User
|
376
|
+
property :name
|
309
377
|
|
310
|
-
|
311
|
-
|
378
|
+
view :all, :key => [:created_at, :name]
|
379
|
+
end
|
380
|
+
```
|
312
381
|
|
313
382
|
You can let Couch Potato generate these map/reduce functions in Erlang, which reslts in much faster view generation:
|
314
383
|
|
315
|
-
|
316
|
-
|
384
|
+
```ruby
|
385
|
+
class User
|
386
|
+
property :name
|
317
387
|
|
318
|
-
|
319
|
-
|
388
|
+
view :all, :key => [:created_at, :name], :language => :erlang
|
389
|
+
end
|
390
|
+
```
|
320
391
|
|
321
392
|
So far only very simple views like the above work with Erlang.
|
322
393
|
|
323
394
|
You can also pass conditions as a JavaScript string:
|
324
395
|
|
325
|
-
|
326
|
-
|
396
|
+
```ruby
|
397
|
+
class User
|
398
|
+
property :name
|
327
399
|
|
328
|
-
|
329
|
-
|
400
|
+
view :completed, :key => :name, :conditions => 'doc.completed === true'
|
401
|
+
end
|
402
|
+
```
|
330
403
|
|
331
|
-
The creation of views is based on view specification classes (see [CouchPotato::View::BaseViewSpec](http://rdoc.info/rdoc/langalex/couch_potato/blob/e8f0069e5529ad08a1bd1f02637ea8f1d6d0ab5b/CouchPotato/View/BaseViewSpec.html) and its descendants for more detailed documentation). The above code uses the ModelViewSpec class which is used to find models by their properties. For more sophisticated searches you can use other view specifications (either use the built-in or provide your own) by passing a type parameter:
|
404
|
+
The creation of views is based on view specification classes (see [CouchPotato::View::BaseViewSpec](http://rdoc.info/rdoc/langalex/couch_potato/blob/e8f0069e5529ad08a1bd1f02637ea8f1d6d0ab5b/CouchPotato/View/BaseViewSpec.html) and its descendants for more detailed documentation). The above code uses the `ModelViewSpec` class which is used to find models by their properties. For more sophisticated searches you can use other view specifications (either use the built-in or provide your own) by passing a type parameter:
|
332
405
|
|
333
|
-
If you have larger structures and you only want to load some attributes you can use the PropertiesViewSpec (the full class name is automatically derived):
|
406
|
+
If you have larger structures and you only want to load some attributes you can use the `PropertiesViewSpec` (the full class name is automatically derived):
|
334
407
|
|
335
|
-
|
336
|
-
|
337
|
-
|
408
|
+
```ruby
|
409
|
+
class User
|
410
|
+
property :name
|
411
|
+
property :bio
|
338
412
|
|
339
|
-
|
340
|
-
|
413
|
+
view :all, :key => :created_at, :properties => [:name], :type => :properties
|
414
|
+
end
|
341
415
|
|
342
|
-
|
343
|
-
|
416
|
+
CouchPotato.database.view(User.everyone).first.name # => "joe"
|
417
|
+
CouchPotato.database.view(User.everyone).first.bio # => nil
|
344
418
|
|
345
|
-
|
346
|
-
|
419
|
+
CouchPotato.database.first(User.everyone).name # => "joe" # convenience function, returns nil if nothing found
|
420
|
+
CouchPotato.database.first!(User.everyone) # would raise CouchPotato::NotFound if nothing was found
|
421
|
+
```
|
347
422
|
|
348
423
|
If you want Rails to automatically show a 404 page when `CouchPotato::NotFound` is raised add this to your `ApplicationController`:
|
349
424
|
|
350
|
-
|
351
|
-
|
352
|
-
|
425
|
+
```ruby
|
426
|
+
rescue_from CouchPotato::NotFound do
|
427
|
+
render(:file => 'public/404.html', :status => :not_found, :layout => false)
|
428
|
+
end
|
429
|
+
```
|
353
430
|
|
354
431
|
You can also pass in custom map/reduce functions with the custom view spec:
|
355
432
|
|
356
|
-
|
357
|
-
|
358
|
-
|
433
|
+
```ruby
|
434
|
+
class User
|
435
|
+
view :all, :map => "function(doc) { emit(doc.created_at, null)}", :include_docs => true, :type => :custom
|
436
|
+
end
|
437
|
+
```
|
359
438
|
|
360
439
|
commonJS modules can also be used in custom views:
|
361
440
|
|
362
|
-
|
363
|
-
|
364
|
-
|
441
|
+
```ruby
|
442
|
+
class User
|
443
|
+
view :all, :map => "function(doc) { emit(null, require("views/lib/test").test)}", :lib => {:test => "exports.test = 'test'"}, :include_docs => true, :type => :custom
|
444
|
+
end
|
445
|
+
```
|
365
446
|
|
366
447
|
If you don't want the results to be converted into models the raw view is your friend:
|
367
448
|
|
368
|
-
|
369
|
-
|
370
|
-
|
449
|
+
```ruby
|
450
|
+
class User
|
451
|
+
view :all, :map => "function(doc) { emit(doc.created_at, doc.name)}", :type => :raw
|
452
|
+
end
|
453
|
+
```
|
454
|
+
|
455
|
+
When querying this view you will get the raw data returned by CouchDB which looks something like this:
|
371
456
|
|
372
|
-
|
457
|
+
```json
|
458
|
+
{'total_entries': 2, 'rows': [{'value': 'alex', 'key': '2009-01-03 00:02:34 +000', 'id': '75976rgi7546gi02a'}]}
|
459
|
+
```
|
373
460
|
|
374
461
|
To process this raw data you can also pass in a results filter:
|
375
462
|
|
376
|
-
|
377
|
-
|
378
|
-
|
463
|
+
```ruby
|
464
|
+
class User
|
465
|
+
view :all, :map => "function(doc) { emit(doc.created_at, doc.name)}", :type => :raw, :results_filter => lambda {|results| results['rows'].map{|row| row['value']}}
|
466
|
+
end
|
467
|
+
```
|
379
468
|
|
380
469
|
In this case querying the view would only return the emitted value for each row.
|
381
470
|
|
382
|
-
You can pass in your own view specifications by passing in
|
471
|
+
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.
|
472
|
+
|
473
|
+
##### Digest view names
|
474
|
+
|
475
|
+
If turned on, Couch Potato will append an MD5 digest of the map function to each view name. This makes sure (together with split_design_documents_per_view) that no views/design documents are ever updated. Instead, new ones are created. Since reindexing can take a long time once your database is larger, you want to avoid blocking your app while CouchDB is busy. Instead, you create a new view, warm it up, and only then start using it.
|
383
476
|
|
384
477
|
##### Lists
|
385
478
|
|
@@ -387,32 +480,43 @@ CouchPotato also supports [CouchDB lists](http://books.couchdb.org/relax/design-
|
|
387
480
|
|
388
481
|
Defining a list works similarly to views:
|
389
482
|
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
483
|
+
```ruby
|
484
|
+
class User
|
485
|
+
include CouchPotato::Persistence
|
486
|
+
|
487
|
+
property :first_name
|
488
|
+
view :with_full_name, key: first_namne, list: :add_last_name
|
489
|
+
view :all, key: :first_name
|
490
|
+
|
491
|
+
list :add_last_name, <<-JS
|
492
|
+
function(head, req) {
|
493
|
+
var row;
|
494
|
+
send('{"rows": [');
|
495
|
+
while(row = getRow()) {
|
496
|
+
row.doc.name = row.doc.first_name + ' doe';
|
497
|
+
send(JSON.stringify(row));
|
498
|
+
};
|
499
|
+
send(']}');
|
500
|
+
}
|
501
|
+
JS
|
502
|
+
end
|
503
|
+
|
504
|
+
CouchPotato.database.save User.new(first_name: 'joe')
|
505
|
+
CouchPotato.database.view(User.with_full_name).first.name # => 'joe doe'
|
506
|
+
```
|
412
507
|
|
413
508
|
You can also pass in the list at query time:
|
414
509
|
|
415
|
-
|
510
|
+
```ruby
|
511
|
+
CouchPotato.database.view(User.all(list: :add_last_name))
|
512
|
+
```
|
513
|
+
|
514
|
+
And you can pass parameters to the list:
|
515
|
+
|
516
|
+
```ruby
|
517
|
+
CouchPotato.database.view(User.all(list: :add_last_name, list_params: {filter: '*'}))
|
518
|
+
```
|
519
|
+
|
416
520
|
|
417
521
|
#### Associations
|
418
522
|
|
@@ -422,124 +526,140 @@ Not supported. Not sure if they ever will be. You can implement those yourself u
|
|
422
526
|
|
423
527
|
Couch Potato supports the usual lifecycle callbacks known from ActiveRecord:
|
424
528
|
|
425
|
-
|
426
|
-
|
529
|
+
```ruby
|
530
|
+
class User
|
531
|
+
include CouchPotato::Persistence
|
427
532
|
|
428
|
-
|
429
|
-
|
430
|
-
|
533
|
+
before_create :do_something_before_create
|
534
|
+
before_update {|user| user.do_something_on_update}
|
535
|
+
end
|
536
|
+
```
|
431
537
|
|
432
538
|
This will call the method do_something_before_create before creating an object and run the given lambda before updating one. Lambda callbacks get passed the model as their first argument. Method callbacks don't receive any arguments.
|
433
539
|
|
434
|
-
Supported callbacks are:
|
540
|
+
Supported callbacks are: `:before_validation`, `:before_validation_on_create`, `:before_validation_on_update`, `:before_validation_on_save`, `:before_create`, `:after_create`, `:before_update`, `:after_update`, `:before_save`, `:after_save`, `:before_destroy`, `:after_destroy`.
|
435
541
|
|
436
542
|
If you need access to the database in a callback: Couch Potato automatically assigns a database instance to the model before saving and when loading. It is available as _database_ accessor from within your model instance.
|
437
543
|
|
438
544
|
#### Attachments
|
439
545
|
|
440
|
-
There is basic attachment support: if you want to store any attachments set the _attachments attribute of a model before saving like this:
|
546
|
+
There is basic attachment support: if you want to store any attachments set the `_attachments` attribute of a model before saving like this:
|
441
547
|
|
442
|
-
|
443
|
-
|
444
|
-
|
548
|
+
```ruby
|
549
|
+
class User
|
550
|
+
include CouchPotato::Persistence
|
551
|
+
end
|
445
552
|
|
446
|
-
|
447
|
-
|
448
|
-
|
553
|
+
data = File.read('some_file.text') # or from upload
|
554
|
+
user = User.new
|
555
|
+
user._attachments = {'photo' => {'data' => data, 'content_type' => 'image/png'}}
|
556
|
+
```
|
449
557
|
|
450
558
|
When saving this object an attachment with the name _photo_ will be uploaded into CouchDB. It will be available under the url of the user object + _/photo_. When loading the user at a later time you still have access to the _content_type_ and additionally to the _length_ of the attachment:
|
451
559
|
|
452
|
-
|
453
|
-
|
560
|
+
```ruby
|
561
|
+
user_reloaded = CouchPotato.database.load user.id
|
562
|
+
user_reloaded._attachments['photo'] # => {'content_type' => 'image/png', 'length' => 37861}
|
563
|
+
```
|
454
564
|
|
455
565
|
#### Multi DB Support
|
456
566
|
|
457
567
|
Couch Potato supports accessing multiple CouchDBs:
|
458
568
|
|
459
|
-
|
460
|
-
|
461
|
-
|
569
|
+
```ruby
|
570
|
+
CouchPotato.with_database('couch_customer') do |couch|
|
571
|
+
couch.save @customer
|
572
|
+
end
|
573
|
+
```
|
462
574
|
|
463
|
-
Unless configured otherwise this would save the customer model to
|
575
|
+
Unless configured otherwise this would save the customer model to `http://127.0.0.1:5984/couch_customer`.
|
464
576
|
|
465
577
|
You can also first retrieve the database instance:
|
466
578
|
|
467
|
-
|
468
|
-
|
579
|
+
```ruby
|
580
|
+
db = CouchPotato.use('couch_customer')
|
581
|
+
db.save @customer
|
582
|
+
```
|
469
583
|
|
470
584
|
#### Testing
|
471
585
|
|
472
586
|
To make testing easier and faster database logic has been put into its own class, which you can replace and stub out in whatever way you want:
|
473
587
|
|
474
|
-
|
475
|
-
|
476
|
-
|
588
|
+
```ruby
|
589
|
+
class User
|
590
|
+
include CouchPotato::Persistence
|
591
|
+
end
|
477
592
|
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
593
|
+
# RSpec
|
594
|
+
describe 'save a user' do
|
595
|
+
it 'should save' do
|
596
|
+
couchrest_db = stub 'couchrest_db',
|
597
|
+
database = CouchPotato::Database.new couchrest_db
|
598
|
+
user = User.new
|
599
|
+
couchrest_db.should_receive(:save_doc).with(...)
|
600
|
+
database.save_document user
|
601
|
+
end
|
602
|
+
end
|
603
|
+
```
|
488
604
|
|
489
|
-
By creating you own instances of CouchPotato::Database and passing them a fake CouchRest database instance you can completely disconnect your unit tests/spec from the database.
|
605
|
+
By creating you own instances of `CouchPotato::Database` and passing them a fake CouchRest database instance you can completely disconnect your unit tests/spec from the database.
|
490
606
|
|
491
|
-
For stubbing out the database couch potato offers some helpers
|
607
|
+
For stubbing out the database couch potato offers some helpers via the `couch_potato-rspec` gem. Use version 2.x of the gem, you you are on RSpec 2, use 3.x for RSpec 3.
|
492
608
|
|
493
|
-
|
494
|
-
|
495
|
-
|
609
|
+
```ruby
|
610
|
+
class Comment
|
611
|
+
view :by_commenter_id, :key => :commenter_id
|
612
|
+
end
|
496
613
|
|
497
|
-
|
498
|
-
|
614
|
+
# RSpec
|
615
|
+
require 'couch_potato/rspec'
|
499
616
|
|
500
|
-
|
501
|
-
|
617
|
+
db = stub_db # stubs CouchPotato.database
|
618
|
+
db.stub_view(Comment, :by_commenter_id).with('23').and_return([:comment1, :comment2])
|
502
619
|
|
503
|
-
|
504
|
-
|
620
|
+
CouchPotato.database.view(Comment.by_commenter_id('23)) # => [:comment1, :comment2]
|
621
|
+
CouchPotato.database.first(Comment.by_commenter_id('23)) # => :comment1
|
622
|
+
```
|
505
623
|
|
506
624
|
##### Testing map/reduce functions
|
507
625
|
|
508
626
|
Couch Potato provides custom RSpec matchers for testing the map and reduce functions of your views. For example you can do this:
|
509
627
|
|
510
|
-
|
511
|
-
|
628
|
+
```ruby
|
629
|
+
class User
|
630
|
+
include CouchPotato::Persistence
|
512
631
|
|
513
|
-
|
514
|
-
|
632
|
+
property :name
|
633
|
+
property :age, :type => Fixnum
|
515
634
|
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
635
|
+
view :by_name, :key => :name
|
636
|
+
view :by_age, :key => :age
|
637
|
+
view :oldest_by_name,
|
638
|
+
:map => "function(doc) { emit(doc.name, doc.age); }",
|
639
|
+
:reduce => "function(keys, values, rereduce) { return Math.max.apply(this, values); }"
|
640
|
+
end
|
522
641
|
|
523
|
-
|
524
|
-
|
642
|
+
#RSpec
|
643
|
+
require 'couch_potato/rspec'
|
525
644
|
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
645
|
+
describe User, 'views' do
|
646
|
+
it "should map users to their name" do
|
647
|
+
User.by_name.should map(User.new(:name => 'bill', :age => 23)).to(['bill', 1])
|
648
|
+
end
|
530
649
|
|
531
|
-
|
532
|
-
|
533
|
-
|
650
|
+
it "should reduce the users to the sum of their age" do
|
651
|
+
User.by_age.should reduce([], [23, 22]).to(45)
|
652
|
+
end
|
534
653
|
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
654
|
+
it "should map/reduce users to the oldest age by name" do
|
655
|
+
docs = [User.new(:name => "John", :age => 25), User.new(:name => "John", :age => 30), User.new(:name => "Jane", :age => 20)]
|
656
|
+
User.oldest_by_name.should map_reduce(docs).with_options(:group => true).to(
|
657
|
+
{"key" => "John", "value" => 30}, {"key" => "Jane", "value" => 20})
|
658
|
+
end
|
659
|
+
end
|
660
|
+
```
|
541
661
|
|
542
|
-
This will actually run your map/reduce functions in a JavaScript interpreter, passing the arguments as JSON and converting the results back to Ruby.
|
662
|
+
This will actually run your map/reduce functions in a JavaScript interpreter, passing the arguments as JSON and converting the results back to Ruby. `map_reduce` specs map the input documents, reduce the emitted keys/values, and rereduce the results while also respecting the `:group` and `:group_level` couchdb options. For more examples see the [spec](http://github.com/langalex/couch_potato/blob/master/spec/unit/rspec_matchers_spec.rb).
|
543
663
|
|
544
664
|
In order for this to work you must have the `js` executable in your PATH. This is usually part of the _spidermonkey_ package/port. (On MacPorts that's _spidemonkey_, on Linux it could be one of _libjs_, _libmozjs_ or _libspidermonkey_). When you installed CouchDB via your packet manager Spidermonkey should already be there.
|
545
665
|
|
@@ -551,7 +671,7 @@ Issues are tracked at github: http://github.com/langalex/couch_potato/issues
|
|
551
671
|
|
552
672
|
There is a mailing list, just write to: couchpotato@librelist.com
|
553
673
|
|
554
|
-
You can run all the specs by calling 'rake spec_unit' and 'rake spec_functional' in the root folder of Couch Potato. The specs require a running CouchDB instance at http://localhost:5984
|
674
|
+
You can run all the specs by calling 'rake spec_unit' and 'rake spec_functional' in the root folder of Couch Potato. The specs require a running CouchDB instance at `http://localhost:5984`
|
555
675
|
|
556
676
|
I will only accept patches that are covered by specs - sorry.
|
557
677
|
|