jnunemaker-mongomapper 0.1.0
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.
- data/.gitignore +5 -0
- data/History +2 -0
- data/LICENSE +20 -0
- data/README.rdoc +25 -0
- data/Rakefile +72 -0
- data/VERSION +1 -0
- data/lib/mongomapper/document.rb +234 -0
- data/lib/mongomapper/embedded_document.rb +260 -0
- data/lib/mongomapper/finder_options.rb +79 -0
- data/lib/mongomapper/key.rb +81 -0
- data/lib/mongomapper/rails_compatibility.rb +20 -0
- data/lib/mongomapper/save_with_validation.rb +27 -0
- data/lib/mongomapper/serialization.rb +55 -0
- data/lib/mongomapper/serializers/json_serializer.rb +77 -0
- data/lib/mongomapper.rb +46 -0
- data/test/serializers/test_json_serializer.rb +104 -0
- data/test/test_associations.rb +52 -0
- data/test/test_callbacks.rb +86 -0
- data/test/test_document.rb +938 -0
- data/test/test_embedded_document.rb +241 -0
- data/test/test_finder_options.rb +133 -0
- data/test/test_helper.rb +64 -0
- data/test/test_key.rb +200 -0
- data/test/test_mongo_mapper.rb +28 -0
- data/test/test_rails_compatibility.rb +24 -0
- data/test/test_serializations.rb +54 -0
- data/test/test_validations.rb +222 -0
- metadata +150 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
class Boolean; end
|
2
|
+
|
3
|
+
module MongoMapper
|
4
|
+
class Key
|
5
|
+
# DateTime and Date are currently not supported by mongo's bson so just use Time
|
6
|
+
NativeTypes = [String, Float, Time, Integer, Boolean, Array, Hash]
|
7
|
+
|
8
|
+
attr_accessor :name, :type, :options, :default_value
|
9
|
+
|
10
|
+
def initialize(name, type, options={})
|
11
|
+
@name, @type = name.to_s, type
|
12
|
+
self.options = options.symbolize_keys
|
13
|
+
self.default_value = options.delete(:default)
|
14
|
+
end
|
15
|
+
|
16
|
+
def ==(other)
|
17
|
+
@name == other.name && @type == other.type
|
18
|
+
end
|
19
|
+
|
20
|
+
def set(value)
|
21
|
+
typecast(value)
|
22
|
+
end
|
23
|
+
|
24
|
+
def native?
|
25
|
+
@native ||= NativeTypes.include?(type)
|
26
|
+
end
|
27
|
+
|
28
|
+
def embedded_document?
|
29
|
+
type.ancestors.include?(EmbeddedDocument) && !type.ancestors.include?(Document)
|
30
|
+
end
|
31
|
+
|
32
|
+
def get(value)
|
33
|
+
return default_value if value.nil? && !default_value.nil?
|
34
|
+
if type == Array
|
35
|
+
value || []
|
36
|
+
elsif type == Hash
|
37
|
+
HashWithIndifferentAccess.new(value || {})
|
38
|
+
else
|
39
|
+
value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def typecast(value)
|
45
|
+
return HashWithIndifferentAccess.new(value) if value.is_a?(Hash) && type == Hash
|
46
|
+
return value if value.kind_of?(type) || value.nil?
|
47
|
+
begin
|
48
|
+
if type == String then value.to_s
|
49
|
+
elsif type == Float then value.to_f
|
50
|
+
elsif type == Array then value.to_a
|
51
|
+
elsif type == Time then Time.parse(value.to_s)
|
52
|
+
#elsif type == Date then Date.parse(value.to_s)
|
53
|
+
elsif type == Boolean then ['true', 't', '1'].include?(value.to_s.downcase)
|
54
|
+
elsif type == Integer
|
55
|
+
# ganked from datamapper
|
56
|
+
value_to_i = value.to_i
|
57
|
+
if value_to_i == 0 && value != '0'
|
58
|
+
value_to_s = value.to_s
|
59
|
+
begin
|
60
|
+
Integer(value_to_s =~ /^(\d+)/ ? $1 : value_to_s)
|
61
|
+
rescue ArgumentError
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
else
|
65
|
+
value_to_i
|
66
|
+
end
|
67
|
+
elsif embedded_document?
|
68
|
+
typecast_embedded_document(value)
|
69
|
+
else
|
70
|
+
value
|
71
|
+
end
|
72
|
+
rescue
|
73
|
+
value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def typecast_embedded_document(value)
|
78
|
+
value.is_a?(type) ? value : type.new(value)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module RailsCompatibility
|
3
|
+
def self.included(model)
|
4
|
+
model.class_eval do
|
5
|
+
alias_method :new_record?, :new?
|
6
|
+
extend ClassMethods
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def column_names
|
12
|
+
keys.keys
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_param
|
17
|
+
id
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module SaveWithValidation
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
alias_method_chain :valid?, :callbacks
|
6
|
+
alias_method_chain :save, :validation
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def save!
|
11
|
+
save_with_validation || raise(DocumentNotValid.new(self))
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def save_with_validation
|
16
|
+
new? ? run_callbacks(:before_validation_on_create) :
|
17
|
+
run_callbacks(:before_validation_on_update)
|
18
|
+
|
19
|
+
valid? ? save_without_validation : false
|
20
|
+
end
|
21
|
+
|
22
|
+
def valid_with_callbacks?
|
23
|
+
run_callbacks(:before_validation)
|
24
|
+
run_callbacks(:after_validation) if valid_without_callbacks?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'active_support/json'
|
2
|
+
|
3
|
+
module MongoMapper #:nodoc:
|
4
|
+
module Serialization
|
5
|
+
class Serializer #:nodoc:
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def initialize(record, options = {})
|
9
|
+
@record, @options = record, options.dup
|
10
|
+
end
|
11
|
+
|
12
|
+
def serializable_key_names
|
13
|
+
key_names = @record.send :defined_key_names
|
14
|
+
|
15
|
+
if options[:only]
|
16
|
+
options.delete(:except)
|
17
|
+
key_names = key_names & Array(options[:only]).collect { |n| n.to_s }
|
18
|
+
else
|
19
|
+
options[:except] = Array(options[:except])
|
20
|
+
key_names = key_names - options[:except].collect { |n| n.to_s }
|
21
|
+
end
|
22
|
+
|
23
|
+
key_names
|
24
|
+
end
|
25
|
+
|
26
|
+
def serializable_method_names
|
27
|
+
Array(options[:methods]).inject([]) do |method_attributes, name|
|
28
|
+
method_attributes << name if @record.respond_to?(name.to_s)
|
29
|
+
method_attributes
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def serializable_names
|
34
|
+
serializable_key_names + serializable_method_names
|
35
|
+
end
|
36
|
+
|
37
|
+
def serializable_record
|
38
|
+
returning(serializable_record = {}) do
|
39
|
+
serializable_names.each { |name| serializable_record[name] = @record.send(name) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def serialize
|
44
|
+
# overwrite to implement
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s(&block)
|
48
|
+
serialize(&block)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
dir = Pathname(__FILE__).dirname.expand_path + 'serializers'
|
55
|
+
require dir + 'json_serializer'
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module MongoMapper #:nodoc:
|
2
|
+
module Serialization
|
3
|
+
def self.included(base)
|
4
|
+
base.cattr_accessor :include_root_in_json, :instance_writer => false
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
# Returns a JSON string representing the model. Some configuration is
|
9
|
+
# available through +options+.
|
10
|
+
#
|
11
|
+
# The option <tt>include_root_in_json</tt> controls the top-level behavior of
|
12
|
+
# to_json. When it is <tt>true</tt>, to_json will emit a single root node named
|
13
|
+
# after the object's type. For example:
|
14
|
+
#
|
15
|
+
# konata = User.find(1)
|
16
|
+
# User.include_root_in_json = true
|
17
|
+
# konata.to_json
|
18
|
+
# # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16,
|
19
|
+
# "created_at": "2006/08/01", "awesome": true} }
|
20
|
+
#
|
21
|
+
# User.include_root_in_json = false
|
22
|
+
# konata.to_json
|
23
|
+
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
|
24
|
+
# "created_at": "2006/08/01", "awesome": true}
|
25
|
+
#
|
26
|
+
# The remainder of the examples in this section assume include_root_in_json is set to
|
27
|
+
# <tt>false</tt>.
|
28
|
+
#
|
29
|
+
# Without any +options+, the returned JSON string will include all
|
30
|
+
# the model's attributes. For example:
|
31
|
+
#
|
32
|
+
# konata = User.find(1)
|
33
|
+
# konata.to_json
|
34
|
+
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
|
35
|
+
# "created_at": "2006/08/01", "awesome": true}
|
36
|
+
#
|
37
|
+
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes
|
38
|
+
# included, and work similar to the +attributes+ method. For example:
|
39
|
+
#
|
40
|
+
# konata.to_json(:only => [ :id, :name ])
|
41
|
+
# # => {"id": 1, "name": "Konata Izumi"}
|
42
|
+
#
|
43
|
+
# konata.to_json(:except => [ :id, :created_at, :age ])
|
44
|
+
# # => {"name": "Konata Izumi", "awesome": true}
|
45
|
+
#
|
46
|
+
# To include any methods on the model, use <tt>:methods</tt>.
|
47
|
+
#
|
48
|
+
# konata.to_json(:methods => :permalink)
|
49
|
+
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
|
50
|
+
# "created_at": "2006/08/01", "awesome": true,
|
51
|
+
# "permalink": "1-konata-izumi"}
|
52
|
+
def to_json(options = {})
|
53
|
+
if include_root_in_json
|
54
|
+
"{#{self.class.json_class_name}: #{JsonSerializer.new(self, options).to_s}}"
|
55
|
+
else
|
56
|
+
JsonSerializer.new(self, options).to_s
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def from_json(json)
|
61
|
+
self.attributes = ActiveSupport::JSON.decode(json)
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
class JsonSerializer < MongoMapper::Serialization::Serializer #:nodoc:
|
66
|
+
def serialize
|
67
|
+
serializable_record.to_json
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
module ClassMethods
|
72
|
+
def json_class_name
|
73
|
+
@json_class_name ||= name.demodulize.underscore.inspect
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/mongomapper.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'rubygems'
|
3
|
+
|
4
|
+
gem 'activesupport'
|
5
|
+
gem 'mongodb-mongo'
|
6
|
+
gem 'jnunemaker-validatable'
|
7
|
+
|
8
|
+
require 'activesupport'
|
9
|
+
require 'mongo'
|
10
|
+
require 'validatable'
|
11
|
+
|
12
|
+
dir = Pathname(__FILE__).dirname.expand_path + 'mongomapper'
|
13
|
+
|
14
|
+
require dir + 'key'
|
15
|
+
require dir + 'finder_options'
|
16
|
+
require dir + 'rails_compatibility'
|
17
|
+
require dir + 'save_with_validation'
|
18
|
+
require dir + 'serialization'
|
19
|
+
require dir + 'embedded_document'
|
20
|
+
require dir + 'document'
|
21
|
+
|
22
|
+
module MongoMapper
|
23
|
+
class DocumentNotFound < StandardError; end
|
24
|
+
class DocumentNotValid < StandardError
|
25
|
+
def initialize(document)
|
26
|
+
@document = document
|
27
|
+
super("Validation failed: #{@document.errors.full_messages.join(", ")}")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.connection
|
32
|
+
@@connection ||= XGen::Mongo::Driver::Mongo.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.connection=(new_connection)
|
36
|
+
@@connection = new_connection
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.database=(name)
|
40
|
+
@@database = MongoMapper.connection.db(name)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.database
|
44
|
+
@@database
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class JsonSerializationTest < Test::Unit::TestCase
|
4
|
+
class Contact
|
5
|
+
include MongoMapper::EmbeddedDocument
|
6
|
+
key :name, String
|
7
|
+
key :age, Integer
|
8
|
+
key :created_at, Time
|
9
|
+
key :awesome, Boolean
|
10
|
+
key :preferences, Hash
|
11
|
+
end
|
12
|
+
|
13
|
+
def setup
|
14
|
+
Contact.include_root_in_json = false
|
15
|
+
@contact = Contact.new(
|
16
|
+
:name => 'Konata Izumi',
|
17
|
+
:age => 16,
|
18
|
+
:created_at => Time.utc(2006, 8, 1),
|
19
|
+
:awesome => true,
|
20
|
+
:preferences => { :shows => 'anime' }
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
should "include demodulized root" do
|
25
|
+
Contact.include_root_in_json = true
|
26
|
+
assert_match %r{^\{"contact": \{}, @contact.to_json
|
27
|
+
end
|
28
|
+
|
29
|
+
should "encode all encodable attributes" do
|
30
|
+
json = @contact.to_json
|
31
|
+
|
32
|
+
assert_match %r{"name": "Konata Izumi"}, json
|
33
|
+
assert_match %r{"age": 16}, json
|
34
|
+
assert json.include?(%("created_at": #{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
|
35
|
+
assert_match %r{"awesome": true}, json
|
36
|
+
assert_match %r{"preferences": \{"shows": "anime"\}}, json
|
37
|
+
end
|
38
|
+
|
39
|
+
should "allow attribute filtering with only" do
|
40
|
+
json = @contact.to_json(:only => [:name, :age])
|
41
|
+
|
42
|
+
assert_match %r{"name": "Konata Izumi"}, json
|
43
|
+
assert_match %r{"age": 16}, json
|
44
|
+
assert_no_match %r{"awesome"}, json
|
45
|
+
assert_no_match %r{"created_at"}, json
|
46
|
+
assert_no_match %r{"preferences"}, json
|
47
|
+
end
|
48
|
+
|
49
|
+
should "allow attribute filtering with except" do
|
50
|
+
json = @contact.to_json(:except => [:name, :age])
|
51
|
+
|
52
|
+
assert_no_match %r{"name"}, json
|
53
|
+
assert_no_match %r{"age"}, json
|
54
|
+
assert_match %r{"awesome"}, json
|
55
|
+
assert_match %r{"created_at"}, json
|
56
|
+
assert_match %r{"preferences"}, json
|
57
|
+
end
|
58
|
+
|
59
|
+
context "including methods" do
|
60
|
+
setup do
|
61
|
+
def @contact.label; "Has cheezburger"; end
|
62
|
+
def @contact.favorite_quote; "Constraints are liberating"; end
|
63
|
+
end
|
64
|
+
|
65
|
+
should "include single method" do
|
66
|
+
# Single method.
|
67
|
+
assert_match %r{"label": "Has cheezburger"}, @contact.to_json(:only => :name, :methods => :label)
|
68
|
+
end
|
69
|
+
|
70
|
+
should "include multiple methods" do
|
71
|
+
json = @contact.to_json(:only => :name, :methods => [:label, :favorite_quote])
|
72
|
+
assert_match %r{"label": "Has cheezburger"}, json
|
73
|
+
assert_match %r{"favorite_quote": "Constraints are liberating"}, json
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "array of records" do
|
78
|
+
setup do
|
79
|
+
@contacts = [
|
80
|
+
Contact.new(:name => 'David', :age => 39),
|
81
|
+
Contact.new(:name => 'Mary', :age => 14)
|
82
|
+
]
|
83
|
+
end
|
84
|
+
|
85
|
+
should "allow attribute filtering with only" do
|
86
|
+
assert_equal %([{"name": "David"}, {"name": "Mary"}]), @contacts.to_json(:only => :name)
|
87
|
+
end
|
88
|
+
|
89
|
+
should "allow attribute filtering with except" do
|
90
|
+
json = @contacts.to_json(:except => [:name, :preferences, :awesome, :created_at])
|
91
|
+
assert_equal %([{"age": 39}, {"age": 14}]), json
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
should "allow options for hash of records" do
|
96
|
+
contacts = {
|
97
|
+
1 => Contact.new(:name => 'David', :age => 39),
|
98
|
+
2 => Contact.new(:name => 'Mary', :age => 14)
|
99
|
+
}
|
100
|
+
|
101
|
+
assert_equal %({"1": {"name": "David"}}), contacts.to_json(:only => [1, :name])
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Address
|
4
|
+
include MongoMapper::EmbeddedDocument
|
5
|
+
|
6
|
+
key :address, String
|
7
|
+
key :city, String
|
8
|
+
key :state, String
|
9
|
+
key :zip, Integer
|
10
|
+
end
|
11
|
+
|
12
|
+
class AssociationsTest < Test::Unit::TestCase
|
13
|
+
def setup
|
14
|
+
@document = Class.new do
|
15
|
+
include MongoMapper::Document
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "Many embedded documents" do
|
20
|
+
setup do
|
21
|
+
@document.class_eval do
|
22
|
+
many :addresses
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
should "default reader to empty array" do
|
27
|
+
instance = @document.new
|
28
|
+
instance.addresses.should == []
|
29
|
+
end
|
30
|
+
|
31
|
+
should "allow adding to association like it was an array" do
|
32
|
+
instance = @document.new
|
33
|
+
instance.addresses << Address.new
|
34
|
+
instance.addresses.push Address.new
|
35
|
+
instance.addresses.size.should == 2
|
36
|
+
end
|
37
|
+
|
38
|
+
should "be embedded in document on save" do
|
39
|
+
sb = Address.new(:city => 'South Bend', :state => 'IN')
|
40
|
+
chi = Address.new(:city => 'Chicago', :state => 'IL')
|
41
|
+
instance = @document.new
|
42
|
+
instance.addresses << sb
|
43
|
+
instance.addresses << chi
|
44
|
+
instance.save
|
45
|
+
|
46
|
+
from_db = @document.find(instance.id)
|
47
|
+
from_db.addresses.size.should == 2
|
48
|
+
from_db.addresses[0].should == sb
|
49
|
+
from_db.addresses[1].should == chi
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
include ActiveSupport::Callbacks
|
4
|
+
|
5
|
+
class CallbacksTest < Test::Unit::TestCase
|
6
|
+
context "Defining and running callbacks" do
|
7
|
+
setup do
|
8
|
+
@document = Class.new do
|
9
|
+
include MongoMapper::Document
|
10
|
+
|
11
|
+
key :name, String
|
12
|
+
|
13
|
+
[ :before_validation_on_create, :before_validation_on_update,
|
14
|
+
:before_validation, :after_validation,
|
15
|
+
:before_create, :after_create,
|
16
|
+
:before_update, :after_update,
|
17
|
+
:before_save, :after_save,
|
18
|
+
:before_destroy, :after_destroy].each do |callback|
|
19
|
+
callback_method = "#{callback}_callback"
|
20
|
+
send(callback, callback_method)
|
21
|
+
define_method(callback_method) do
|
22
|
+
history << callback.to_sym
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def history
|
27
|
+
@history ||= []
|
28
|
+
end
|
29
|
+
|
30
|
+
def clear_history
|
31
|
+
@history = nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
@document.collection.clear
|
35
|
+
end
|
36
|
+
|
37
|
+
should "get the order right for creating documents" do
|
38
|
+
doc = @document.create(:name => 'John Nunemaker')
|
39
|
+
doc.history.should == [:before_validation_on_create, :before_validation, :after_validation, :before_save, :before_create, :after_create, :after_save]
|
40
|
+
end
|
41
|
+
|
42
|
+
should "get the order right for updating documents" do
|
43
|
+
doc = @document.create(:name => 'John Nunemaker')
|
44
|
+
doc.clear_history
|
45
|
+
doc.name = 'John'
|
46
|
+
doc.save
|
47
|
+
doc.history.should == [:before_validation_on_update, :before_validation, :after_validation, :before_save, :before_update, :after_update, :after_save]
|
48
|
+
end
|
49
|
+
|
50
|
+
should "work for before and after validation" do
|
51
|
+
doc = @document.new(:name => 'John Nunemaker')
|
52
|
+
doc.valid?
|
53
|
+
doc.history.should include(:before_validation)
|
54
|
+
doc.history.should include(:after_validation)
|
55
|
+
end
|
56
|
+
|
57
|
+
should "work for before and after create" do
|
58
|
+
doc = @document.create(:name => 'John Nunemaker')
|
59
|
+
doc.history.should include(:before_create)
|
60
|
+
doc.history.should include(:after_create)
|
61
|
+
end
|
62
|
+
|
63
|
+
should "work for before and after update" do
|
64
|
+
doc = @document.create(:name => 'John Nunemaker')
|
65
|
+
doc.name = 'John Doe'
|
66
|
+
doc.save
|
67
|
+
doc.history.should include(:before_update)
|
68
|
+
doc.history.should include(:after_update)
|
69
|
+
end
|
70
|
+
|
71
|
+
should "work for before and after save" do
|
72
|
+
doc = @document.new
|
73
|
+
doc.name = 'John Doe'
|
74
|
+
doc.save
|
75
|
+
doc.history.should include(:before_save)
|
76
|
+
doc.history.should include(:after_save)
|
77
|
+
end
|
78
|
+
|
79
|
+
should "work for before and after destroy" do
|
80
|
+
doc = @document.create(:name => 'John Nunemaker')
|
81
|
+
doc.destroy
|
82
|
+
doc.history.should include(:before_destroy)
|
83
|
+
doc.history.should include(:after_destroy)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|