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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/.travis.yml +12 -8
  4. data/CHANGES.md +4 -0
  5. data/Gemfile +1 -1
  6. data/README.md +396 -276
  7. data/Rakefile +9 -9
  8. data/couch_potato-rspec.gemspec +20 -0
  9. data/couch_potato.gemspec +15 -16
  10. data/{active_support_4_0 → gemfiles/active_support_4_0} +3 -3
  11. data/{active_support_3_2 → gemfiles/active_support_4_1} +3 -2
  12. data/gemfiles/active_support_4_2 +11 -0
  13. data/lib/couch_potato-rspec.rb +3 -0
  14. data/lib/couch_potato.rb +3 -1
  15. data/lib/couch_potato/database.rb +42 -39
  16. data/lib/couch_potato/persistence/magic_timestamps.rb +5 -5
  17. data/lib/couch_potato/persistence/properties.rb +8 -2
  18. data/lib/couch_potato/persistence/simple_property.rb +11 -9
  19. data/lib/couch_potato/persistence/type_caster.rb +1 -1
  20. data/lib/couch_potato/railtie.rb +2 -0
  21. data/lib/couch_potato/version.rb +2 -1
  22. data/lib/couch_potato/view/base_view_spec.rb +18 -8
  23. data/lib/couch_potato/view/view_query.rb +2 -3
  24. data/spec/attachments_spec.rb +3 -3
  25. data/spec/callbacks_spec.rb +193 -113
  26. data/spec/conflict_handling_spec.rb +4 -4
  27. data/spec/create_spec.rb +5 -5
  28. data/spec/default_property_spec.rb +6 -6
  29. data/spec/destroy_spec.rb +5 -5
  30. data/spec/property_spec.rb +71 -61
  31. data/spec/rails_spec.rb +3 -3
  32. data/spec/railtie_spec.rb +12 -13
  33. data/spec/spec_helper.rb +3 -3
  34. data/spec/unit/active_model_compliance_spec.rb +16 -16
  35. data/spec/unit/attributes_spec.rb +36 -34
  36. data/spec/unit/base_view_spec_spec.rb +82 -35
  37. data/spec/unit/callbacks_spec.rb +2 -2
  38. data/spec/unit/couch_potato_spec.rb +3 -3
  39. data/spec/unit/create_spec.rb +12 -12
  40. data/spec/unit/custom_views_spec.rb +1 -1
  41. data/spec/unit/database_spec.rb +95 -95
  42. data/spec/unit/date_spec.rb +3 -3
  43. data/spec/unit/deep_dirty_attributes_spec.rb +104 -104
  44. data/spec/unit/dirty_attributes_spec.rb +19 -19
  45. data/spec/unit/forbidden_attributes_protection_spec.rb +4 -4
  46. data/spec/unit/initialize_spec.rb +37 -19
  47. data/spec/unit/json_spec.rb +4 -4
  48. data/spec/unit/lists_spec.rb +8 -8
  49. data/spec/unit/model_view_spec_spec.rb +14 -14
  50. data/spec/unit/persistence_spec.rb +6 -6
  51. data/spec/unit/properties_view_spec_spec.rb +4 -4
  52. data/spec/unit/rspec_matchers_spec.rb +73 -73
  53. data/spec/unit/rspec_stub_db_spec.rb +43 -42
  54. data/spec/unit/string_spec.rb +1 -1
  55. data/spec/unit/time_spec.rb +2 -2
  56. data/spec/unit/validation_spec.rb +1 -1
  57. data/spec/unit/view_query_spec.rb +54 -59
  58. data/spec/update_spec.rb +5 -5
  59. data/spec/view_updates_spec.rb +4 -4
  60. data/spec/views_spec.rb +43 -43
  61. metadata +18 -22
  62. data/lib/couch_potato/rspec.rb +0 -2
  63. data/lib/couch_potato/rspec/matchers.rb +0 -56
  64. data/lib/couch_potato/rspec/matchers/json2.js +0 -482
  65. data/lib/couch_potato/rspec/matchers/list_as_matcher.rb +0 -53
  66. data/lib/couch_potato/rspec/matchers/map_reduce_to_matcher.rb +0 -166
  67. data/lib/couch_potato/rspec/matchers/map_to_matcher.rb +0 -61
  68. data/lib/couch_potato/rspec/matchers/reduce_to_matcher.rb +0 -48
  69. 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: 66ce129081c4c70157fc61c40f500d9460660900
