Skip to content

Commit 701ecdd

Browse files
author
Grant Ammons
committed
factor out @post.revise into its own class. clean up PostRevisor class to be more readable
1 parent f44dba5 commit 701ecdd

5 files changed

Lines changed: 263 additions & 74 deletions

File tree

app/models/post.rb

Lines changed: 10 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require_dependency 'jobs'
22
require_dependency 'pretty_text'
33
require_dependency 'rate_limiter'
4+
require_dependency 'post_revisor'
45

56
require 'archetype'
67
require 'hpricot'
@@ -14,12 +15,6 @@ module HiddenReason
1415
FLAG_THRESHOLD_REACHED_AGAIN = 2
1516
end
1617

17-
# A custom rate limiter for edits
18-
class EditRateLimiter < RateLimiter
19-
def initialize(user)
20-
super(user, "edit-post:#{Date.today.to_s}", SiteSetting.max_edits_per_day, 1.day.to_i)
21-
end
22-
end
2318

2419
versioned
2520

@@ -115,6 +110,11 @@ def cooked_document
115110
@cooked_document ||= Nokogiri::HTML.fragment(self.cooked)
116111
end
117112

113+
def reset_cooked
114+
@cooked_document = nil
115+
self.cooked = nil
116+
end
117+
118118
def image_count
119119
return 0 unless self.raw.present?
120120
cooked_document.search("img.emoji").remove
@@ -290,77 +290,14 @@ def unhide!
290290
self.save
291291
end
292292

293-
# Update the body of a post. Will create a new version when appropriate
294-
def revise(updated_by, new_raw, opts={})
295-
296-
# Only update if it changes
297-
return false if self.raw == new_raw
298-
299-
updater = lambda do |new_version=false|
300-
301-
# Raw is about to change, enable validations
302-
@cooked_document = nil
303-
self.cooked = nil
304-
305-
self.raw = new_raw
306-
self.updated_by = updated_by
307-
self.last_editor_id = updated_by.id
308-
309-
if self.hidden && self.hidden_reason_id == HiddenReason::FLAG_THRESHOLD_REACHED
310-
self.hidden = false
311-
self.hidden_reason_id = nil
312-
self.topic.update_attributes(visible: true)
313-
314-
PostAction.clear_flags!(self, -1)
315-
end
316-
317-
self.save
318-
end
319-
320-
# We can optionally specify when this version was revised. Defaults to now.
321-
revised_at = opts[:revised_at] || Time.now
322-
new_version = false
323-
324-
# We always create a new version if the poster has changed
325-
new_version = true if (self.last_editor_id != updated_by.id)
326-
327-
# We always create a new version if it's been greater than the ninja edit window
328-
new_version = true if (revised_at - last_version_at) > SiteSetting.ninja_edit_window.to_i
329-
330-
new_version = true if opts[:force_new_version]
331-
332-
# Create the new version (or don't)
333-
if new_version
334-
335-
self.cached_version = version + 1
336-
337-
Post.transaction do
338-
self.last_version_at = revised_at
339-
updater.call(true)
340-
EditRateLimiter.new(updated_by).performed! unless opts[:bypass_rate_limiter]
341-
342-
# If a new version is created of the last post, bump it.
343-
unless Post.where('post_number > ? and topic_id = ?', self.post_number, self.topic_id).exists?
344-
topic.update_column(:bumped_at, Time.now) unless opts[:bypass_bump]
345-
end
346-
end
347-
348-
else
349-
skip_version(&updater)
350-
end
351-
352-
# Invalidate any oneboxes
353-
self.invalidate_oneboxes = true
354-
trigger_post_process
355-
356-
true
357-
end
358-
359-
360293
def url
361294
"/t/#{Slug.for(topic.title)}/#{topic.id}/#{post_number}"
362295
end
363296

297+
def revise(updated_by, new_raw, opts={})
298+
PostRevisor.new(self).revise!(updated_by, new_raw, opts)
299+
end
300+
364301
# Various callbacks
365302
before_create do
366303
self.post_number ||= Topic.next_post_number(topic_id, reply_to_post_number.present?)

