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,52 @@
1
+ require 'couch_potato/view/base_view_spec'
2
+ require 'couch_potato/view/model_view_spec'
3
+ require 'couch_potato/view/properties_view_spec'
4
+ require 'couch_potato/view/custom_view_spec'
5
+ require 'couch_potato/view/raw_view_spec'
6
+
7
+
8
+ module CouchPotato
9
+ module View
10
+ module CustomViews
11
+
12
+ def self.included(base) #:nodoc:
13
+ base.extend ClassMethods
14
+ end
15
+
16
+ module ClassMethods
17
+ def views(view_name = nil) #:nodoc:
18
+ if view_name
19
+ _find_view(view_name)
20
+ else
21
+ @views ||= {}
22
+ end
23
+ end
24
+
25
+ def execute_view(view_name, view_parameters) #:nodoc:
26
+ view_spec_class(views(view_name)[:type]).new(self, view_name, views(view_name), view_parameters)
27
+ end
28
+
29
+ # Declare a CouchDB view, for examples on how to use see the *ViewSpec classes in CouchPotato::View
30
+ def view(view_name, options)
31
+ view_name = view_name.to_s
32
+ views[view_name] = options
33
+ method_str = "def #{view_name}(view_parameters = {}); execute_view(\"#{view_name}\", view_parameters); end"
34
+ self.instance_eval(method_str)
35
+ end
36
+
37
+ def view_spec_class(type) #:nodoc:
38
+ if type && type.is_a?(Class)
39
+ type
40
+ else
41
+ name = type.nil? ? 'Model' : type.to_s.camelize
42
+ CouchPotato::View.const_get("#{name}ViewSpec")
43
+ end
44
+ end
45
+
46
+ def _find_view(view) #:nodoc:
47
+ (@views && @views[view]) || (superclass._find_view(view) if superclass.respond_to?(:_find_view))
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,23 @@
1
+ module CouchPotato
2
+ module View
3
+ module Lists
4
+ def self.included(base)
5
+ base.send :extend, ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def list(name, function)
10
+ lists[name] = function
11
+ end
12
+
13
+ def lists(name = nil)
14
+ if name.nil?
15
+ @lists ||= {}
16
+ else
17
+ (@lists && @lists[name]) || (superclass.lists(name) if superclass.respond_to?(:lists))
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,75 @@
1
+ module CouchPotato
2
+ module View
3
+ # A view to return model instances by searching its properties.
4
+ # If you pass reduce => true will count instead
5
+ #
6
+ # example:
7
+ # view :my_view, :key => :name
8
+ #
9
+ # in addition you can pass in conditions as a javascript string
10
+ # view :my_view_only_completed, :key => :name, :conditions => 'doc.completed = true'
11
+ class ModelViewSpec < BaseViewSpec
12
+
13
+ def view_parameters
14
+ _super = super
15
+ if _super[:reduce]
16
+ _super
17
+ else
18
+ {:include_docs => true, :reduce => false}.merge(_super)
19
+ end
20
+ end
21
+
22
+ def map_function
23
+ map_body do
24
+ "emit(#{formatted_key(key)}, 1);"
25
+ end
26
+ end
27
+
28
+ def reduce_function
29
+ "function(key, values) {
30
+ return sum(values);
31
+ }"
32
+ end
33
+
34
+ def process_results(results)
35
+ if count?
36
+ results['rows'].first.try(:[], 'value') || 0
37
+ else
38
+ results['rows'].map { |row| row['doc'] || row['id'] }
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def map_body(&block)
45
+ "function(doc) {
46
+ if(doc.#{JSON.create_id} && doc.#{JSON.create_id} == '#{@klass.name}'#{conditions_js}) {
47
+ " + yield + "
48
+ }
49
+ }"
50
+
51
+ end
52
+
53
+ def conditions_js
54
+ " && (#{options[:conditions]})" if options[:conditions]
55
+ end
56
+
57
+ def count?
58
+ view_parameters[:reduce]
59
+ end
60
+
61
+ def key
62
+ options[:key]
63
+ end
64
+
65
+ def formatted_key(key)
66
+ if key.is_a? Array
67
+ '[' + key.map{|attribute| formatted_key(attribute)}.join(', ') + ']'
68
+ else
69
+ "doc['#{key}']"
70
+ end
71
+ end
72
+
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,47 @@
1
+ module CouchPotato
2
+ module View
3
+ # A view to return model instances with only some properties poulated by searching its properties, e.g. for very large documents where you are only interested in some of their data
4
+ #
5
+ # example:
6
+ # view :my_view, :key => :name, :properties => [:name, :author], :type => :properties
7
+ class PropertiesViewSpec < ModelViewSpec
8
+ def map_function
9
+ map_body do
10
+ "emit(#{formatted_key(key)}, #{properties_for_map(properties)});"
11
+ end
12
+ end
13
+
14
+ def reduce_function
15
+ <<-JS
16
+ function(key, values, rereduce) {
17
+ if(rereduce) {
18
+ return sum(values);
19
+ } else {
20
+ return values.length;
21
+ }
22
+ }
23
+ JS
24
+ end
25
+
26
+ def process_results(results)
27
+ results['rows'].map do |row|
28
+ klass.json_create row['value'].merge(:_id => row['id'])
29
+ end
30
+ end
31
+
32
+ def view_parameters
33
+ {:include_docs => false}.merge(super)
34
+ end
35
+
36
+ private
37
+
38
+ def properties
39
+ options[:properties]
40
+ end
41
+
42
+ def properties_for_map(properties)
43
+ '{' + properties.map{|p| "#{p}: doc.#{p}"}.join(', ') + '}'
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,25 @@
1
+ module CouchPotato
2
+ module View
3
+ # A view for custom map/reduce functions that returns the raw data fromcouchdb
4
+ #
5
+ # example:
6
+ # view :my_custom_view, :map => "function(doc) { emit(doc._id, null); }", :type => :raw, :reduce => nil
7
+ # optionally you can pass in a results filter which you can use to process the raw couchdb results before returning them
8
+ #
9
+ # example:
10
+ # view :my_custom_view, :map => "function(doc) { emit(doc._id, null); }", :type => :raw, :results_filter => lambda{|results| results['rows].map{|row| row['value']}}
11
+ class RawViewSpec < BaseViewSpec
12
+ def map_function
13
+ options[:map]
14
+ end
15
+
16
+ def process_results(results)
17
+ options[:results_filter] ? options[:results_filter].call(results) : results
18
+ end
19
+
20
+ def reduce_function
21
+ options[:reduce]
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,82 @@
1
+ module CouchPotato
2
+ module View
3
+ # Used to query views (and create them if they don't exist). Usually you won't have to use this class directly. Instead it is used internally by the CouchPotato::Database.view method.
4
+ class ViewQuery
5
+ def initialize(couchrest_database, design_document_name, view, list = nil)
6
+ @database = couchrest_database
7
+ @design_document_name = design_document_name
8
+ @view_name = view.keys[0]
9
+ @map_function = view.values[0][:map]
10
+ @reduce_function = view.values[0][:reduce]
11
+ if list
12
+ @list_function = list.values[0]
13
+ @list_name = list.keys[0]
14
+ end
15
+ end
16
+
17
+ def query_view!(parameters = {})
18
+ update_view unless view_has_been_updated?
19
+ begin
20
+ query_view parameters
21
+ rescue RestClient::ResourceNotFound
22
+ update_view
23
+ retry
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def update_view
30
+ design_doc = @database.get "_design/#{@design_document_name}" rescue nil
31
+ original_views = design_doc && design_doc['views'].dup
32
+ original_lists = design_doc && design_doc['lists'] && design_doc['lists'].dup
33
+ view_updated unless design_doc.nil?
34
+ design_doc ||= empty_design_document
35
+ design_doc['views'][@view_name.to_s] = view_functions
36
+ if @list_function
37
+ design_doc['lists'] ||= {}
38
+ design_doc['lists'][@list_name.to_s] = @list_function
39
+ end
40
+ @database.save_doc(design_doc) if original_views != design_doc['views'] || original_lists != design_doc['lists']
41
+ end
42
+
43
+ def view_functions
44
+ if @reduce_function
45
+ {'map' => @map_function, 'reduce' => @reduce_function}
46
+ else
47
+ {'map' => @map_function}
48
+ end
49
+ end
50
+
51
+ def empty_design_document
52
+ {'views' => {}, 'lists' => {}, "_id" => "_design/#{@design_document_name}", "language" => "javascript"}
53
+ end
54
+
55
+ def view_has_been_updated?
56
+ updated_views[[@design_document_name, @view_name]]
57
+ end
58
+
59
+ def view_updated
60
+ updated_views[[@design_document_name, @view_name]] = true
61
+ end
62
+
63
+ def updated_views
64
+ @@updated_views ||= {}
65
+ @@updated_views
66
+ end
67
+
68
+ def query_view(parameters)
69
+ if @list_name
70
+ CouchRest.get CouchRest.paramify_url(CouchPotato.full_url_to_database + "/_design/#{@design_document_name}/_list/#{@list_name}/#{@view_name}", parameters)
71
+ else
72
+ @database.view view_url, parameters
73
+ end
74
+ end
75
+
76
+ def view_url
77
+ "#{@design_document_name}/#{@view_name}"
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,4 @@
1
+ # this is for rails only
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/couch_potato')
4
+ Rails.logger.info "** couch_potato: initialized from #{__FILE__}"
@@ -0,0 +1,47 @@
1
+ module CouchPotato
2
+
3
+ module ClassReloading
4
+ private
5
+
6
+ def with_class_reloading(&block)
7
+ begin
8
+ yield
9
+ rescue ArgumentError => e
10
+ if(name = e.message.scan(/(can't find const|undefined class\/module) ([\w\:]+)/).try(:first).try(:[], 1))
11
+ eval name.gsub(/\:+$/, '')
12
+ retry
13
+ else
14
+ raise e
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ View::ViewQuery.class_eval do
21
+ include ClassReloading
22
+
23
+ def query_view_with_class_reloading(*args)
24
+ with_class_reloading do
25
+ query_view_without_class_reloading(*args)
26
+ end
27
+ end
28
+
29
+ alias_method :query_view_without_class_reloading, :query_view
30
+ alias_method :query_view, :query_view_with_class_reloading
31
+ end
32
+
33
+ Database.class_eval do
34
+ include ClassReloading
35
+
36
+ def load_document_with_class_reloading(*args)
37
+ with_class_reloading do
38
+ load_document_without_class_reloading *args
39
+ end
40
+ end
41
+
42
+ alias_method :load_document_without_class_reloading, :load_document
43
+ alias_method :load_document, :load_document_with_class_reloading
44
+ alias_method :load, :load_document
45
+ end
46
+ end
47
+
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe CouchPotato, 'attachments' do
4
+ it "should persist an attachment" do
5
+ comment = Comment.new :title => 'nil'
6
+ comment._attachments['body'] = {'data' => 'a useful comment', 'content_type' => 'text/plain'}
7
+ CouchPotato.database.save! comment
8
+ CouchPotato.couchrest_database.fetch_attachment(comment.to_hash, 'body').to_s.should == 'a useful comment'
9
+ end
10
+
11
+ it "should give me information about the attachments of a document" do
12
+ comment = Comment.new :title => 'nil'
13
+ comment._attachments = {'body' => {'data' => 'a useful comment', 'content_type' => 'text/plain'}}
14
+ CouchPotato.database.save! comment
15
+ comment_reloaded = CouchPotato.database.load comment.id
16
+ comment_reloaded._attachments["body"].should include({"content_type" => "text/plain", "stub" => true, "length" => 16})
17
+ end
18
+
19
+ it "should have an empty array for a new object" do
20
+ Comment.new._attachments.should == {}
21
+ end
22
+
23
+ end
@@ -0,0 +1,297 @@
1
+ require 'spec_helper'
2
+
3
+ class CallbackRecorder
4
+ include CouchPotato::Persistence
5
+
6
+ property :required_property
7
+
8
+ validates_presence_of :required_property
9
+
10
+ [:before_validation, :before_validation_on_create,
11
+ :before_validation_on_save, :before_validation_on_update,
12
+ :before_save, :before_create, :before_create,
13
+ :after_save, :after_create, :after_create,
14
+ :before_update, :after_update,
15
+ :before_destroy, :after_destroy
16
+ ].each do |callback|
17
+ define_method callback do
18
+ callbacks << callback
19
+ end
20
+ self.send callback, callback
21
+ end
22
+
23
+ view :all, :key => :required_property
24
+
25
+ def callbacks
26
+ @callbacks ||= []
27
+ end
28
+
29
+ private
30
+
31
+ def method_callback_with_argument(db)
32
+ db.view CallbackRecorder.all
33
+ end
34
+
35
+ end
36
+
37
+ describe "multiple callbacks at once" do
38
+
39
+ class Monkey
40
+ include CouchPotato::Persistence
41
+ attr_accessor :eaten_banana, :eaten_apple
42
+
43
+ before_create :eat_apple, :eat_banana
44
+
45
+ private
46
+
47
+ def eat_banana
48
+ self.eaten_banana = true
49
+ end
50
+
51
+ def eat_apple
52
+ self.eaten_apple = true
53
+ end
54
+ end
55
+
56
+ it "should run all callback methods given to the callback method call" do
57
+ monkey = Monkey.new
58
+ monkey.run_callbacks :create
59
+ monkey.eaten_banana.should be_true
60
+ monkey.eaten_apple.should be_true
61
+ end
62
+ end
63
+
64
+ describe 'create callbacks' do
65
+
66
+ before(:each) do
67
+ @recorder = CallbackRecorder.new
68
+ couchrest_database = stub 'couchrest_database', :save_doc => {'id' => '1', 'rev' => '2'}, :view => {'rows' => []}, :info => nil
69
+ @db = CouchPotato::Database.new(couchrest_database)
70
+ end
71
+
72
+ describe "successful create" do
73
+ before(:each) do
74
+ @recorder.required_property = 1
75
+ end
76
+
77
+ it "should call before_validation" do
78
+ @recorder.valid?
79
+ @recorder.callbacks.should include(:before_validation)
80
+ end
81
+
82
+ it "should call before_validation_on_create" do
83
+ @db.save_document! @recorder
84
+ @recorder.callbacks.should include(:before_validation_on_create)
85
+ end
86
+
87
+ it "should call before_validation_on_save" do
88
+ @db.save_document! @recorder
89
+ @recorder.callbacks.should include(:before_validation_on_save)
90
+ end
91
+
92
+ it "should call before_save" do
93
+ @db.save_document! @recorder
94
+ @recorder.callbacks.should include(:before_save)
95
+ end
96
+
97
+ it "should call after_save" do
98
+ @db.save_document! @recorder
99
+ @recorder.callbacks.should include(:after_save)
100
+ end
101
+
102
+ it "should call before_create" do
103
+ @db.save_document! @recorder
104
+ @recorder.callbacks.should include(:before_create)
105
+ end
106
+
107
+ it "should call after_create" do
108
+ @db.save_document! @recorder
109
+ @recorder.callbacks.should include(:after_create)
110
+ end
111
+
112
+ end
113
+
114
+ describe "failed create" do
115
+
116
+ it "should call before_validation" do
117
+ @recorder.valid?
118
+ @recorder.callbacks.should include(:before_validation)
119
+ end
120
+
121
+ it "should call before_validation_on_create" do
122
+ @db.save_document @recorder
123
+ @recorder.callbacks.should include(:before_validation_on_create)
124
+ end
125
+
126
+ it "should call before_validation_on_save" do
127
+ @db.save_document @recorder
128
+ @recorder.callbacks.should include(:before_validation_on_save)
129
+ end
130
+
131
+ it "should not call before_save" do
132
+ @db.save_document @recorder
133
+ @recorder.callbacks.should_not include(:before_save)
134
+ end
135
+
136
+ it "should not call after_save" do
137
+ @db.save_document @recorder
138
+ @recorder.callbacks.should_not include(:after_save)
139
+ end
140
+
141
+ it "should not call before_create" do
142
+ @db.save_document @recorder
143
+ @recorder.callbacks.should_not include(:before_create)
144
+ end
145
+
146
+ it "should not call after_create" do
147
+ @db.save_document @recorder
148
+ @recorder.callbacks.should_not include(:after_create)
149
+ end
150
+ end
151
+ end
152
+
153
+ describe "update callbacks" do
154
+
155
+ before(:each) do
156
+ @recorder = CallbackRecorder.new :required_property => 1
157
+
158
+ couchrest_database = stub 'couchrest_database', :save_doc => {'id' => '1', 'rev' => '2'}, :view => {'rows' => []}, :info => nil
159
+ @db = CouchPotato::Database.new(couchrest_database)
160
+ @db.save_document! @recorder
161
+
162
+ @recorder.required_property = 2
163
+ @recorder.callbacks.clear
164
+ end
165
+
166
+ describe "successful update" do
167
+
168
+ before(:each) do
169
+ @db.save_document! @recorder
170
+ end
171
+
172
+ it "should call before_validation" do
173
+ @recorder.callbacks.should include(:before_validation)
174
+ end
175
+
176
+ it "should call before_validation_on_update" do
177
+ @recorder.callbacks.should include(:before_validation_on_update)
178
+ end
179
+
180
+ it "should call before_validation_on_save" do
181
+ @recorder.callbacks.should include(:before_validation_on_save)
182
+ end
183
+
184
+ it "should call before_save" do
185
+ @recorder.callbacks.should include(:before_save)
186
+ end
187
+
188
+ it "should call after_save" do
189
+ @recorder.callbacks.should include(:after_save)
190
+ end
191
+
192
+ it "should call before_update" do
193
+ @recorder.callbacks.should include(:before_update)
194
+ end
195
+
196
+ it "should call after_update" do
197
+ @recorder.callbacks.should include(:after_update)
198
+ end
199
+
200
+ end
201
+
202
+ describe "failed update" do
203
+
204
+ before(:each) do
205
+ @recorder.required_property = nil
206
+ @db.save_document @recorder
207
+ end
208
+
209
+ it "should call before_validation" do
210
+ @recorder.callbacks.should include(:before_validation)
211
+ end
212
+
213
+ it "should call before_validation_on_update" do
214
+ @recorder.callbacks.should include(:before_validation_on_update)
215
+ end
216
+
217
+ it "should call before_validation_on_save" do
218
+ @recorder.callbacks.should include(:before_validation_on_save)
219
+ end
220
+
221
+ it "should not call before_save" do
222
+ @recorder.callbacks.should_not include(:before_save)
223
+ end
224
+
225
+ it "should not call after_save" do
226
+ @recorder.callbacks.should_not include(:after_save)
227
+ end
228
+
229
+ it "should not call before_update" do
230
+ @recorder.callbacks.should_not include(:before_update)
231
+ end
232
+
233
+ it "should not call after_update" do
234
+ @recorder.callbacks.should_not include(:after_update)
235
+ end
236
+
237
+ end
238
+
239
+ end
240
+
241
+ describe "destroy callbacks" do
242
+
243
+ before(:each) do
244
+ @recorder = CallbackRecorder.new :required_property => 1
245
+ couchrest_database = stub 'couchrest_database', :save_doc => {'id' => '1', 'rev' => '2'}, :delete_doc => nil, :view => {'rows' => []}, :info => nil
246
+ @db = CouchPotato::Database.new(couchrest_database)
247
+ @db.save_document! @recorder
248
+
249
+ @recorder.callbacks.clear
250
+ end
251
+
252
+ it "should call before_destroy" do
253
+ @db.destroy_document @recorder
254
+ @recorder.callbacks.should include(:before_destroy)
255
+ end
256
+
257
+ it "should call after_destroy" do
258
+ @db.destroy_document @recorder
259
+ @recorder.callbacks.should include(:after_destroy)
260
+ end
261
+ end
262
+
263
+ describe "validation callbacks" do
264
+ class ValidatedUser
265
+ include CouchPotato::Persistence
266
+
267
+ property :name
268
+ before_validation :check_name
269
+ validates_presence_of :name
270
+
271
+ def check_name
272
+ errors.add(:name, 'should be Paul') unless name == "Paul"
273
+ end
274
+ end
275
+
276
+ it "should keep error messages set in custom before_validation filters" do
277
+ user = ValidatedUser.new(:name => "john")
278
+ user.valid?.should == false
279
+ user.errors.on(:name).should == "should be Paul"
280
+ end
281
+
282
+ it "should combine the errors from validations and callbacks" do
283
+ user = ValidatedUser.new(:name => nil)
284
+ user.valid?.should == false
285
+ user.errors.on(:name).any? {|msg| msg =~ /can't be (empty|blank)/ }.should == true
286
+ user.errors.on(:name).any? {|msg| msg == "should be Paul" }.should == true
287
+ user.errors.on(:name).size.should == 2
288
+ end
289
+
290
+ it "should clear the errors on subsequent calls to valid?" do
291
+ user = ValidatedUser.new(:name => nil)
292
+ user.valid?.should == false
293
+ user.name = 'Paul'
294
+ user.valid?.should == true
295
+ user.errors.on(:name).should == nil
296
+ end
297
+ end