4
- data.tar.gz: 7be5df77cd9c672f71db5782440237702a7e1c8c
3
+ metadata.gz: 8581d86c34cc798e082cfd4e02bb53f042fa7751
4
+ data.tar.gz: b2f1a4996b05581a519706cb0efb10db43aa942f
5
5
  SHA512:
6
- metadata.gz: 8f3e28aad6d21326a3497f995b242c055ec764c786d73571bbc72053ee84954277832a3f519e61b1667c0a323d1ed0f02a45eb6f1a343c3611f7cec6091cb624
7
- data.tar.gz: 84f5e3b8ceb96051d8af95f0101b5620ac24b54736e4db36ccf8ad6ffe524c787140def2c242fd77d2f27fed8a2999a86803a94a6121ea7e8ea401ae956726cb
6
+ metadata.gz: aead6991131097d92510834befd4feb441c477a761472d43b72c3422cf0371eef35c7acfd52e2711483812c48f508ed706a72c8194875d00ebdd233fad774c38
7
+ data.tar.gz: f15626c508f062dbfd99d6672e0ed94b8364ec85d5030b77935656a42e3d27923b9bbc2e2bb8028f8f985de8c3967e55ff7754a410d97844f6836b410b8d05ba
@@ -0,0 +1 @@
1
+ 2.2.2
@@ -1,16 +1,20 @@
1
1
  rvm:
2
- - 1.9.3
3
2
  - 2.0.0
4
3
  - 2.1.2
5
- - jruby-19mode
6
- - rbx-2.2.10
4
+ - 2.2.2
5
+ - jruby-9.0.1.0
6
+ - rbx-2.5.8
7
+ services:
8
+ - couchdb
7
9
  gemfile:
8
- - active_support_3_2
9
- - active_support_4_0
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
- matrix:
15
- allow_failures:
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
@@ -1,4 +1,4 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in couch_potato.gemspec
4
- gemspec
4
+ gemspec name: 'couch_potato'
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 1.9.3, 2.0, 2.1, Rubinius
31
- * CouchDB 1.2.0
32
- * ActiveSupport 3.2, 4.0
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
- gem install couch_potato
40
+ ```bash
41
+ gem install couch_potato
42
+ ```
41
43
 
42
44
  #### Using with your ruby application:
43
45
 
44
- require 'rubygems'
45
- require 'couch_potato'
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
- CouchPotato::Config.database_name = 'name_of_the_db'
53
+ ```ruby
54
+ CouchPotato::Config.database_name = 'name_of_the_db'
55
+ ```
50
56
 
51
- The server URL will default to http://localhost:5984/ unless specified:
57
+ The server URL will default to `http://localhost:5984/` unless specified:
52
58
 
53
- CouchPotato::Config.database_name = "http://example.com:5984/name_of_the_db"
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
- CouchPotato::Config.database_host = "http://example.com:5984"
58
- CouchPotato::Config.database_name = "name_of_the_db"
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
- CouchPotato::Config.database_name = "http://username:password@example.com:5984/name_of_the_db"
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 (:javascript (default) or :erlang).
76
+ Optionally you can configure the default language for design documents (`:javascript` (default) or `:erlang`).
65
77
 
66
- CouchPotato::Config.default_language = :javascript | :erlang
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
- CouchPotato::Config.split_design_documents_per_view = true
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
- default: &default
77
- split_design_documents_per_view: true # optional
78
- default_language: :erlang # optional
79
-
80
- development:
81
- <<: *default
82
- database: development_db_name
83
- test:
84
- <<: *default
85
- database: test_db_name
86
- production:
87
- <<: *default
88
- database: <%= ENV['DB_NAME'] %>
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 _Gemfile_:
111
+ Add to your `Gemfile`:
93
112
 
