couch_potato 0.2.12

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 (52) hide show
  1. data/CHANGES.md +15 -0
  2. data/MIT-LICENSE.txt +19 -0
  3. data/README.md +295 -0
  4. data/VERSION.yml +4 -0
  5. data/init.rb +3 -0
  6. data/lib/core_ext/date.rb +10 -0
  7. data/lib/core_ext/object.rb +5 -0
  8. data/lib/core_ext/string.rb +19 -0
  9. data/lib/core_ext/symbol.rb +15 -0
  10. data/lib/core_ext/time.rb +11 -0
  11. data/lib/couch_potato.rb +40 -0
  12. data/lib/couch_potato/database.rb +106 -0
  13. data/lib/couch_potato/persistence.rb +98 -0
  14. data/lib/couch_potato/persistence/attachments.rb +31 -0
  15. data/lib/couch_potato/persistence/callbacks.rb +60 -0
  16. data/lib/couch_potato/persistence/dirty_attributes.rb +49 -0
  17. data/lib/couch_potato/persistence/ghost_attributes.rb +22 -0
  18. data/lib/couch_potato/persistence/json.rb +46 -0
  19. data/lib/couch_potato/persistence/magic_timestamps.rb +13 -0
  20. data/lib/couch_potato/persistence/properties.rb +52 -0
  21. data/lib/couch_potato/persistence/simple_property.rb +83 -0
  22. data/lib/couch_potato/persistence/validation.rb +18 -0
  23. data/lib/couch_potato/view/base_view_spec.rb +24 -0
  24. data/lib/couch_potato/view/custom_view_spec.rb +27 -0
  25. data/lib/couch_potato/view/custom_views.rb +44 -0
  26. data/lib/couch_potato/view/model_view_spec.rb +63 -0
  27. data/lib/couch_potato/view/properties_view_spec.rb +39 -0
  28. data/lib/couch_potato/view/raw_view_spec.rb +25 -0
  29. data/lib/couch_potato/view/view_query.rb +44 -0
  30. data/rails/init.rb +7 -0
  31. data/spec/attachments_spec.rb +23 -0
  32. data/spec/callbacks_spec.rb +271 -0
  33. data/spec/create_spec.rb +22 -0
  34. data/spec/custom_view_spec.rb +149 -0
  35. data/spec/default_property_spec.rb +34 -0
  36. data/spec/destroy_spec.rb +29 -0
  37. data/spec/fixtures/address.rb +9 -0
  38. data/spec/fixtures/person.rb +6 -0
  39. data/spec/property_spec.rb +101 -0
  40. data/spec/spec.opts +4 -0
  41. data/spec/spec_helper.rb +29 -0
  42. data/spec/unit/attributes_spec.rb +48 -0
  43. data/spec/unit/callbacks_spec.rb +33 -0
  44. data/spec/unit/couch_potato_spec.rb +20 -0
  45. data/spec/unit/create_spec.rb +58 -0
  46. data/spec/unit/customs_views_spec.rb +15 -0
  47. data/spec/unit/database_spec.rb +50 -0
  48. data/spec/unit/dirty_attributes_spec.rb +131 -0
  49. data/spec/unit/string_spec.rb +13 -0
  50. data/spec/unit/view_query_spec.rb +9 -0
  51. data/spec/update_spec.rb +40 -0
  52. metadata +153 -0
