couch_potato-rails2 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/.gitignore +7 -0
  2. data/.travis.yml +5 -0
  3. data/CHANGES.md +148 -0
  4. data/CREDITS +6 -0
  5. data/Gemfile +4 -0
  6. data/MIT-LICENSE.txt +19 -0
  7. data/README.md +450 -0
  8. data/Rakefile +82 -0
  9. data/couch_potato.gemspec +27 -0
  10. data/init.rb +3 -0
  11. data/lib/core_ext/date.rb +14 -0
  12. data/lib/core_ext/object.rb +5 -0
  13. data/lib/core_ext/string.rb +12 -0
  14. data/lib/core_ext/symbol.rb +15 -0
  15. data/lib/core_ext/time.rb +23 -0
  16. data/lib/couch_potato.rb +48 -0
  17. data/lib/couch_potato/database.rb +179 -0
  18. data/lib/couch_potato/persistence.rb +124 -0
  19. data/lib/couch_potato/persistence/active_model_compliance.rb +44 -0
  20. data/lib/couch_potato/persistence/attachments.rb +31 -0
  21. data/lib/couch_potato/persistence/callbacks.rb +29 -0
  22. data/lib/couch_potato/persistence/dirty_attributes.rb +56 -0
  23. data/lib/couch_potato/persistence/ghost_attributes.rb +12 -0
  24. data/lib/couch_potato/persistence/json.rb +47 -0
  25. data/lib/couch_potato/persistence/magic_timestamps.rb +23 -0
  26. data/lib/couch_potato/persistence/properties.rb +79 -0
  27. data/lib/couch_potato/persistence/simple_property.rb +82 -0
  28. data/lib/couch_potato/persistence/type_caster.rb +40 -0
  29. data/lib/couch_potato/railtie.rb +25 -0
  30. data/lib/couch_potato/rspec.rb +2 -0
  31. data/lib/couch_potato/rspec/matchers.rb +39 -0
  32. data/lib/couch_potato/rspec/matchers/json2.js +482 -0
  33. data/lib/couch_potato/rspec/matchers/list_as_matcher.rb +54 -0
  34. data/lib/couch_potato/rspec/matchers/map_to_matcher.rb +49 -0
  35. data/lib/couch_potato/rspec/matchers/print_r.js +60 -0
  36. data/lib/couch_potato/rspec/matchers/reduce_to_matcher.rb +50 -0
  37. data/lib/couch_potato/rspec/stub_db.rb +46 -0
  38. data/lib/couch_potato/validation.rb +16 -0
  39. data/lib/couch_potato/validation/with_active_model.rb +27 -0
  40. data/lib/couch_potato/validation/with_validatable.rb +41 -0
  41. data/lib/couch_potato/version.rb +3 -0
  42. data/lib/couch_potato/view/base_view_spec.rb +84 -0
  43. data/lib/couch_potato/view/custom_view_spec.rb +42 -0
  44. data/lib/couch_potato/view/custom_views.rb +52 -0
  45. data/lib/couch_potato/view/lists.rb +23 -0
  46. data/lib/couch_potato/view/model_view_spec.rb +75 -0
  47. data/lib/couch_potato/view/properties_view_spec.rb +47 -0
  48. data/lib/couch_potato/view/raw_view_spec.rb +25 -0
  49. data/lib/couch_potato/view/view_query.rb +82 -0
  50. data/rails/init.rb +4 -0
  51. data/rails/reload_classes.rb +47 -0
  52. data/spec/attachments_spec.rb +23 -0
  53. data/spec/callbacks_spec.rb +297 -0
  54. data/spec/create_spec.rb +35 -0
  55. data/spec/custom_view_spec.rb +239 -0
  56. data/spec/default_property_spec.rb +38 -0
  57. data/spec/destroy_spec.rb +29 -0
  58. data/spec/fixtures/address.rb +10 -0
  59. data/spec/fixtures/person.rb +6 -0
  60. data/spec/property_spec.rb +323 -0
  61. data/spec/rails_spec.rb +50 -0
  62. data/spec/railtie_spec.rb +65 -0
  63. data/spec/spec.opts +2 -0
  64. data/spec/spec_helper.rb +44 -0
  65. data/spec/unit/active_model_compliance_spec.rb +98 -0
  66. data/spec/unit/attributes_spec.rb +135 -0
  67. data/spec/unit/base_view_spec_spec.rb +106 -0
  68. data/spec/unit/callbacks_spec.rb +46 -0
  69. data/spec/unit/couch_potato_spec.rb +39 -0
  70. data/spec/unit/create_spec.rb +69 -0
  71. data/spec/unit/custom_views_spec.rb +15 -0
  72. data/spec/unit/database_spec.rb +317 -0
  73. data/spec/unit/date_spec.rb +22 -0
  74. data/spec/unit/dirty_attributes_spec.rb +136 -0
  75. data/spec/unit/initialize_spec.rb +38 -0
  76. data/spec/unit/json_spec.rb +30 -0
  77. data/spec/unit/lists_spec.rb +20 -0
  78. data/spec/unit/model_view_spec_spec.rb +13 -0
  79. data/spec/unit/properties_view_spec_spec.rb +31 -0
  80. data/spec/unit/rspec_matchers_spec.rb +124 -0
  81. data/spec/unit/rspec_stub_db_spec.rb +35 -0
  82. data/spec/unit/string_spec.rb +7 -0
  83. data/spec/unit/time_spec.rb +15 -0
  84. data/spec/unit/validation_spec.rb +67 -0
  85. data/spec/unit/view_query_spec.rb +86 -0
  86. data/spec/update_spec.rb +40 -0
  87. data/spec/view_updates_spec.rb +28 -0
  88. metadata +243 -0
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ couch_potato_js_runner.js
5
+ pkg
6
+ .rvmrc
7
+ *.swp
@@ -0,0 +1,5 @@
1
+ rvm:
2
+ - 1.9.2
3
+ - 1.8.7
4
+ - ree
5
+ env: "DATABASE='http://couchpotato.iriscouch.com/couch_potato_test' "
@@ -0,0 +1,148 @@
1
+ ## Changes
2
+
3
+ ### 0.5.6
4
+
5
+ * remove the stale parameter from a view query if it's nil, as couchdb only allows stale to be ok or update\_after (langalex)
6
+
7
+ ### 0.5.5
8
+
9
+ * support for split_design_documents_per_view (jweiss)
10
+ * errors now returns a Hash instead of an Array (bterkuile)
11
+ * support passing in list names as symbols in view specs (langalex)
12
+
13
+ ### 0.5.4
14
+ * cast 'false' to false for boolean properties (langalex)
15
+
16
+ ### 0.5.3
17
+ * added CouchPotato::Database.load! (langalex)
18
+
19
+ ### 0.5.2
20
+ * added CouchPotato::Database#first and #first! methods (langalex)
21
+ * added workaround for BigCouch/Cloudant to not add null reduce functions to views (langalex)
22
+ * don't add _attachments if there are none (langalex)
23
+
24
+ ### 0.5.1
25
+ * fixed issues with tzinfo gem (Bernd Ahlers)
26
+
27
+ ### 0.5.0
28
+ * time zone support (Time properties are now converted to current Time.zone) (langalex)
29
+ * lazy property initialization (performance!) (langalex)
30
+ * active_model is now the default validation framework (langalex)
31
+
32
+ ### 0.4.0
33
+ * ruby 1.9.2 compatibility (langalex)
34
+ * couch potato objects now behave correctly when used as keys in Hashes (langalex)
35
+ * use as\_json instead of to\_s(:json), which is the rails way
36
+ * use ActiveModel dirty tracking (langalex) - this means no more "deep tracking", e.g. `user.tags << 'new_tag'; user.dirty? # false`
37
+
38
+ ### 0.3.2
39
+ * support yielding to blocks on #initialize (martinrehfeld)
40
+ * support for negative numbers in Fixnum/Float properties (langalex)
41
+
42
+ ### 0.3.1
43
+ * ActiveModel callbacks (kazjote)
44
+ * do not use Rails.env in initializer as it will free Rails.env for all times and in Rails 2.3.x apps it will be called too early thus always beeing development (jweiss)
45
+ * ruby 1.9.2 compatibility (langalex)
46
+ * can configure validation framework in couchdb.yml, process couchdb.yml with erb (langalex)
47
+
48
+ ### 0.3.0
49
+ * support for lists (langalex)
50
+
51
+ ### 0.2.32
52
+ * added persisted? and to_key for proper ActiveModel compliance (thilo)
53
+ * id setter (jhohertz-work)
54
+ * load document ids if include\_documents is false (jweiss)
55
+ * persist given created\_at/updated\_at instead of Time.now (langalex)
56
+
57
+ ### 0.2.31
58
+ * Removed requirement for validatable gem. Allows for using more uptodate versions of the library, or doesn't install it when you're using ActiveModel. (mattmatt)
59
+ * fixed callbacks of super classes were not run (langalex)
60
+
61
+ ### 0.2.30
62
+ * pass in multiple keys when querying a view (langalex)
63
+
64
+ ### 0.2.29
65
+ * nicer inspect() for models (mattmatt)
66
+ * fixed (re)reduce for property views wasn't working (langalex)
67
+
68
+ ### 0.2.28
69
+ * fixed reloading nested classes (langalex)
70
+ * fixed constant missing error when loading models with uninitialized classes via views (langalex)
71
+ * added rspec helpers for stubbing out views (langalex)
72
+ * fixed design document names for nested model classes (svenfuchs)
73
+
74
+ ### 0.2.27
75
+ * workaround for Rails apps using bundler: database name was not initialized from couchdb.yml (langalex)
76
+
77
+ ### 0.2.26
78
+ * added to_s(:json) to Date and Time to be able to get properly formatted dates/times for searching with dates/times (langalex)
79
+ * all times are now stored as UTC (langalex)
80
+ * added support for Float attributes (arbovm)
81
+
82
+
83
+ ### 0.2.25
84
+ * automatic view updates: when you change the definition of a view couch potato will now update the design document in the database (langalex)
85
+ * support for properties of type Date, better support for Time (langalex)
86
+ * support for default reduce count methods in custom views (jweiss)
87
+
88
+ ### 0.2.24
89
+ * persistent instances can now be marked as dirty with #is_dirty (langalex)
90
+
91
+ ### 0.2.23
92
+ * Couch Potato models now conform to the ActiveModel interface when ActiveModel is installed, see http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/ (langalex)
93
+ * fixed error with dirty tracking and BigDecimals (thilo)
94
+ * added the ability to use ActiveModel validations instead of validatable (martinrehfeld)
95
+
96
+ ### 0.2.22
97
+ * fixed properties with default values returned default when a blank value like '' or [] was set (langalex)
98
+
99
+ ### 0.2.21
100
+ * automatically set a database instance on results of CouchPotato::Database#view (langalex)
101
+ * improved auto loading of unloaded constants - can now load constants that have never been loaded before (langalex)
102
+ * raise exception on invalid parameters passed to a couchdb view query (langalex)
103
+ * when querying a view: pass in ranges as key instead of startkey/endkey, pass in plain value instead of hash with key (langalex)
104
+
105
+ ### 0.2.20
106
+ * support for :boolean properties (jweiss)
107
+ * return the total_rows when querying a view (langalex)
108
+
109
+ ### 0.2.19
110
+ * added conditions to views (langalex)
111
+
112
+ ### 0.2.18
113
+ * set Fixnum property to nil when given a blank string (langalex)
114
+
115
+ ### 0.2.17
116
+ * fixed nil attributes were omitted in json (jweiss, mattmatt)
117
+ * support for properties of type Fixnum (langalex)
118
+
119
+ ### 0.2.16
120
+ * fixed problem with classes being not loaded in rails development mode (langalex)
121
+ * fixed persist boolean false value (bernd)
122
+
123
+ ### 0.2.15
124
+ * ability to change the name of the attribute that stores the ruby class in the documents by setting JSON.create_id (lennart)
125
+ * fixed double loading issue with bundler (jweiss)
126
+ * fixed an issue with setting attachments (endor)
127
+
128
+ ### 0.2.13
129
+
130
+ * support adding errors in before_validation callbacks (mattmatt)
131
+ * support for inheritance (mattmatt)
132
+ * support for save without validations (mattmatt)
133
+ * improved (de)serialization now supports deserializing nested objects (railsbros, specs by hagenburger)
134
+ * RSpec matchers for testing map/reduce functions (langalex)
135
+
136
+ ### 0.2.10
137
+ * fixed bug with hardcoded timezone
138
+
139
+ ### 0.2.9
140
+
141
+ * allow to overwrite attribute accessor of properties and use super to call the original accessors
142
+ * allow read access to attributes that are present in the Couchdb document but not defined as properties
143
+ * support default values for properties via the :default parameter
144
+ * support attachments via the _attachments property
145
+ * support for namespaces models
146
+ * removed belongs_to macro for now
147
+ * removed CouchPotato::Config.database_server, just set CouchPotato::Config.database_name to the full url if you are not using localhost:5984
148
+ * Ruby 1.9 was broken and is now working again
data/CREDITS ADDED
@@ -0,0 +1,6 @@
1
+ Parts of this are taken or inspired by Geoffrey Grosenbach's travel_app which is part of his CouchDB screencast.
2
+
3
+ Jan Lehnardt - got me started on doing this.
4
+ Brian Candler - contributed performance patches
5
+
6
+ see CHANGES.md for all the committers
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in couch_potato.gemspec
4
+ gemspec
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2007 Bryan Helmkamp, Seth Fitzsimmons
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,450 @@
1
+ ## Couch Potato
2
+
3
+ ... is a persistence layer written in ruby for CouchDB.
4
+
5
+ ### Mission
6
+
7
+ 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.
8
+
9
+ It follows the document/view/querying semantics established by CouchDB and won't try to mimic ActiveRecord behavior in any way as that IS BAD.
10
+
11
+ Code that uses Couch Potato should be easy to test.
12
+
13
+ Lastly Couch Potato aims to provide a seamless integration with Ruby on Rails, e.g. routing, form helpers etc.
14
+
15
+ ### Core Features
16
+
17
+ * persisting objects by including the CouchPotato::Persistence module
18
+ * declarative views with either custom or generated map/reduce functions
19
+ * extensive spec suite
20
+
21
+ ### Supported Environments
22
+
23
+ * Ruby 1.8.7, 1.9.2
24
+ * CouchDB 1.1.0
25
+
26
+ (Supported means I run the specs against those before releasing a new gem.)
27
+
28
+ ### Installation
29
+
30
+ Couch Potato is hosted as a gem which you can install like this:
31
+
32
+ (sudo) gem install couch_potato
33
+
34
+ #### Using with your ruby application:
35
+
36
+ require 'rubygems'
37
+ require 'couch_potato'
38
+
39
+ After that you configure the name of the database:
40
+
41
+ CouchPotato::Config.database_name = 'name_of_the_db'
42
+
43
+ The server URL will default to http://localhost:5984/ unless specified:
44
+
45
+ CouchPotato::Config.database_name = "http://example.com:5984/name_of_the_db"
46
+
47
+ Or with authentication
48
+
49
+ CouchPotato::Config.database_name = "http://username:password@example.com:5984/name_of_the_db"
50
+
51
+ Optionally you can configure which framework you want to use for validations (either validatable or ActiveModel (default))
52
+
53
+ CouchPotato::Config.validation_framework = :validatable | :active_model
54
+
55
+ Another switch allows you to store each CouchDB view in its own design document. Otherwise views are grouped by model.
56
+
57
+ CouchPotato::Config.split_design_documents_per_view = true
58
+
59
+ #### Using with Rails
60
+
61
+ Create a config/couchdb.yml:
62
+
63
+ default: &default
64
+ validation_framework: :active_model # optional
65
+ split_design_documents_per_view: true # optional
66
+
67
+ development:
68
+ <<: *default
69
+ database: development_db_name
70
+ test:
71
+ <<: *default
72
+ database: test_db_name
73
+ production:
74
+ <<: *default
75
+ database: <%= ENV['DB_NAME'] %>
76
+
77
+ #### Rails 2.x
78
+
79
+ Add to your _config/environment.rb_:
80
+
81
+ config.gem 'couch_potato', :source => 'http://gemcutter.org'
82
+ config.frameworks -= [:active_record] # if you switch completely
83
+
84
+ #### Rails 3.x
85
+
86
+ Add to your _Gemfile_:
87
+
88
+ # gem 'rails' # we don't want to load activerecord so we can't require rails
89
+ gem 'railties'
90
+ gem 'actionpack'
91
+ gem 'actionmailer'
92
+ gem 'activemodel'
93
+ gem "couch_potato"
94
+
95
+ 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.
96
+
97
+ ### Introduction
98
+
99
+ This is a basic tutorial on how to use Couch Potato. If you want to know all the details feel free to read the specs and the [rdocs](http://rdoc.info/projects/langalex/couch_potato).
100
+
101
+ #### Save, load objects
102
+
103
+ First you need a class.
104
+
105
+ class User
106
+ end
107
+
108
+ To make instances of this class persistent include the persistence module:
109
+
110
+ class User
111
+ include CouchPotato::Persistence
112
+ end
113
+
114
+ If you want to store any properties you have to declare them:
115
+
116
+ class User
117
+ include CouchPotato::Persistence
118
+
119
+ property :name
120
+ end
121
+
122
+ Properties can be typed:
123
+
124
+ class User
125
+ include CouchPotato::Persistence
126
+
127
+ property :address, :type => Address
128
+ end
129
+
130
+ In this case Address also implements CouchPotato::Persistence which means its JSON representation will be added to the user document.
131
+ Couch Potato also has support for the basic types (right now Fixnum, Date, Time and :boolean are supported):
132
+
133
+ class User
134
+ include CouchPotato::Persistence
135
+
136
+ property :age, :type => Fixnum
137
+ property :receive_newsletter, :type => :boolean
138
+ end
139
+
140
+ 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.
141
+
142
+
143
+ Properties can have a default value:
144
+
145
+ class User
146
+ include CouchPotato::Persistence
147
+
148
+ property :active, :default => true
149
+ end
150
+
151
+ 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.
152
+
153
+ user = User.new :name => 'joe'
154
+ CouchPotato.database.save_document user # or save_document!
155
+
156
+ You can of course also retrieve your instance:
157
+
158
+ CouchPotato.database.load_document "id_of_the_user_document" # => <#User 0x3075>
159
+
160
+
161
+ #### Properties
162
+
163
+ You can access the properties you declared above through normal attribute accessors.
164
+
165
+ user.name # => 'joe'
166
+ user.name = {:first => ['joe', 'joey'], :last => 'doe', :middle => 'J'} # you can set any ruby object that responds_to :to_json (includes all core objects)
167
+ user._id # => "02097f33a0046123f1ebc0ebb6937269"
168
+ user._rev # => "2769180384"
169
+ user.created_at # => Fri Oct 24 19:05:54 +0200 2008
170
+ user.updated_at # => Fri Oct 24 19:05:54 +0200 2008
171
+ user.new? # => false
172
+
173
+ 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:
174
+
175
+ class User
176
+ property :date_of_birth, :type => Date
177
+ end
178
+
179
+ The date_of_birth property is now automatically serialized to JSON and back when storing/retrieving objects.
180
+
181
+ #### Dirty tracking
182
+
183
+ CouchPotato tracks the dirty state of attributes in the same way ActiveRecord does:
184
+
185
+ user = User.create :name => 'joe'
186
+ user.name # => 'joe'
187
+ user.name_changed? # => false
188
+ user.name_was # => nil
189
+
190
+ You can also force a dirty state:
191
+
192
+ user.name = 'jane'
193
+ user.name_changed? # => true
194
+ user.name_not_changed
195
+ user.name_changed? # => false
196
+ CouchPotato.database.save_document user # does nothing as no attributes are dirty
197
+
198
+
199
+ #### Object validations
200
+
201
+ Couch Potato by default uses ActiveModel for validation
202
+
203
+ class User
204
+ property :name
205
+ validates_presence_of :name
206
+ end
207
+
208
+ user = User.new
209
+ user.valid? # => false
210
+ user.errors[:name] # => ['can't be blank']
211
+
212
+ If you want you can use [Validatable](http://validatable.rubyforge.org/) by setting `CouchPotato::Config.validation(http://validatable.rubyforge.org/)\_framework = :validatable`
213
+
214
+ #### Finding stuff / views / lists
215
+
216
+ 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:
217
+
218
+ class User
219
+ include CouchPotato::Persistence
220
+ property :name
221
+
222
+ view :all, :key => :created_at
223
+ end
224
+
225
+ This will create a view called "all" in the "user" design document with a map function that emits "created_at" for every user document.
226
+
227
+ CouchPotato.database.view User.all
228
+
229
+ This will load all user documents in your database sorted by created_at.
230
+
231
+ CouchPotato.database.view User.all(:key => (Time.now- 10)..(Time.now), :descending => true)
232
+
233
+ Any options you pass in will be passed onto CouchDB.
234
+
235
+ Composite keys are also possible:
236
+
237
+ class User
238
+ property :name
239
+
240
+ view :all, :key => [:created_at, :name]
241
+ end
242
+
243
+ You can also pass conditions as a JavaScript string:
244
+
245
+ class User
246
+ property :name
247
+
248
+ view :completed, :key => :name, :conditions => 'doc.completed === true'
249
+ end
250
+
251
+ 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:
252
+
253
+ 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):
254
+
255
+ class User
256
+ property :name
257
+ property :bio
258
+
259
+ view :all, :key => :created_at, :properties => [:name], :type => :properties
260
+ end
261
+
262
+ CouchPotato.database.view(User.everyone).first.name # => "joe"
263
+ CouchPotato.database.view(User.everyone).first.bio # => nil
264
+
265
+ CouchPotato.database.first(User.everyone).name # => "joe" # convenience function, returns nil if nothing found
266
+ CouchPotato.database.first!(User.everyone) # would raise CouchPotato::NotFound if nothing was found
267
+
268
+ If you want Rails to automatically show a 404 page when `CouchPotato::NotFound` is raised add this to your `ApplicationController`:
269
+
270
+ rescue_from CouchPotato::NotFound do
271
+ render(:file => 'public/404.html', :status => :not_found, :layout => false)
272
+ end
273
+
274
+ You can also pass in custom map/reduce functions with the custom view spec:
275
+
276
+ class User
277
+ view :all, :map => "function(doc) { emit(doc.created_at, null)}", :include_docs => true, :type => :custom
278
+ end
279
+
280
+ If you don't want the results to be converted into models the raw view is your friend:
281
+
282
+ class User
283
+ view :all, :map => "function(doc) { emit(doc.created_at, doc.name)}", :type => :raw
284
+ end
285
+
286
+ 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'}]}
287
+
288
+ To process this raw data you can also pass in a results filter:
289
+
290
+ class User
291
+ view :all, :map => "function(doc) { emit(doc.created_at, doc.name)}", :type => :raw, :results_filter => lambda {|results| results['rows'].map{|row| row['value']}}
292
+ end
293
+
294
+ In this case querying the view would only return the emitted value for each row.
295
+
296
+ 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.
297
+
298
+ ##### Lists
299
+
300
+ CouchPotato also supports [CouchDB lists](http://books.couchdb.org/relax/design-documents/lists). With lists you can process the result of a view query with another JavaScript function. This can be useful for example if you want to filter your results, or add some data to each document.
301
+
302
+ Defining a list works similarly to views:
303
+
304
+ class User
305
+ include CouchPotato::Persistence
306
+
307
+ property :first_name
308
+ view :with_full_name, key: first_namne, list: :add_last_name
309
+ view :all, key: :first_name
310
+
311
+ list :add_last_name, <<-JS
312
+ function(head, req) {
313
+ var row;
314
+ send('{"rows": [');
315
+ while(row = getRow()) {
316
+ row.doc.name = row.doc.first_name + ' doe';
317
+ send(JSON.stringify(row));
318
+ };
319
+ send(']}');
320
+ }
321
+ JS
322
+ end
323
+
324
+ CouchPotato.database.save User.new(first_name: 'joe')
325
+ CouchPotato.database.view(User.with_full_name).first.name # => 'joe doe'
326
+
327
+ You can also pass in the list at query time:
328
+
329
+ CouchPotato.database.view(User.all(list: :add_last_name))
330
+
331
+ #### Associations
332
+
333
+ Not supported. Not sure if they ever will be. You can implement those yourself using views and custom methods on your models.
334
+
335
+ #### Callbacks
336
+
337
+ Couch Potato supports the usual lifecycle callbacks known from ActiveRecord:
338
+
339
+ class User
340
+ include CouchPotato::Persistence
341
+
342
+ before_create :do_something_before_create
343
+ before_update {|user| user.do_something_on_update}
344
+ end
345
+
346
+ 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.
347
+
348
+ 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.
349
+
350
+ 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.
351
+
352
+ #### Attachments
353
+
354
+ There is basic attachment support: if you want to store any attachments set the _attachments attribute of a model before saving like this:
355
+
356
+ class User
357
+ include CouchPotato::Persistence
358
+ end
359
+
360
+ data = File.read('some_file.text') # or from upload
361
+ user = User.new
362
+ user._attachments = {'photo' => {'data' => data, 'content_type' => 'image/png'}}
363
+
364
+ 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:
365
+
366
+ user_reloaded = CouchPotato.database.load user.id
367
+ user_reloaded._attachments['photo'] # => {'content_type' => 'image/png', 'length' => 37861}
368
+
369
+ #### Testing
370
+
371
+ 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:
372
+
373
+ class User
374
+ include CouchPotato::Persistence
375
+ end
376
+
377
+ # RSpec
378
+ describe 'save a user' do
379
+ it 'should save' do
380
+ couchrest_db = stub 'couchrest_db',
381
+ database = CouchPotato::Database.new couchrest_db
382
+ user = User.new
383
+ couchrest_db.should_receive(:save_doc).with(...)
384
+ database.save_document user
385
+ end
386
+ end
387
+
388
+ 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.
389
+
390
+ For stubbing out the database couch potato offers some helpers:
391
+
392
+ class Comment
393
+ view :by_commenter_id, :key => :commenter_id
394
+ end
395
+
396
+ # RSpec
397
+ require 'couch_potato/rspec'
398
+
399
+ db = stub_db # stubs CouchPotato.database
400
+ db.stub_view(Comment, :by_commenter_id).with('23').and_return([:comment1, :comment2])
401
+
402
+ CouchPotato.database.view(Comment.by_commenter_id('23)) # => [:comment1, :comment2]
403
+
404
+ ##### Testing map/reduce functions
405
+
406
+ Couch Potato provides custom RSpec matchers for testing the map and reduce functions of your views. For example you can do this:
407
+
408
+ Class User
409
+ include CouchPotato::Persistence
410
+
411
+ view :by_name, :key => :name
412
+ view :by_age, :key => :age
413
+ end
414
+
415
+ #RSpec
416
+ require 'couch_potato/rspec'
417
+
418
+ describe User, 'by_name' do
419
+ it "should map users to their name" do
420
+ User.by_name.should map(User.new(:name => 'bill', :age => 23)).to(['bill', null])
421
+ end
422
+
423
+ it "should reduce the users to the sum of their age" do
424
+ User.by_age.should reduce([], [[23], [22]]).to(45)
425
+ end
426
+
427
+ it "should rereduce" do
428
+ User.by_age.should rereduce([], [[23], [22]]).to(45)
429
+ end
430
+ end
431
+
432
+ This will actually run your map/reduce functions in a JavaScript interpreter, passing the arguments as JSON and converting the results back to Ruby. For more examples see the [spec](http://github.com/langalex/couch_potato/blob/master/spec/unit/rspec_matchers_spec.rb).
433
+
434
+ 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.
435
+
436
+ ### Helping out
437
+
438
+ Please fix bugs, add more specs, implement new features by forking the github repo at http://github.com/langalex/couch_potato.
439
+
440
+ Issues are tracked at github: http://github.com/langalex/couch_potato/issues
441
+
442
+ There is a mailing list, just write to: couchpotato@librelist.com
443
+
444
+ 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
445
+
446
+ I will only accept patches that are covered by specs - sorry.
447
+
448
+ ### Contact
449
+
450
+ If you have any questions/suggestions etc. please contact me at alex at upstream-berlin.com or @langalex on twitter.