Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

anonymous forum posting and other fixes

  • Loading branch information...
commit 2cb49bfe98fd57cb55c46c21ff6aed11efb62c7f 1 parent eb2545e
@bborn authored
Showing with 153 additions and 31 deletions.
  1. +2 −0  CHANGELOG
  2. +1 −1  app/controllers/comment_sweeper.rb
  3. +21 −3 app/controllers/comments_controller.rb
  4. +1 −1  app/controllers/post_sweeper.rb
  5. +1 −1  app/helpers/base_helper.rb
  6. +1 −1  app/models/user_notifier.rb
  7. +5 −0 app/views/comments/_comment.html.haml
  8. +29 −0 app/views/comments/_edit_form.html.haml
  9. +7 −0 app/views/comments/edit.js.rjs
  10. +9 −0 app/views/comments/update.js.rjs
  11. +3 −3 app/views/friendships/index.xml.builder
  12. +2 −2 app/views/layouts/application.html.haml
  13. +2 −2 app/views/layouts/beta.html.haml
  14. +4 −4 app/views/shared/_header.html.haml
  15. +2 −2 app/views/sitemap/index.xml.builder
  16. +1 −1  app/views/user_notifier/follow_up_comment_notice.erb
  17. +1 −1  app/views/user_notifier/new_forum_post_notice.erb
  18. +3 −3 lib/authenticated_system.rb
  19. +4 −4 sample_files/sample_themes/dappled/views/shared/_header.html.haml
  20. +15 −2 test/functional/admin_controller_test.rb
  21. +26 −0 test/functional/comments_controller_test.rb
  22. +4 −0 test/functional/sb_posts_controller_test.rb
  23. +8 −0 test/functional/users_controller_test.rb
  24. +1 −0  test/unit/sb_post_test.rb