@@ -0,0 +1,83 @@
1
+ module CouchPotato
2
+ module Persistence
3
+ class SimpleProperty #:nodoc:
4
+ attr_accessor :name, :type
5
+
6
+ def initialize(owner_clazz, name, options = {})
7
+ self.name = name
8
+ self.type = options[:type]
9
+
10
+ define_accessors accessors_module_for(owner_clazz), name, options
11
+ end
12
+
13
+ def build(object, json)
14
+ value = json[name.to_s] || json[name.to_sym]
15
+ typecast_value = if type
16
+ type.json_create value
17
+ else
18
+ value
19
+ end
20
+ object.send "#{name}=", typecast_value
21
+ end
22
+
23
+ def dirty?(object)
24
+ object.send("#{name}_changed?")
25
+ end
26
+
27
+ def save(object)
28
+
29
+ end
30
+
31
+ def destroy(object)
32
+
33
+ end
34
+
35
+ def serialize(json, object)
36
+ json[name] = object.send name
37
+ end
38
+
39
+ private
40
+
41
+ def accessors_module_for(clazz)
42
+ unless clazz.const_defined?('AccessorMethods')
43
+ accessors_module = clazz.const_set('AccessorMethods', Module.new)
44
+ clazz.send(:include, accessors_module)
45
+ end
46
+ clazz.const_get('AccessorMethods')
47
+ end
48
+
49
+ def define_accessors(base, name, options)
50
+ base.class_eval do
51
+ attr_reader "#{name}_was"
52
+
53
+ define_method "#{name}" do
54
+ value = self.instance_variable_get("@#{name}")
55
+ if value.blank? && options[:default]
56
+ default = clone_attribute(options[:default])
57
+ self.instance_variable_set("@#{name}", default)
58
+ default
59
+ else
60
+ value
61
+ end
62
+ end
63
+
64
+ define_method "#{name}=" do |value|
65
+ self.instance_variable_set("@#{name}", value)
66
+ end
67
+
68
+ define_method "#{name}?" do
69
+ !self.send(name).nil? && !self.send(name).try(:blank?)
70
+ end
71
+
72
+ define_method "#{name}_changed?" do
73
+ !self.instance_variable_get("@#{name}_not_changed") && self.send(name) != self.send("#{name}_was")
74
+ end
75
+
76
+ define_method "#{name}_not_changed" do
77
+ self.instance_variable_set("@#{name}_not_changed", true)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,18 @@
1
+ require 'validatable'
2
+
3
+ module CouchPotato
4
+ module Persistence
5
+ module Validation
6
+ def self.included(base)
7
+ base.send :include, Validatable
8
+ base.class_eval do
9
+ # Override the validate method to first run before_validation callback
10
+ def valid?
11
+ self.run_callbacks :before_validation
12
+ super
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ module CouchPotato
2
+ module View
3
+ class BaseViewSpec
4
+ attr_reader :reduce_function, :design_document, :view_name, :view_parameters, :klass, :options
5
+ private :klass, :options
6
+
7
+ def initialize(klass, view_name, options, view_parameters)
8
+ @klass = klass
9
+ @design_document = klass.to_s.underscore
10
+ @view_name = view_name
11
+ @options = options
12
+ @view_parameters = {}
13
+ [:group, :include_docs, :descending, :group_level, :limit].each do |key|
14
+ @view_parameters[key] = options[key] if options.include?(key)
15
+ end
16
+ @view_parameters.merge!(view_parameters)
17
+ end
18
+
19
+ def process_results(results)
20
+ results
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ module CouchPotato
2
+ module View
3
+ # a view for custom map/reduce functions that still returns model instances
4
+ #
5
+ # example:
6
+ # view :my_custom_view, :map => "function(doc) { emit(doc._id, null); }", :include_docs => true, :type => :custom, :reduce => nil
7
+ class CustomViewSpec < BaseViewSpec
8
+ def map_function
9
+ options[:map]
10
+ end
11
+
12
+ def reduce_function
13
+ options[:reduce]
14
+ end
15
+
16
+ def view_parameters
17
+ {:include_docs => options[:include_docs] || false}.merge(super)
18
+ end
19
+
20
+ def process_results(results)
21
+ results['rows'].map do |row|
22
+ klass.json_create row['doc'] || row['value'].merge(:_id => row['id'] || row['key'])
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,44 @@
1
+ require File.dirname(__FILE__) + '/base_view_spec'
2
+ require File.dirname(__FILE__) + '/model_view_spec'
3
+ require File.dirname(__FILE__) + '/properties_view_spec'
4
+ require File.dirname(__FILE__) + '/custom_view_spec'
5
+ require File.dirname(__FILE__) + '/raw_view_spec'
6
+
7
+
8
+ module CouchPotato
9
+ module View
10
+ module CustomViews
11
+
12
+ def self.included(base)
13
+ base.extend ClassMethods
14
+ end
15
+
16
+ module ClassMethods
17
+ # Declare a CouchDB view, for examples on how to use see the *ViewSpec classes in CouchPotato::View
18
+ def views
19
+ @views ||= {}
20
+ end
21
+
22
+ def execute_view(view_name, view_parameters)
23
+ view_spec_class(views[view_name][:type]).new(self, view_name, views[view_name], view_parameters)
24
+ end
25
+
26
+ def view(view_name, options)
27
+ view_name = view_name.to_s
28
+ views[view_name] = options
29
+ method_str = "def #{view_name}(view_parameters = {}); execute_view(\"#{view_name}\", view_parameters); end"
30
+ self.instance_eval(method_str)
31
+ end
32
+
33
+ def view_spec_class(type)
34
+ if type && type.is_a?(Class)
35
+ type
36
+ else
37
+ name = type.nil? ? 'Model' : type.to_s.camelize
38
+ CouchPotato::View.const_get("#{name}ViewSpec")
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,63 @@
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
+ class ModelViewSpec < BaseViewSpec
9
+
10
+ def view_parameters
11
+ _super = super
12
+ if _super[:reduce]
13
+ _super
14
+ else
15
+ {:include_docs => true, :reduce => false}.merge(_super)
16
+ end
17
+ end
18
+
19
+ def map_function
20
+ "function(doc) {
21
+ if(doc.ruby_class && doc.ruby_class == '#{@klass.name}') {
22
+ emit(#{formatted_key(key)}, null);
23
+ }
24
+ }"
25
+ end
26
+
27
+ def reduce_function
28
+ "function(key, values) {
29
+ return values.length;
30
+ }"
31
+ end
32
+
33
+ def process_results(results)
34
+ if count?
35
+ results['rows'].first.try(:[], 'value') || 0
36
+ else
37
+ results['rows'].map do |row|
38
+ klass.json_create row['doc']
39
+ end
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def count?
46
+ view_parameters[:reduce]
47
+ end
48
+
49
+ def key
50
+ options[:key]
51
+ end
52
+
53
+ def formatted_key(key)
54
+ if key.is_a? Array
55
+ '[' + key.map{|attribute| formatted_key(attribute)}.join(', ') + ']'
56
+ else
57
+ "doc['#{key}']"
58
+ end
59
+ end
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,39 @@
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
+ "function(doc) {
10
+ if(doc.ruby_class && doc.ruby_class == '#{@klass.name}') {
11
+ emit(#{formatted_key(key)}, #{properties_for_map(properties)});
12
+ }
13
+ }"
14
+ end
15
+
16
+ def process_results(results)
17
+ results['rows'].map do |row|
18
+ klass.json_create row['value'].merge(:_id => row['id'])
19
+ end
20
+ end
21
+
22
+ def view_parameters
23
+ {:include_docs => false}.merge(super)
24
+ end
25
+
26
+ private
27
+
28
+ def properties
29
+ options[:properties]
30
+ end
31
+
32
+ def properties_for_map(properties)
33
+ '{' + properties.map{|p| "#{p}: doc.#{p}"}.join(', ') + '}'
34
+ end
35
+
36
+
37
+ end
38
+ end
39
+ 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,44 @@
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_name, map_function, reduce_function = nil)
6
+ @database = couchrest_database
7
+ @design_document_name = design_document_name
8
+ @view_name = view_name
9
+ @map_function = map_function
10
+ @reduce_function = reduce_function
11
+ end
12
+
13
+ def query_view!(parameters = {})
14
+ begin
15
+ query_view parameters
16
+ rescue RestClient::ResourceNotFound# => e
17
+ create_view
18
+ retry
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def create_view
25
+ design_doc = @database.get "_design/#{@design_document_name}" rescue nil
26
+ design_doc ||= {'views' => {}, "_id" => "_design/#{@design_document_name}"}
27
+ design_doc['views'][@view_name.to_s] = {
28
+ 'map' => @map_function,
29
+ 'reduce' => @reduce_function
30
+ }
31
+ @database.save_doc(design_doc)
32
+ end
33
+
34
+ def query_view(parameters)
35
+ @database.view view_url, parameters
36
+ end
37
+
38
+ def view_url
39
+ "#{@design_document_name}/#{@view_name}"
40
+ end
41
+
42
+ end
43
+ end
44
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,7 @@
1
+ # this is for rails only
2
+
3
+ require File.dirname(__FILE__) + '/../lib/couch_potato'
4
+
5
+ CouchPotato::Config.database_name = YAML::load(File.read(Rails.root.to_s + '/config/couchdb.yml'))[RAILS_ENV]
6
+
7
+ RAILS_DEFAULT_LOGGER.info "** couch_potato: initialized from #{__FILE__}"
@@ -0,0 +1,23 @@
1
+ require File.dirname(__FILE__) + '/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.should == {"body" => {"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,271 @@
1
+ require File.dirname(__FILE__) + '/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
+ attr_accessor :lambda_works
26
+ before_create lambda {|model| model.lambda_works = true }
27
+
28
+ def callbacks
29
+ @callbacks ||= []
30
+ end
31
+
32
+ private
33
+
34
+ def method_callback_with_argument(db)
35
+ db.view CallbackRecorder.all
36
+ end
37
+
38
+ end
39
+
40
+ describe "multiple callbacks at once" do
41
+
42
+ class Monkey
43
+ include CouchPotato::Persistence
44
+ attr_accessor :eaten_banana, :eaten_apple
45
+
46
+ before_create :eat_apple, :eat_banana
47
+
48
+ private
49
+
50
+ def eat_banana
51
+ self.eaten_banana = true
52
+ end
53
+
54
+ def eat_apple
55
+ self.eaten_apple = true
56
+ end
57
+ end
58
+ it "should run all callback methods given to the callback method call" do
59
+ monkey = Monkey.new
60
+ CouchPotato.database.save_document! monkey
61
+ monkey.eaten_banana.should be_true
62
+ monkey.eaten_apple.should be_true
63
+ end
64
+ end
65
+
66
+ describe 'create callbacks' do
67
+
68
+ before(:each) do
69
+ @recorder = CallbackRecorder.new
70
+ couchrest_database = stub 'couchrest_database', :save_doc => {'id' => '1', 'rev' => '2'}, :view => {'rows' => []}, :info => nil
71
+ @db = CouchPotato::Database.new(couchrest_database)
72
+ end
73
+
74
+ describe "successful create" do
75
+ before(:each) do
76
+ @recorder.required_property = 1
77
+ end
78
+
79
+ it "should call before_validation" do
80
+ @recorder.valid?
81
+ @recorder.callbacks.should include(:before_validation)
82
+ end
83
+
84
+ it "should call before_validation_on_create" do
85
+ @db.save_document! @recorder
86
+ @recorder.callbacks.should include(:before_validation_on_create)
87
+ end
88
+
89
+ it "should call before_validation_on_save" do
90
+ @db.save_document! @recorder
91
+ @recorder.callbacks.should include(:before_validation_on_save)
92
+ end
93
+
94
+ it "should call before_save" do
95
+ @db.save_document! @recorder
96
+ @recorder.callbacks.should include(:before_save)
97
+ end
98
+
99
+ it "should call after_save" do
100
+ @db.save_document! @recorder
101
+ @recorder.callbacks.should include(:after_save)
102
+ end
103
+
104
+ it "should call before_create" do
105
+ @db.save_document! @recorder
106
+ @recorder.callbacks.should include(:before_create)
107
+ end
108
+
109
+ it "should call after_create" do
110
+ @db.save_document! @recorder
111
+ @recorder.callbacks.should include(:after_create)
112
+ end
113
+
114
+ end
115
+
116
+ describe "failed create" do
117
+
118
+ it "should call before_validation" do
119
+ @recorder.valid?
120
+ @recorder.callbacks.should include(:before_validation)
121
+ end
122
+
123
+ it "should call before_validation_on_create" do
124
+ @db.save_document @recorder
125
+ @recorder.callbacks.should include(:before_validation_on_create)
126
+ end
127
+
128
+ it "should call before_validation_on_save" do
129
+ @db.save_document @recorder
130
+ @recorder.callbacks.should include(:before_validation_on_save)
131
+ end
132
+
133
+ it "should not call before_save" do
134
+ @db.save_document @recorder
135
+ @recorder.callbacks.should_not include(:before_save)
136
+ end
137
+
138
+ it "should not call after_save" do
139
+ @db.save_document @recorder
140
+ @recorder.callbacks.should_not include(:after_save)
141
+ end
142
+
143
+ it "should not call before_create" do
144
+ @db.save_document @recorder
145
+ @recorder.callbacks.should_not include(:before_create)
146
+ end
147
+
148
+ it "should not call after_create" do
149
+ @db.save_document @recorder
150
+ @recorder.callbacks.should_not include(:after_create)
151
+ end
152
+ end
153
+ end
154
+
155
+ describe "update callbacks" do
156
+
157
+ before(:each) do
158
+ @recorder = CallbackRecorder.new :required_property => 1
159
+
160
+ couchrest_database = stub 'couchrest_database', :save_doc => {'id' => '1', 'rev' => '2'}, :view => {'rows' => []}, :info => nil
161
+ @db = CouchPotato::Database.new(couchrest_database)
162
+ @db.save_document! @recorder
163
+
164
+ @recorder.required_property = 2
165
+ @recorder.callbacks.clear
166
+ end
167
+
168
+ describe "successful update" do
169
+
170
+ before(:each) do
171
+ @db.save_document! @recorder
172
+ end
173
+
174
+ it "should call before_validation" do
175
+ @recorder.callbacks.should include(:before_validation)
176
+ end
177
+
178
+ it "should call before_validation_on_update" do
179
+ @recorder.callbacks.should include(:before_validation_on_update)
180
+ end
181
+
182
+ it "should call before_validation_on_save" do
183
+ @recorder.callbacks.should include(:before_validation_on_save)
184
+ end
185
+
186
+ it "should call before_save" do
187
+ @recorder.callbacks.should include(:before_save)
188
+ end
189
+
190
+ it "should call after_save" do
191
+ @recorder.callbacks.should include(:after_save)
192
+ end
193
+
194
+ it "should call before_update" do
195
+ @recorder.callbacks.should include(:before_update)
196
+ end
197
+
198
+ it "should call after_update" do
199
+ @recorder.callbacks.should include(:after_update)
200
+ end
201
+
202
+ end
203
+
204
+ describe "failed update" do
205
+
206
+ before(:each) do
207
+ @recorder.required_property = nil
208
+ @db.save_document @recorder
209
+ end
210
+
211
+ it "should call before_validation" do
212
+ @recorder.callbacks.should include(:before_validation)
213
+ end
214
+
215
+ it "should call before_validation_on_update" do
216
+ @recorder.callbacks.should include(:before_validation_on_update)
217
+ end
218
+
219
+ it "should call before_validation_on_save" do
220
+ @recorder.callbacks.should include(:before_validation_on_save)
221
+ end
222
+
223
+ it "should not call before_save" do
224
+ @recorder.callbacks.should_not include(:before_save)
225
+ end
226
+
227
+ it "should not call after_save" do
228
+ @recorder.callbacks.should_not include(:after_save)
229
+ end
230
+
231
+ it "should not call before_update" do
232
+ @recorder.callbacks.should_not include(:before_update)
233
+ end
234
+
235
+ it "should not call after_update" do
236
+ @recorder.callbacks.should_not include(:after_update)
237
+ end
238
+
239
+ end
240
+
241
+ end
242
+
243
+ describe "destroy callbacks" do
244
+
245
+ before(:each) do
246
+ @recorder = CallbackRecorder.new :required_property => 1
247
+ couchrest_database = stub 'couchrest_database', :save_doc => {'id' => '1', 'rev' => '2'}, :delete_doc => nil, :view => {'rows' => []}, :info => nil
248
+ @db = CouchPotato::Database.new(couchrest_database)
249
+ @db.save_document! @recorder
250
+
251
+ @recorder.callbacks.clear
252
+ end
253
+
254
+ it "should call before_destroy" do
255
+ @db.destroy_document @recorder
256
+ @recorder.callbacks.should include(:before_destroy)
257
+ end
258
+
259
+ it "should call after_destroy" do
260
+ @db.destroy_document @recorder
261
+ @recorder.callbacks.should include(:after_destroy)
262
+ end
263
+ end
264
+
265
+ describe "lambda callbacks" do
266
+ it "should run the lambda" do
267
+ recorder = CallbackRecorder.new
268
+ recorder.run_callbacks :before_create
269
+ recorder.lambda_works.should be_true
270
+ end
271
+ end