elastictastic 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README.md +326 -0
- data/lib/elastictastic/association.rb +21 -0
- data/lib/elastictastic/bulk_persistence_strategy.rb +70 -0
- data/lib/elastictastic/callbacks.rb +30 -0
- data/lib/elastictastic/child_collection_proxy.rb +56 -0
- data/lib/elastictastic/client.rb +101 -0
- data/lib/elastictastic/configuration.rb +35 -0
- data/lib/elastictastic/dirty.rb +130 -0
- data/lib/elastictastic/discrete_persistence_strategy.rb +52 -0
- data/lib/elastictastic/document.rb +98 -0
- data/lib/elastictastic/errors.rb +7 -0
- data/lib/elastictastic/field.rb +38 -0
- data/lib/elastictastic/index.rb +19 -0
- data/lib/elastictastic/mass_assignment_security.rb +15 -0
- data/lib/elastictastic/middleware.rb +119 -0
- data/lib/elastictastic/nested_document.rb +29 -0
- data/lib/elastictastic/new_relic_instrumentation.rb +26 -0
- data/lib/elastictastic/observer.rb +3 -0
- data/lib/elastictastic/observing.rb +21 -0
- data/lib/elastictastic/parent_child.rb +115 -0
- data/lib/elastictastic/persistence.rb +67 -0
- data/lib/elastictastic/properties.rb +236 -0
- data/lib/elastictastic/railtie.rb +35 -0
- data/lib/elastictastic/resource.rb +4 -0
- data/lib/elastictastic/scope.rb +283 -0
- data/lib/elastictastic/scope_builder.rb +32 -0
- data/lib/elastictastic/scoped.rb +20 -0
- data/lib/elastictastic/search.rb +180 -0
- data/lib/elastictastic/server_error.rb +15 -0
- data/lib/elastictastic/test_helpers.rb +172 -0
- data/lib/elastictastic/util.rb +63 -0
- data/lib/elastictastic/validations.rb +45 -0
- data/lib/elastictastic/version.rb +3 -0
- data/lib/elastictastic.rb +82 -0
- data/spec/environment.rb +6 -0
- data/spec/examples/active_model_lint_spec.rb +20 -0
- data/spec/examples/bulk_persistence_strategy_spec.rb +233 -0
- data/spec/examples/callbacks_spec.rb +96 -0
- data/spec/examples/dirty_spec.rb +238 -0
- data/spec/examples/document_spec.rb +600 -0
- data/spec/examples/mass_assignment_security_spec.rb +13 -0
- data/spec/examples/middleware_spec.rb +92 -0
- data/spec/examples/observing_spec.rb +141 -0
- data/spec/examples/parent_child_spec.rb +308 -0
- data/spec/examples/properties_spec.rb +92 -0
- data/spec/examples/scope_spec.rb +491 -0
- data/spec/examples/search_spec.rb +382 -0
- data/spec/examples/spec_helper.rb +15 -0
- data/spec/examples/validation_spec.rb +65 -0
- data/spec/models/author.rb +9 -0
- data/spec/models/blog.rb +5 -0
- data/spec/models/comment.rb +5 -0
- data/spec/models/post.rb +41 -0
- data/spec/models/post_observer.rb +11 -0
- data/spec/support/fakeweb_request_history.rb +13 -0
- metadata +227 -0
@@ -0,0 +1,233 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Elastictastic::BulkPersistenceStrategy do
|
4
|
+
include Elastictastic::TestHelpers
|
5
|
+
|
6
|
+
let(:last_request) { FakeWeb.last_request }
|
7
|
+
let(:bulk_requests) do
|
8
|
+
last_request.body.split("\n").map do |request|
|
9
|
+
JSON.parse(request)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'create without ID' do
|
14
|
+
let(:post) { Post.new }
|
15
|
+
|
16
|
+
before do
|
17
|
+
stub_elasticsearch_bulk(
|
18
|
+
'create' => { '_index' => 'default', '_type' => 'post', '_id' => '123', '_version' => 1, 'ok' => true }
|
19
|
+
)
|
20
|
+
Elastictastic.bulk do
|
21
|
+
post.title = 'Bulky'
|
22
|
+
post.save
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should send bulk request' do
|
27
|
+
last_request.path.should == '/_bulk'
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should send index operation' do
|
31
|
+
bulk_requests.should == [
|
32
|
+
{ 'create' => { '_index' => 'default', '_type' => 'post' }},
|
33
|
+
post.elasticsearch_doc
|
34
|
+
]
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should set ID' do
|
38
|
+
post.id.should == '123'
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should set persisted' do
|
42
|
+
post.should be_persisted
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should have final newline' do
|
46
|
+
last_request.body[-1].should == "\n"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'before bulk operation completes' do
|
51
|
+
let(:post) { Post.new }
|
52
|
+
|
53
|
+
around do |example|
|
54
|
+
Elastictastic.bulk do
|
55
|
+
example.run
|
56
|
+
# have to do this here because the before/after hooks run inside the
|
57
|
+
# around hook
|
58
|
+
stub_elasticsearch_bulk(
|
59
|
+
'create' => { '_index' => 'default', '_type' => 'post', '_id' => '123', '_version' => 1, 'ok' => true }
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should not set ID' do
|
65
|
+
post.save
|
66
|
+
post.id.should be_nil
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should not set persisted' do
|
70
|
+
post.should be_transient
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should not allow you to call save again on transient document' do
|
74
|
+
post.save
|
75
|
+
expect { post.save }.to raise_error(Elastictastic::OperationNotAllowed)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe 'creating multiple' do
|
80
|
+
let(:posts) { Array.new(2) { Post.new }}
|
81
|
+
|
82
|
+
before do
|
83
|
+
stub_elasticsearch_bulk(
|
84
|
+
{ 'create' => { '_index' => 'default', '_type' => 'post', '_id' => '123', '_version' => 1, 'ok' => true }},
|
85
|
+
{ 'create' => { '_index' => 'default', '_type' => 'post', '_id' => '124', '_version' => 1, 'ok' => true }}
|
86
|
+
)
|
87
|
+
Elastictastic.bulk { posts.each { |post| post.save }}
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should send both create operations' do
|
91
|
+
bulk_requests.should == posts.map do |post|
|
92
|
+
[
|
93
|
+
{ 'create' => { '_index' => 'default', '_type' => 'post' }},
|
94
|
+
post.elasticsearch_doc
|
95
|
+
]
|
96
|
+
end.flatten
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should set IDs' do
|
100
|
+
posts.map { |post| post.id }.should == %w(123 124)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'create with ID set' do
|
105
|
+
let(:post) do
|
106
|
+
Post.new.tap do |post|
|
107
|
+
post.id = '123'
|
108
|
+
post.title = 'bulky'
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
before do
|
113
|
+
stub_elasticsearch_bulk(
|
114
|
+
'create' => { '_index' => 'default', '_type' => 'post', '_id' => '123', '_version' => 1, 'ok' => true }
|
115
|
+
)
|
116
|
+
Elastictastic.bulk { post.save }
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should send ID in request to create' do
|
120
|
+
bulk_requests.should == [
|
121
|
+
{ 'create' => { '_index' => 'default', '_type' => 'post', '_id' => '123' }},
|
122
|
+
post.elasticsearch_doc
|
123
|
+
]
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'should set object persistent' do
|
127
|
+
post.should be_persisted
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'should retain ID' do
|
131
|
+
post.id.should == '123'
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe '#update' do
|
136
|
+
let(:post) do
|
137
|
+
Post.new.tap do |post|
|
138
|
+
post.id = '123'
|
139
|
+
post.persisted!
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
before do
|
144
|
+
stub_elasticsearch_bulk(
|
145
|
+
'index' => { '_index' => 'default', '_type' => 'post', '_id' => '123', '_version' => 1, 'ok' => true }
|
146
|
+
)
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'should send update' do
|
150
|
+
Elastictastic.bulk { post.save }
|
151
|
+
|
152
|
+
bulk_requests.should == [
|
153
|
+
{ 'index' => { '_index' => 'default', '_type' => 'post', '_id' => '123' }},
|
154
|
+
post.elasticsearch_doc
|
155
|
+
]
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe 'destroy' do
|
160
|
+
let(:post) do
|
161
|
+
Post.new.tap do |post|
|
162
|
+
post.id = '123'
|
163
|
+
post.title = 'bulky'
|
164
|
+
post.persisted!
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
before do
|
169
|
+
stub_elasticsearch_bulk(
|
170
|
+
'destroy' => { '_index' => 'default', '_type' => 'post', '_id' => '123', '_version' => 1, 'ok' => true }
|
171
|
+
)
|
172
|
+
Elastictastic.bulk { post.destroy }
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'should send destroy' do
|
176
|
+
bulk_requests.should == [
|
177
|
+
{ 'delete' => { '_index' => 'default', '_type' => 'post', '_id' => '123' }}
|
178
|
+
]
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'should mark record as not persistent' do
|
182
|
+
post.should_not be_persisted
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
shared_examples_for 'block with error' do
|
187
|
+
it 'should not run bulk operation' do
|
188
|
+
error_proc.call rescue nil
|
189
|
+
last_request.should_not be
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'should return to individual persistence strategy' do
|
193
|
+
error_proc.call rescue nil
|
194
|
+
stub_elasticsearch_create('default', 'post')
|
195
|
+
Post.new.save
|
196
|
+
last_request.path.should == '/default/post'
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
describe 'with uncaught exception raised' do
|
201
|
+
let :error_proc do
|
202
|
+
lambda do
|
203
|
+
Elastictastic.bulk do
|
204
|
+
Post.new.save
|
205
|
+
raise
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'should propagate error up' do
|
211
|
+
expect(&error_proc).to raise_error(RuntimeError)
|
212
|
+
end
|
213
|
+
|
214
|
+
it_should_behave_like 'block with error'
|
215
|
+
end
|
216
|
+
|
217
|
+
describe 'raising CancelBulkOperation' do
|
218
|
+
let :error_proc do
|
219
|
+
lambda do
|
220
|
+
Elastictastic.bulk do
|
221
|
+
Post.new.save
|
222
|
+
raise Elastictastic::CancelBulkOperation
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'should not propagate error' do
|
228
|
+
expect(&error_proc).not_to raise_error
|
229
|
+
end
|
230
|
+
|
231
|
+
it_should_behave_like 'block with error'
|
232
|
+
end
|
233
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Elastictastic::Callbacks do
|
4
|
+
include Elastictastic::TestHelpers
|
5
|
+
|
6
|
+
let(:MyModel) do
|
7
|
+
Class.new do
|
8
|
+
def self.name
|
9
|
+
'MyModel'
|
10
|
+
end
|
11
|
+
|
12
|
+
include Elastictastic::Document
|
13
|
+
|
14
|
+
before_save :before_save_ran!
|
15
|
+
before_create :before_create_ran!
|
16
|
+
before_update :before_update_ran!
|
17
|
+
before_destroy :before_destroy_ran!
|
18
|
+
|
19
|
+
def hooks_that_ran
|
20
|
+
@hooks_that_ran ||= Set[]
|
21
|
+
end
|
22
|
+
|
23
|
+
def has_run_hook?(hook)
|
24
|
+
hooks_that_ran.include?(hook.to_sym)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def method_missing(method, *args, &block)
|
30
|
+
if method.to_s =~ /^(.*)_ran!$/
|
31
|
+
hooks_that_ran << $1.to_sym
|
32
|
+
else
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
let(:id) { '123' }
|
40
|
+
let(:instance) { MyModel().new }
|
41
|
+
let(:persisted_instance) do
|
42
|
+
MyModel().new.tap do |instance|
|
43
|
+
instance.elasticsearch_hit = { '_id' => id, '_index' => 'default' }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
before do
|
48
|
+
stub_elasticsearch_create('default', 'my_model')
|
49
|
+
stub_elasticsearch_update('default', 'my_model', id)
|
50
|
+
stub_elasticsearch_destroy('default', 'my_model', id)
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#before_save' do
|
54
|
+
|
55
|
+
it 'should run before create' do
|
56
|
+
instance.save
|
57
|
+
instance.should have_run_hook(:before_save)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should run before update' do
|
61
|
+
persisted_instance.save
|
62
|
+
persisted_instance.should have_run_hook(:before_save)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#before_create' do
|
67
|
+
it 'should run before create' do
|
68
|
+
instance.save
|
69
|
+
instance.should have_run_hook(:before_create)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should run before update' do
|
73
|
+
persisted_instance.save
|
74
|
+
persisted_instance.should_not have_run_hook(:before_create)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#before_update' do
|
79
|
+
it 'should not run before create' do
|
80
|
+
instance.save
|
81
|
+
instance.should_not have_run_hook :before_update
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should run before update' do
|
85
|
+
persisted_instance.save
|
86
|
+
persisted_instance.should have_run_hook(:before_update)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '#before_destroy' do
|
91
|
+
it 'should run before destroy' do
|
92
|
+
persisted_instance.destroy
|
93
|
+
persisted_instance.should have_run_hook(:before_destroy)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Elastictastic::Dirty do
|
4
|
+
include Elastictastic::TestHelpers
|
5
|
+
|
6
|
+
let(:post) do
|
7
|
+
Post.new.tap do |post|
|
8
|
+
post.elasticsearch_hit = {
|
9
|
+
'_id' => '1',
|
10
|
+
'_type' => 'post',
|
11
|
+
'_index' => 'default',
|
12
|
+
'_source' => source
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
before do
|
18
|
+
stub_elasticsearch_update('default', 'post', '1')
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'top-level attribute' do
|
22
|
+
let(:source) { { 'title' => 'first title' } }
|
23
|
+
|
24
|
+
context 'with no attribute changed' do
|
25
|
+
it 'should not be changed' do
|
26
|
+
post.should_not be_changed
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should not have any changes' do
|
30
|
+
post.changes.should be_empty
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should not have the title attribute changed' do
|
34
|
+
post.should_not be_title_changed
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'with title attribute changed' do
|
39
|
+
before do
|
40
|
+
post.title = 'hey guy'
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should be changed' do
|
44
|
+
post.should be_changed
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should expose change' do
|
48
|
+
post.changes[:title].should == ['first title', 'hey guy']
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should have the title attribute changed' do
|
52
|
+
post.should be_title_changed
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'after change and save' do
|
57
|
+
before do
|
58
|
+
post.title = 'hey guy'
|
59
|
+
post.save
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should not be changed' do
|
63
|
+
post.should_not be_changed
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should not have any changes' do
|
67
|
+
post.changes.should be_empty
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should not have the title attribute changed' do
|
71
|
+
post.should_not be_title_changed
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'single nested document' do
|
77
|
+
let(:source) { { 'author' => { 'name' => 'Mat Brown' }} }
|
78
|
+
|
79
|
+
context 'with nothing changed' do
|
80
|
+
it 'should not be changed' do
|
81
|
+
post.should_not be_changed
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'with nested document attribute changed' do
|
86
|
+
before do
|
87
|
+
post.author.name = 'Barack Obama'
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should be changed' do
|
91
|
+
post.should be_changed
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should mark association as changed' do
|
95
|
+
post.changed.should == %w(author)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should return *_changed? for the association' do
|
99
|
+
post.should be_author_changed
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should have change' do
|
103
|
+
change = post.changes['author']
|
104
|
+
change.map { |author| author.name }.should == ['Mat Brown', 'Barack Obama']
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'should not be changed after save' do
|
108
|
+
post.save
|
109
|
+
post.should_not be_changed
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should not have changed nested document after save' do
|
113
|
+
post.save
|
114
|
+
post.author.should_not be_changed
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context 'with nested document replaced' do
|
119
|
+
before do
|
120
|
+
post.author = Author.new(:name => 'Barack Obama')
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should be changed' do
|
124
|
+
post.should be_changed
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should expose changes' do
|
128
|
+
post.changes['author'].map { |author| author.name }.should ==
|
129
|
+
['Mat Brown', 'Barack Obama']
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'nested document collection' do
|
135
|
+
let(:source) { { 'comments' => [{ 'body' => 'I like pizza' }] } }
|
136
|
+
|
137
|
+
let(:mapped_changes) do
|
138
|
+
post.changes['comments'].map do |change|
|
139
|
+
change.map { |comment| comment.body }
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'with nothing changed' do
|
144
|
+
it 'should not be changed' do
|
145
|
+
post.should_not be_changed
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context 'when document changed' do
|
150
|
+
before do
|
151
|
+
post.comments.first.body = 'I like lasagne'
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'should be changed' do
|
155
|
+
post.should be_changed
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'should expose changed document' do
|
159
|
+
mapped_changes.should == [['I like pizza'], ['I like lasagne']]
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'should not be changed after save' do
|
163
|
+
post.save
|
164
|
+
post.should_not be_changed
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'should not have changed nested document after save' do
|
168
|
+
post.save
|
169
|
+
post.comments.first.should_not be_changed
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'when document added' do
|
174
|
+
before do
|
175
|
+
post.comments << Comment.new(:body => 'Lasagne is better')
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'should be changed' do
|
179
|
+
post.should be_changed
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'should expose new document' do
|
183
|
+
mapped_changes.should == [
|
184
|
+
['I like pizza'],
|
185
|
+
['I like pizza', 'Lasagne is better']
|
186
|
+
]
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'should not be changed after save' do
|
190
|
+
post.save
|
191
|
+
post.should_not be_changed
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'should not have changed nested documents after save' do
|
195
|
+
post.save
|
196
|
+
post.comments.each { |comment| comment.should_not be_changed }
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
context 'when document removed' do
|
201
|
+
before do
|
202
|
+
post.comments.delete(post.comments.first)
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'should be changed' do
|
206
|
+
post.should be_changed
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'should show changes' do
|
210
|
+
mapped_changes.should == [['I like pizza'], []]
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'should not be changed after save' do
|
214
|
+
post.save
|
215
|
+
post.should_not be_changed
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
context 'with collection replaced' do
|
220
|
+
before do
|
221
|
+
post.comments = [Comment.new(:body => 'I like lasagne')]
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'should be changed' do
|
225
|
+
post.should be_changed
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'should expose changes' do
|
229
|
+
mapped_changes.should == [['I like pizza'], ['I like lasagne']]
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'should not be changed after save' do
|
233
|
+
post.save
|
234
|
+
post.should_not be_changed
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|