View
2  CHANGELOG
@@ -3,6 +3,8 @@
=1.2.1
* Anonymous forum replies
* Turn comment notifications on or off by post
+* Allow admins/mods to edit comments (they can already delete them)
+* Fix security vulnerability in AuthenticatedSystem
=1.2.0
* Threaded private messages
View
2  app/controllers/comment_sweeper.rb
@@ -21,7 +21,7 @@ def expire_cache_for(record)
expire_action :controller => 'base', :action => 'footer_content'
if record.commentable_type.eql?('Post')
- expire_action :controller => 'posts', :action => 'show', :id => record.commentable , :user_id => record.commentable.user
+ expire_action :controller => 'posts', :action => 'show', :id => record.commentable.to_param , :user_id => record.commentable.user.to_param
if Post.find_recent(:limit => 16).include?(record.commentable)
# Expire the home page
View
24 app/controllers/comments_controller.rb
@@ -1,19 +1,37 @@
class CommentsController < BaseController
helper :comments
before_filter :login_required, :except => [:index, :unsubscribe]
- before_filter :admin_or_moderator_required, :only => [:delete_selected]
+ before_filter :admin_or_moderator_required, :only => [:delete_selected, :edit, :update]
if AppConfig.allow_anonymous_commenting
skip_before_filter :verify_authenticity_token, :only => [:create] #because the auth token might be cached anyway
skip_before_filter :login_required, :only => [:create]
end
- uses_tiny_mce(:only => [:index]) do
+ uses_tiny_mce(:only => [:index, :edit, :update]) do
AppConfig.simple_mce_options
end
cache_sweeper :comment_sweeper, :only => [:create, :destroy]
+
+ def edit
+ @comment = Comment.find(params[:id])
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def update
+ @comment = Comment.find(params[:id])
+ @comment.update_attributes(params[:comment])
+ @comment.save!
+ respond_to do |format|
+ format.js
+ end
+ end
+
+
def index
@commentable = comment_type.constantize.find(comment_id)
@@ -156,7 +174,7 @@ def comment_link
end
def full_comment_link
- "#{application_url}#{comment_link}"
+ "#{home_url}#{comment_link}"
end
def comment_rss_link
View
2  app/controllers/post_sweeper.rb
@@ -31,6 +31,6 @@ def expire_cache_for(record)
expire_action(:controller => 'categories', :action => 'show')
# Also expire the show pages, in case we just edited a blog entry
- expire_action(:controller => 'posts', :action => 'show', :id => record.to_param)
+ expire_action(:controller => 'posts', :action => 'show', :id => record.to_param, :user_id => record.user.to_param)
end
end
View
2  app/helpers/base_helper.rb
@@ -255,7 +255,7 @@ def show_footer_content?
end
def clippings_link
- "javascript:(function() {d=document, w=window, e=w.getSelection, k=d.getSelection, x=d.selection, s=(e?e():(k)?k():(x?x.createRange().text:0)), e=encodeURIComponent, document.location='#{application_url}new_clipping?uri='+e(document.location)+'&title='+e(document.title)+'&selection='+e(s);} )();"
+ "javascript:(function() {d=document, w=window, e=w.getSelection, k=d.getSelection, x=d.selection, s=(e?e():(k)?k():(x?x.createRange().text:0)), e=encodeURIComponent, document.location='#{home_url}new_clipping?uri='+e(document.location)+'&title='+e(document.title)+'&selection='+e(s);} )();"
end
def paginating_links(paginator, options = {}, html_options = {})
View
2  app/models/user_notifier.rb
@@ -70,7 +70,7 @@ def new_forum_post_notice(user, post)
def signup_notification(user)
setup_email(user)
@subject += "#{:please_activate_your_new_account.l(:site => AppConfig.community_name)}"
- @body[:url] = "#{application_url}users/activate/#{user.activation_code}"
+ @body[:url] = "#{home_url}users/activate/#{user.activation_code}"
end
def message_notification(message)
View
5 app/views/comments/_comment.html.haml
@@ -11,6 +11,8 @@
= I18n.l(comment.created_at, :format => :short_literal_date)
-if ( comment.can_be_deleted_by(current_user) )
%li.delete=link_to_remote(:delete.l, {:url => comment_path(comment.commentable_type, comment.commentable_id, comment), :method => :delete, 500 => 'alert(\'Sorry, there was a server error\'); return false', :success => visual_effect(:fade, "comment_#{comment.id}"), :confirm => :are_you_sure_you_want_to_permanently_delete_this_comment.l} )
+ -if logged_in? && (current_user.admin? || current_user.moderator?)
+ %li.edit=link_to_remote :edit.l, :url => {:controller => 'comments', :action => 'edit', :id => comment.id}, :method => :get
.entry-content= comment.comment
-else
@@ -28,6 +30,9 @@
= I18n.l(comment.created_at, :format => :short_literal_date)
-if ( comment.can_be_deleted_by(current_user) )
%li.delete=link_to_remote(:delete.l, {:url => comment_path(comment.commentable_type, comment.commentable_id, comment), :method => :delete, 500 => 'alert(\'Sorry, there was a server error\'); return false', :success => visual_effect(:fade, "comment_#{comment.id}"), :confirm => :are_you_sure_you_want_to_permanently_delete_this_comment.l} )
+ -if logged_in? && (current_user.admin? || current_user.moderator?)
+ %li.edit=link_to_remote :edit.l, :url => {:controller => 'comments', :action => 'edit', :id => comment.id}, :method => :get
+
.entry-content= comment.comment
- highlighted ||= nil
View
29 app/views/comments/_edit_form.html.haml
@@ -0,0 +1,29 @@
+- form_remote_for(:comment, :loading => "$$('#comment_#{comment.id} .errors')[0].innerHTML = '';", :before => "tinyMCE.activeEditor.save();", :url => {:controller => 'comments', :action => 'update', :id => comment.id}, :method => :put, :html => {:id => "edit_comment_#{comment.id}_form", :class => "MainForm"}) do |f|
+ .errors
+ %label
+ %em=:comment_character_limit.l
+ = text_area :comment, :comment, {:size => "86x5", :class => "rich_text_editor"}
+
+ %label{"for"=>"comment[author_name"}
+ =:name.l
+ %em="(#{:optional.l})"
+ = f.text_field :author_name, :size => 35
+ %label{"for"=>"comment[author_email"}
+ ="#{:email.l}:"
+ %em="(#{:comment_author_email_required.l})"
+ = f.text_field :author_email, :size => 35
+ %br
+ %label
+ =f.check_box :notify_by_email, :style => 'width: 10px;'
+ =:notify_me_of_follow_ups_via_email.l
+ -if comment.commentable.respond_to?(:send_comment_notifications?) && !comment.commentable.send_comment_notifications?
+ %em="(#{:comment_notifications_off.l})"
+
+ %label{"for"=>"comment[author_url"}
+ =:comment_web_site_label.l
+ %em="(#{:optional.l})"
+ = f.text_field :author_url, :size => 35
+
+ %p
+ = submit_tag :add_comment.l
+ = link_to_remote :cancel.l, :url => {:controller => 'comments', :action => 'edit', :id => comment.id, :cancel => true}, :method => :get
View
7 app/views/comments/edit.js.rjs
@@ -0,0 +1,7 @@
+if params[:cancel]
+ page << "tinyMCE.execCommand('mceFocus', false, $$('#edit_comment_#{@comment.id}_form textarea')[0]); tinyMCE.execCommand('mceRemoveControl', false, $$('#edit_comment_#{@comment.id}_form textarea')[0]);"
+ page.replace "comment_#{@comment.id}", :partial => 'comments/comment.html.haml', :locals => {:comment => @comment, :highlighted => true}
+else
+ page.replace_html "comment_#{@comment.id}", :partial => 'comments/edit_form', :locals => {:comment => @comment}
+ page << "tinyMCE.execCommand('mceAddControl', true, $$('#edit_comment_#{@comment.id}_form textarea')[0]);"
+end
View
9 app/views/comments/update.js.rjs
@@ -0,0 +1,9 @@
+if @comment.errors.any?
+ page.select("#comment_#{@comment.id} .errors").invoke('update', @comment.errors.full_messages.to_sentence)
+ page << "new Effect.Highlight($$('#comment_#{@comment.id} .errors')[0]);"
+else
+ page << "tinyMCE.execCommand('mceFocus', false, $$('#edit_comment_#{@comment.id}_form textarea')[0]); tinyMCE.execCommand('mceRemoveControl', false, $$('#edit_comment_#{@comment.id}_form textarea')[0]);"
+ page.replace "comment_#{@comment.id}", :partial => 'comments/comment.html.haml', :locals => {:comment => @comment, :highlighted => true}
+end
+
+
View
6 app/views/friendships/index.xml.builder
@@ -1,6 +1,6 @@
xml.instruct!
xml.RelationViewerData do
- xml.Settings :appTitle=>"#{AppConfig.community_name} Friendships Browser", :WWWLinkTargetFrame=>"_blank", :startID=>"#{application_url}#{@user.login_slug}",
+ xml.Settings :appTitle=>"#{AppConfig.community_name} Friendships Browser", :WWWLinkTargetFrame=>"_blank", :startID=>"#{home_url}#{@user.login_slug}",
:defaultRadius=>"170", :maxRadius=>"240", :contextRadius=>"130" do
xml.RelationTypes do
xml.DirectedRelation :color=>"0x999999", :lineSize=>"3"
@@ -14,7 +14,7 @@ xml.RelationViewerData do
@users.each do |user|
imageUrl = (user.avatar_photo_url(:thumb).eql?('icon_missing_thumb.png') ? '/images/icon_missing_thumb.png' : user.avatar_photo_url(:thumb) )
xml.Person :tags => "#{user.tags.collect{|t| t.name }.join(", ")}", :dataURL=>"friendships.xml?id=#{user.id}",
- :id=>"#{application_url}#{user.login_slug}", :name=>"#{user.login}", :imageURL=>imageUrl, :URL=>"#{application_url}#{user.login_slug}" do
+ :id=>"#{home_url}#{user.login_slug}", :name=>"#{user.login}", :imageURL=>imageUrl, :URL=>"#{home_url}#{user.login_slug}" do
xml.cdata!( truncate_words( strip_tags(user.description), 50, '...') )
end
end
@@ -22,7 +22,7 @@ xml.RelationViewerData do
xml.Relations do
@friendships.each do |friendship|
- xml.DirectedRelation :fromID=>"#{application_url}#{friendship.user.login_slug}", :toID=>"#{application_url}#{friendship.friend.login_slug}"
+ xml.DirectedRelation :fromID=>"#{home_url}#{friendship.user.login_slug}", :toID=>"#{home_url}#{friendship.friend.login_slug}"
end
end
View
4 app/views/layouts/application.html.haml
@@ -1,7 +1,7 @@
!!!
%html
%head
- %link{:rel=>"shortcut icon", :href=>"#{application_url}favicon.ico"}
+ %link{:rel=>"shortcut icon", :href=>"#{home_url}favicon.ico"}
%meta{"http-equiv"=>"Content-Type", :content=>"text/html;charset=utf-8"}
%title= page_title
@@ -34,7 +34,7 @@
#CommunityFooter
%ul
%li
- %a{:href=>application_url, :title=>"#{AppConfig.community_name} " + :home.l}= :home.l
+ %a{:href=>home_url, :title=>"#{AppConfig.community_name} " + :home.l}= :home.l
- if !logged_in?
%li
= link_to :log_in.l , login_path
View
4 app/views/layouts/beta.html.haml
@@ -1,7 +1,7 @@
!!!
%html
%head
- %link{:rel=>"shortcut icon", :href=>"#{application_url}favicon.ico"}
+ %link{:rel=>"shortcut icon", :href=>"#{home_url}favicon.ico"}
%meta{"http-equiv"=>"Content-Type", :content=>"text/html;charset=utf-8"}
%title
@@ -15,7 +15,7 @@
#doc2.yui-t6
#hd
%h1
- %a{:href=>application_url, :title=>"#{AppConfig.community_name}"}= AppConfig.community_name
+ %a{:href=>home_url, :title=>"#{AppConfig.community_name}"}= AppConfig.community_name
#NavBar
%ul
View
8 app/views/shared/_header.html.haml
@@ -1,6 +1,6 @@
#hd
%h1
- %a{:href=>application_url, :title=>"#{AppConfig.community_name}"}= AppConfig.community_name
+ %a{:href=>home_url, :title=>"#{AppConfig.community_name}"}= AppConfig.community_name
- if logged_in?
= render :partial => 'shared/user_menu'
- if current_user.unread_messages?
@@ -25,17 +25,17 @@
/ SiteSearch Google
%form{:method=>"get", :action=>"http://www.google.com/custom", :target=>"_top"}
- %input{ :type=>"hidden", :name=>"domains", :value=>"#{application_url}"}
+ %input{ :type=>"hidden", :name=>"domains", :value=>"#{home_url}"}
%label{ :for=>"search", :style=>"display: none"}
= :search.l+" #{AppConfig.community_name}"
%input{ :type=>"text",:name=>"q", :size=>"17", :maxlength=>"255", :id=>"search"}
- %input{ :type=>"hidden", :name=>"sitesearch", :value=>"#{application_url}", :id=>"ss1"}
+ %input{ :type=>"hidden", :name=>"sitesearch", :value=>"#{home_url}", :id=>"ss1"}
%input{ :type=>"hidden", :name=>"client", :value=>"pub-9113954708528841"}
%input{ :type=>"hidden", :name=>"forid", :value=>"1"}
%input{ :type=>"hidden", :name=>"channel", :value=>"1842224655"}
%input{ :type=>"hidden", :name=>"ie", :value=>"ISO-8859-1"}
%input{ :type=>"hidden", :name=>"oe", :value=>"ISO-8859-1"}
- %input{ :type=>"hidden", :name=>"cof", :value=>"GALT:#008000;GL:1;DIV:#FFFFFF;VLC:663399;AH:center;BGC:FFFFFF;LBGC:FFFFFF;ALC:0066CC;LC:0066CC;T:000000;GFNT:0000FF;GIMP:0000FF;LH:50;LW:90;S:http://#{application_url};FORID:1"}
+ %input{ :type=>"hidden", :name=>"cof", :value=>"GALT:#008000;GL:1;DIV:#FFFFFF;VLC:663399;AH:center;BGC:FFFFFF;LBGC:FFFFFF;ALC:0066CC;LC:0066CC;T:000000;GFNT:0000FF;GIMP:0000FF;LH:50;LW:90;S:http://#{home_url};FORID:1"}
%input{ :type=>"hidden", :name=>"hl", :value=>"en"}
/ NavBar
View
4 app/views/sitemap/index.xml.builder
@@ -2,14 +2,14 @@ xml.instruct!
xml.urlset "xmlns" => "http://www.google.com/schemas/sitemap/0.84" do
xml.url do
- xml.loc "#{application_url}"
+ xml.loc "#{home_url}"
xml.lastmod w3c_date(Time.now)
xml.changefreq "hourly"
end
@users.each do |user|
xml.url do
- xml.loc "#{application_url}#{user.login_slug}"
+ xml.loc "#{home_url}#{user.login_slug}"
xml.lastmod w3c_date(user.updated_at || Time.now)
xml.changefreq "weekly"
xml.priority 0.7
View
2  app/views/user_notifier/follow_up_comment_notice.erb
@@ -12,4 +12,4 @@ The <%= AppConfig.community_name %> team
If you don't want to receive e-mails like this one from <%= AppConfig.community_name %>, you can change
your e-mail preferences here:
-<%= application_url %>/<%= @user.login_slug %>/edit_account
+<%= home_url %>/<%= @user.login_slug %>/edit_account
View
2  app/views/user_notifier/new_forum_post_notice.erb
@@ -11,4 +11,4 @@ The <%= AppConfig.community_name %> team
If you don't want to receive e-mails like this one from <%= AppConfig.community_name %>, you can change
your e-mail preferences here:
-<%= application_url %>/<%= @user.login_slug %>/edit_account
+<%= home_url %>/<%= @user.login_slug %>/edit_account
View
6 lib/authenticated_system.rb
@@ -95,13 +95,13 @@ def access_denied
accepts.xml do
headers["Status"] = "Unauthorized"
headers["WWW-Authenticate"] = %(Basic realm="Web Password")
- render :text => "Could't authenticate you", :status => '401 Unauthorized'
+ render :text => "Couldn't authenticate you", :status => '401 Unauthorized'
end
accepts.js do
store_location
render :update do |page|
- page.redirect_to login_path and return false
- end
+ page.redirect_to login_path
+ end and return false
end
end
false
View
8 sample_files/sample_themes/dappled/views/shared/_header.html.haml
@@ -1,6 +1,6 @@
#hd
%h1
- %a{:href=>application_url, :title=>"#{AppConfig.community_name}"}= AppConfig.community_name
+ %a{:href=>home_url, :title=>"#{AppConfig.community_name}"}= AppConfig.community_name
- if logged_in?
= render :partial => 'shared/user_menu'
@@ -22,17 +22,17 @@
/ SiteSearch Google
%form{:method=>"get", :action=>"http://www.google.com/custom", :target=>"_top"}
- %input{ :type=>"hidden", :name=>"domains", :value=>"#{application_url}"}
+ %input{ :type=>"hidden", :name=>"domains", :value=>"#{home_url}"}
%label{ :for=>"search", :style=>"display: none"}
= "Search #{AppConfig.community_name}"
%input{ :type=>"text",:name=>"q", :size=>"17", :maxlength=>"255", :id=>"search"}
- %input{ :type=>"hidden", :name=>"sitesearch", :value=>"#{application_url}", :id=>"ss1"}
+ %input{ :type=>"hidden", :name=>"sitesearch", :value=>"#{home_url}", :id=>"ss1"}
%input{ :type=>"hidden", :name=>"client", :value=>"pub-9113954708528841"}
%input{ :type=>"hidden", :name=>"forid", :value=>"1"}
%input{ :type=>"hidden", :name=>"channel", :value=>"1842224655"}
%input{ :type=>"hidden", :name=>"ie", :value=>"ISO-8859-1"}
%input{ :type=>"hidden", :name=>"oe", :value=>"ISO-8859-1"}
- %input{ :type=>"hidden", :name=>"cof", :value=>"GALT:#008000;GL:1;DIV:#FFFFFF;VLC:663399;AH:center;BGC:FFFFFF;LBGC:FFFFFF;ALC:0066CC;LC:0066CC;T:000000;GFNT:0000FF;GIMP:0000FF;LH:50;LW:90;S:http://#{application_url};FORID:1"}
+ %input{ :type=>"hidden", :name=>"cof", :value=>"GALT:#008000;GL:1;DIV:#FFFFFF;VLC:663399;AH:center;BGC:FFFFFF;LBGC:FFFFFF;ALC:0066CC;LC:0066CC;T:000000;GFNT:0000FF;GIMP:0000FF;LH:50;LW:90;S:http://#{home_url};FORID:1"}
%input{ :type=>"hidden", :name=>"hl", :value=>"en"}
/ NavBar
View
17 test/functional/admin_controller_test.rb
@@ -2,7 +2,7 @@
class AdminControllerTest < ActionController::TestCase
fixtures :users, :categories, :roles
-
+
def test_should_get_index
login_as :admin
get :users
@@ -17,9 +17,22 @@ def test_should_not_get_index
def test_should_not_activate_user
login_as :quentin
- put :activate_user
+ users(:quentin).update_attribute('activation_code', 'test')
+ users(:quentin).update_attribute('activated_at', nil)
+
+ put :activate_user, :id => 1
+ assert !users(:quentin).active?
assert_redirected_to login_path
end
+
+ def test_should_not_activate_user_js
+ users(:quentin).update_attribute('activation_code', 'test')
+ users(:quentin).update_attribute('activated_at', nil)
+
+ get :activate_user, :id => 1, :format => 'js'
+ assert !users(:quentin).active?
+ end
+
def test_should_activate_user
users(:quentin).update_attribute('activation_code', 'test')
View
26 test/functional/comments_controller_test.rb
@@ -159,6 +159,32 @@ def test_should_no_unsubscribe_with_bad_token
post :unsubscribe, :commentable_type => 'User', :commentable_id => users(:quentin).to_param, :comment_id => comment.id, :token => 'badtokengoeshere'
assert comment.reload.notify_by_email.eql?(true)
end
+
+ def test_should_get_edit_js_as_admin
+ login_as :admin
+ get :edit, :id => comments(:quentins_comment_on_his_own_post), :format => 'js'
+ assert_response :success
+ end
+
+ def test_should_update_as_admin
+ login_as :admin
+ edited_text = 'edited the comment body'
+ put :update, :id => comments(:quentins_comment_on_his_own_post), :comment => {:comment => edited_text}, :format => 'js'
+
+ assert assigns(:comment).comment.eql?(edited_text)
+ assert_response :success
+ end
+
+ def test_should_not_update_if_not_admin_or_moderator
+ login_as :quentin
+
+ edited_text = 'edited the comment body'
+ put :update, :id => comments(:quentins_comment_on_his_own_post), :comment => {:comment => edited_text}, :format => "js"
+
+ assert_response :success #js redirect
+ assert_not_equal(comments(:quentins_comment_on_his_own_post).comment, edited_text)
+ end
+
protected
View
4 test/functional/sb_posts_controller_test.rb
@@ -171,17 +171,21 @@ def test_disallow_new_post_to_locked_topic
test "should create anonymous reply" do
+ AppConfig.allow_anonymous_forum_posting = true
assert_difference SbPost, :count, 1 do
post :create, :forum_id => forums(:rails).to_param, :topic_id => topics(:pdi).to_param, :post => { :body => 'blah', :author_email => 'foo@bar.com' }
assert_redirected_to :controller => "topics", :action => "show", :forum_id => forums(:rails).to_param, :id => topics(:pdi).to_param
end
+ AppConfig.allow_anonymous_forum_posting = false
end
test "should fail creating an anonymous reply" do
+ AppConfig.allow_anonymous_forum_posting = true
assert_difference SbPost, :count, 0 do
post :create, :forum_id => forums(:rails).to_param, :topic_id => topics(:pdi).to_param, :post => { :body => 'blah', :author_email => 'foo' }
assert_response :redirect
end
+ AppConfig.allow_anonymous_forum_posting = false
end
View
8 test/functional/users_controller_test.rb
@@ -361,6 +361,14 @@ def test_only_admin_can_assume_id
assert_not_equal UserSession.find.record, users(:aaron)
assert_nil session[:admin_id]
end
+
+ def test_only_admin_can_assume_id_js
+ login_as :quentin
+ post :assume, :id => users(:aaron).id, :format => 'js'
+ assert_response :success
+ assert_not_equal UserSession.find.record, users(:aaron)
+ assert_nil session[:admin_id]
+ end
def test_return_admin_should_set_user_to_admin
login_as :quentin
View
1  test/unit/sb_post_test.rb
@@ -123,6 +123,7 @@ def test_should_be_deleted_when_user_destroyed
end
test "should not allow anonymous posting" do
+ AppConfig.allow_anonymous_forum_posting = false
topic = topics(:pdi)
post = topic.sb_posts.create(:topic => topic, :body => "Ok!", :author_email => 'anon@example.com', :author_ip => "1.2.3.4")
assert !post.valid?
Please sign in to comment.
Something went wrong with that request. Please try again.