94
- # gem 'rails' # we don't want to load activerecord so we can't require rails
95
- gem 'railties'
96
- gem 'actionpack'
97
- gem 'actionmailer'
98
- gem 'activemodel'
99
- gem "couch_potato"
100
- gem 'tzinfo'
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
- class User
113
- end
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
- class User
118
- include CouchPotato::Persistence
119
- end
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
- class User
124
- include CouchPotato::Persistence
148
+ ```ruby
149
+ class User
150
+ include CouchPotato::Persistence
125
151
 
126
- property :name
127
- end
152
+ property :name
153
+ end
154
+ ```
128
155
 
129
156
  Properties can be typed:
130
157
 
131
- class User
132
- include CouchPotato::Persistence
158
+ ```ruby
159
+ class User
160
+ include CouchPotato::Persistence
133
161
 
134
- property :address, :type => Address
135
- end
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, Date, Time and :boolean are supported):
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
- class User
141
- include CouchPotato::Persistence
169
+ ```ruby
170
+ class User
171
+ include CouchPotato::Persistence
142
172
 
143
- property :age, :type => Fixnum
144
- property :receive_newsletter, :type => :boolean
145
- end
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 hTML form) it will be converted into a Fixnum automatically.
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
- class User
153
- include CouchPotato::Persistence
183
+ ```ruby
184
+ class User
185
+ include CouchPotato::Persistence
154
186
 
155
- property :active, :default => true
156
- property :signed_up, :default => Proc.new { Time.now }
157
- end
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
- user = User.new :name => 'joe'
162
- CouchPotato.database.save_document user # or save_document!
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
- CouchPotato.database.load_document "id_of_the_user_document" # => <#User 0x3075>
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
- CouchPotato.database.save_document user do |user|
173
- user.name = 'joe'
174
- end
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
- CouchPotato.database.load ['user1', 'user2', 'user3'] # => [<#User 0x3075>, <#User 0x3076>, <#User 0x3077>]
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
- user.name # => 'joe'
189
- user.name = {:first => ['joe', 'joey'], :last => 'doe', :middle => 'J'} # you can set any ruby object that responds_to :to_json (includes all core objects)
190
- user._id # => "02097f33a0046123f1ebc0ebb6937269"
191
- user._rev # => "2769180384"
192
- user.created_at # => Fri Oct 24 19:05:54 +0200 2008
193
- user.updated_at # => Fri Oct 24 19:05:54 +0200 2008
194
- user.new? # => false
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, Number, Boolean, Hash or Array you have to define the type like this:
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
- class User
199
- property :date_of_birth, :type => Date
200
- end
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
- class User
207
- property :birthdays, :type => [Date]
208
- end
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
- user = User.create :name => 'joe'
215
- user.name # => 'joe'
216
- user.name_changed? # => false
217
- user.name_was # => nil
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
- user.name = 'jane'
222
- user.name_changed? # => true
223
- user.name_not_changed
224
- user.name_changed? # => false
225
- CouchPotato.database.save_document user # does nothing as no attributes are dirty
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 ```CouchPotato::DeepDirtyAttributes``` module in your models. This provides two benefits:
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 ```changed?```. With standard dirty checking, the ```#{property}=``` method must be called on the root document for it to be ```changed?```.
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 ```#{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.
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
- book = Book.new(:cover => Cover.new(:color => "red"))
239
- book.cover.color = "blue"
240
- book.cover_changed? # => true
241
- book.cover_was # => <deep clone of original state of book.cover>
242
- book.cover_change # => [<deep clone of original state of book.cover>, {:color => ["red", "blue"]}]
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
- book = Book.new(:authors => ["Sarah", "Jane"])
247
- book.authors.delete "Jane"
248
- book.authors << "Sue"
249
- book.authors_changed? # => true
250
- book.authors_was # => ["Sarah", "Jane"]
251
- book.authors_change # => [["Sarah", "Jane"], {:added => ["Sue"], :removed => ["Jane"]}]
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
- book = Book.new(:pages => [Page.new(:number => 1), Page.new(:number => 2)]
256
- book.pages[0].title = "New title"
257
- book.pages.delete_at 1
258
- book.pages << Page.new(:number => 3)
259
- book.pages_changed? # => true
260
- book.pages_was # => <deep clone of original pages array>
261
- book.pages_change[0] # => <deep clone of original pages array>
262
- book.pages_change[1] # => {:added => [<page 3>], :removed => [<page 2>], :changed => [[<deep clone of original page 1>, {:title => [nil, "New title"]}]]}
263
-
264
- 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```:
265
-
266
- def initialize(*args)
267
- self._id = SecureRandom.uuid
268
- super
269
- end
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
- class User
276
- property :name
277
- validates_presence_of :name
278
- end
334
+ ```ruby
335
+ class User
336
+ property :name
337
+ validates_presence_of :name
338
+ end
279
339
 
280
- user = User.new
281
- user.valid? # => false
282
- user.errors[:name] # => ['can't be blank']
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
- class User
289
- include CouchPotato::Persistence
290
- property :name
349
+ ```ruby
350
+ class User
351
+ include CouchPotato::Persistence
352
+ property :name
291
353
 