lib/edit_rate_limiter.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
require 'rate_limiter'
2+
class EditRateLimiter < RateLimiter
3+
def initialize(user)
4+
super(user, "edit-post:#{Date.today.to_s}", SiteSetting.max_edits_per_day, 1.day.to_i)
5+
end
6+
end

lib/post_revisor.rb

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
require 'edit_rate_limiter'
2+
class PostRevisor
3+
def initialize(post)
4+
@post = post
5+
end
6+
7+
def revise!(user, new_raw, opts = {})
8+
@user, @new_raw, @opts = user, new_raw, opts
9+
return false if not should_revise?
10+
revise_post
11+
post_process_post
12+
true
13+
end
14+
15+
private
16+
17+
def should_revise?
18+
@post.raw != @new_raw
19+
end
20+
21+
def revise_post
22+
if should_create_new_version?
23+
revise_and_create_new_version
24+
else
25+
revise_without_creating_a_new_version
26+
end
27+
end
28+
29+
def get_revised_at
30+
@opts[:revised_at] || Time.now
31+
end
32+
33+
def should_create_new_version?
34+
(@post.last_editor_id != @user.id) or
35+
((get_revised_at - @post.last_version_at) > SiteSetting.ninja_edit_window.to_i) or
36+
@opts[:force_new_version] == true
37+
end
38+
39+
def revise_and_create_new_version
40+
Post.transaction do
41+
@post.cached_version = @post.version + 1
42+
@post.last_version_at = get_revised_at
43+
update_post
44+
EditRateLimiter.new(@post.user).performed! unless @opts[:bypass_rate_limiter] == true
45+
bump_topic unless @opts[:bypass_bump]
46+
end
47+
end
48+
49+
def revise_without_creating_a_new_version
50+
@post.skip_version do
51+
update_post
52+
end
53+
end
54+
55+
def bump_topic
56+
unless Post.where('post_number > ? and topic_id = ?', @post.post_number, @post.topic_id).exists?
57+
@post.topic.update_column(:bumped_at, Time.now)
58+
end
59+
end
60+
61+
def update_post
62+
@post.reset_cooked
63+
64+
@post.raw = @new_raw
65+
@post.updated_by = @user
66+
@post.last_editor_id = @user.id
67+
68+
if @post.hidden && @post.hidden_reason_id == Post::HiddenReason::FLAG_THRESHOLD_REACHED
69+
@post.hidden = false
70+
@post.hidden_reason_id = nil
71+
@post.topic.update_attributes(visible: true)
72+
73+
PostAction.clear_flags!(@post, -1)
74+
end
75+
76+
@post.save
77+
end
78+
79+
def post_process_post
80+
@post.invalidate_oneboxes = true
81+
@post.trigger_post_process
82+
end
83+
end
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
require 'spec_helper'
2+
require 'post_revisor'
3+
4+
describe PostRevisor do
5+
6+
let(:topic) { Fabricate(:topic) }
7+
let(:post_args) { {user: topic.user, topic: topic} }
8+
9+
context 'revise' do
10+
11+
let(:post) { Fabricate(:post, post_args) }
12+
let(:first_version_at) { post.last_version_at }
13+
14+
subject { described_class.new(post) }
15+
16+
describe 'with the same body' do
17+
18+
it 'returns false' do
19+
subject.revise!(post.user, post.raw).should be_false
20+
end
21+
22+
it "doesn't change cached_version" do
23+
lambda { subject.revise!(post.user, post.raw); post.reload }.should_not change(post, :cached_version)
24+
end
25+
26+
end
27+
28+
describe 'ninja editing' do
29+
before do
30+
SiteSetting.expects(:ninja_edit_window).returns(1.minute.to_i)
31+
subject.revise!(post.user, 'updated body', revised_at: post.updated_at + 10.seconds)
32+
post.reload
33+
end
34+
35+
it 'does not update cached_version' do
36+
post.cached_version.should == 1
37+
end
38+
39+
it 'does not create a new version' do
40+
post.all_versions.size.should == 1
41+
end
42+
43+
it "doesn't change the last_version_at" do
44+
post.last_version_at.should == first_version_at
45+
end
46+
end
47+
48+
describe 'revision much later' do
49+
50+
let!(:revised_at) { post.updated_at + 2.minutes }
51+
52+
before do
53+
SiteSetting.stubs(:ninja_edit_window).returns(1.minute.to_i)
54+
subject.revise!(post.user, 'updated body', revised_at: revised_at)
55+
post.reload
56+
end
57+
58+
it 'updates the cached_version' do
59+
post.cached_version.should == 2
60+
end
61+
62+
it 'creates a new version' do
63+
post.all_versions.size.should == 2
64+
end
65+
66+
it "updates the last_version_at" do
67+
post.last_version_at.to_i.should == revised_at.to_i
68+
end
69+
70+
describe "new edit window" do
71+
72+
before do
73+
subject.revise!(post.user, 'yet another updated body', revised_at: revised_at)
74+
post.reload
75+
end
76+
77+
it "doesn't create a new version if you do another" do
78+
post.cached_version.should == 2
79+
end
80+
81+
it "doesn't change last_version_at" do
82+
post.last_version_at.to_i.should == revised_at.to_i
83+
end
84+
85+
context "after second window" do
86+
87+
let!(:new_revised_at) {revised_at + 2.minutes}
88+
89+
before do
90+
subject.revise!(post.user, 'yet another, another updated body', revised_at: new_revised_at)
91+
post.reload
92+
end
93+
94+
it "does create a new version after the edit window" do
95+
post.cached_version.should == 3
96+
end
97+
98+
it "does create a new version after the edit window" do
99+
post.last_version_at.to_i.should == new_revised_at.to_i
100+
end
101+
end
102+
end
103+
end
104+
105+
describe 'rate limiter' do
106+
let(:changed_by) { Fabricate(:coding_horror) }
107+
108+
it "triggers a rate limiter" do
109+
EditRateLimiter.any_instance.expects(:performed!)
110+
subject.revise!(changed_by, 'updated body')
111+
end
112+
end
113+
114+
describe 'with a new body' do
115+
let(:changed_by) { Fabricate(:coding_horror) }
116+
let!(:result) { subject.revise!(changed_by, 'updated body') }
117+
118+
it 'returns true' do
119+
result.should be_true
120+
end
121+
122+
it 'updates the body' do
123+
post.raw.should == 'updated body'
124+
end
125+
126+
it 'sets the invalidate oneboxes attribute' do
127+
post.invalidate_oneboxes.should == true
128+
end
129+
130+
it 'increased the cached_version' do
131+
post.cached_version.should == 2
132+
end
133+
134+
it 'has the new version in all_versions' do
135+
post.all_versions.size.should == 2
136+
end
137+
138+
it 'has versions' do
139+
post.versions.should be_present
140+
end
141+
142+
it "saved the user who made the change in the version" do
143+
post.versions.first.user.should be_present
144+
end
145+
146+
context 'second poster posts again quickly' do
147+
before do
148+
SiteSetting.expects(:ninja_edit_window).returns(1.minute.to_i)
149+
subject.revise!(changed_by, 'yet another updated body', revised_at: post.updated_at + 10.seconds)
150+
post.reload
151+
end
152+
153+
it 'is a ninja edit, because the second poster posted again quickly' do
154+
post.cached_version.should == 2
155+
end
156+
157+
it 'is a ninja edit, because the second poster posted again quickly' do
158+
post.all_versions.size.should == 2
159+
end
160+
end
161+
end
162+
end
163+
end

spec/models/post_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@
370370
let(:changed_by) { Fabricate(:coding_horror) }
371371

372372
it "triggers a rate limiter" do
373-
Post::EditRateLimiter.any_instance.expects(:performed!)
373+
EditRateLimiter.any_instance.expects(:performed!)
374374
post.revise(changed_by, 'updated body')
375375
end
376376
end

0 commit comments

Comments
 (0)