couch_potato-rails2 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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