292
- view :all, :key => :created_at
293
- end
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
- CouchPotato.database.view User.all
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
- CouchPotato.database.view User.all(:key => (Time.now- 10)..(Time.now), :descending => true)
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
- class User
308
- property :name
374
+ ```ruby
375
+ class User
376
+ property :name
309
377
 
310
- view :all, :key => [:created_at, :name]
311
- end
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
- class User
316
- property :name
384
+ ```ruby
385
+ class User
386
+ property :name
317
387
 
318
- view :all, :key => [:created_at, :name], :language => :erlang
319
- end
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
- class User
326
- property :name
396
+ ```ruby
397
+ class User
398
+ property :name
327
399
 
328
- view :completed, :key => :name, :conditions => 'doc.completed === true'
329
- end
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
- class User
336
- property :name
337
- property :bio
408
+ ```ruby
409
+ class User
410
+ property :name
411
+ property :bio
338
412
 
339
- view :all, :key => :created_at, :properties => [:name], :type => :properties
340
- end
413
+ view :all, :key => :created_at, :properties => [:name], :type => :properties
414
+ end
341
415
 
342
- CouchPotato.database.view(User.everyone).first.name # => "joe"
343
- CouchPotato.database.view(User.everyone).first.bio # => nil
416
+ CouchPotato.database.view(User.everyone).first.name # => "joe"
417
+ CouchPotato.database.view(User.everyone).first.bio # => nil
344
418
 
345
- CouchPotato.database.first(User.everyone).name # => "joe" # convenience function, returns nil if nothing found
346
- CouchPotato.database.first!(User.everyone) # would raise CouchPotato::NotFound if nothing was found
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
- rescue_from CouchPotato::NotFound do
351
- render(:file => 'public/404.html', :status => :not_found, :layout => false)
352
- end
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
- class User
357
- view :all, :map => "function(doc) { emit(doc.created_at, null)}", :include_docs => true, :type => :custom
358
- end
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
- class User
363
- view :all, :map => "function(doc) { emit(null, require("views/lib/test").test)}", :lib => {:test => "exports.test = 'test'"}, :include_docs => true, :type => :custom
364
- end
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
- class User
369
- view :all, :map => "function(doc) { emit(doc.created_at, doc.name)}", :type => :raw
370
- end
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
- When querying this view you will get the raw data returned by CouchDB which looks something like this: {'total_entries': 2, 'rows': [{'value': 'alex', 'key': '2009-01-03 00:02:34 +000', 'id': '75976rgi7546gi02a'}]}
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
- class User
377
- view :all, :map => "function(doc) { emit(doc.created_at, doc.name)}", :type => :raw, :results_filter => lambda {|results| results['rows'].map{|row| row['value']}}
378
- end
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 :type => MyViewSpecClass. Take a look at the CouchPotato::View::*ViewSpec classes to get an idea of how this works.
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
- class User
391
- include CouchPotato::Persistence
392
-
393
- property :first_name
394
- view :with_full_name, key: first_namne, list: :add_last_name
395
- view :all, key: :first_name
396
-
397
- list :add_last_name, <<-JS
398
- function(head, req) {
399
- var row;
400
- send('{"rows": [');
401
- while(row = getRow()) {
402
- row.doc.name = row.doc.first_name + ' doe';
403
- send(JSON.stringify(row));
404
- };
405
- send(']}');
406
- }
407
- JS
408
- end
409
-
410
- CouchPotato.database.save User.new(first_name: 'joe')
411
- CouchPotato.database.view(User.with_full_name).first.name # => 'joe doe'
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
- CouchPotato.database.view(User.all(list: :add_last_name))
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
- class User
426
- include CouchPotato::Persistence
529
+ ```ruby
530
+ class User
531
+ include CouchPotato::Persistence
427
532
 
428
- before_create :do_something_before_create
429
- before_update {|user| user.do_something_on_update}
430
- end
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: :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.
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
- class User
443
- include CouchPotato::Persistence
444
- end
548
+ ```ruby
549
+ class User
550
+ include CouchPotato::Persistence
551
+ end
445
552
 
446
- data = File.read('some_file.text') # or from upload
447
- user = User.new
448
- user._attachments = {'photo' => {'data' => data, 'content_type' => 'image/png'}}
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
- user_reloaded = CouchPotato.database.load user.id
453
- user_reloaded._attachments['photo'] # => {'content_type' => 'image/png', 'length' => 37861}
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
- CouchPotato.with_database('couch_customer') do |couch|
460
- couch.save @customer
461
- end
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 _http://127.0.0.1:5984/couch_customer_.
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
- db = CouchPotato.use('couch_customer')
468
- db.save @customer
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
- class User
475
- include CouchPotato::Persistence
476
- end
588
+ ```ruby
589
+ class User
590
+ include CouchPotato::Persistence
591
+ end
477
592
 
478
- # RSpec
479
- describe 'save a user' do
480
- it 'should save' do
481
- couchrest_db = stub 'couchrest_db',
482
- database = CouchPotato::Database.new couchrest_db
483
- user = User.new
484
- couchrest_db.should_receive(:save_doc).with(...)
485
- database.save_document user
486
- end
487
- end
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
- class Comment
494
- view :by_commenter_id, :key => :commenter_id
495
- end
609
+ ```ruby
610
+ class Comment
611
+ view :by_commenter_id, :key => :commenter_id
612
+ end
496
613
 
497
- # RSpec
498
- require 'couch_potato/rspec'
614
+ # RSpec
615
+ require 'couch_potato/rspec'
499
616
 
500
- db = stub_db # stubs CouchPotato.database
501
- db.stub_view(Comment, :by_commenter_id).with('23').and_return([:comment1, :comment2])
617
+ db = stub_db # stubs CouchPotato.database
618
+ db.stub_view(Comment, :by_commenter_id).with('23').and_return([:comment1, :comment2])
502
619
 
503
- CouchPotato.database.view(Comment.by_commenter_id('23)) # => [:comment1, :comment2]
504
- CouchPotato.database.first(Comment.by_commenter_id('23)) # => :comment1
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
- class User
511
- include CouchPotato::Persistence
628
+ ```ruby
629
+ class User
630
+ include CouchPotato::Persistence
512
631
 
513
- property :name
514
- property :age, :type => Fixnum
632
+ property :name
633
+ property :age, :type => Fixnum
515
634
 
516
- view :by_name, :key => :name
517
- view :by_age, :key => :age
518
- view :oldest_by_name,
519
- :map => "function(doc) { emit(doc.name, doc.age); }",
520
- :reduce => "function(keys, values, rereduce) { return Math.max.apply(this, values); }"
521
- end
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
- #RSpec
524
- require 'couch_potato/rspec'
642
+ #RSpec
643
+ require 'couch_potato/rspec'
525
644
 
526
- describe User, 'views' do
527
- it "should map users to their name" do
528
- User.by_name.should map(User.new(:name => 'bill', :age => 23)).to(['bill', 1])
529
- end
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
- it "should reduce the users to the sum of their age" do
532
- User.by_age.should reduce([], [23, 22]).to(45)
533
- end
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
- it "should map/reduce users to the oldest age by name" do
536
- docs = [User.new(:name => "John", :age => 25), User.new(:name => "John", :age => 30), User.new(:name => "Jane", :age => 20)]
537
- User.oldest_by_name.should map_reduce(docs).with_options(:group => true).to(
538
- {"key" => "John", "value" => 30}, {"key" => "Jane", "value" => 20})
539
- end
540
- end
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. ```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).
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