The Railscasts site has been getting a lot of comment spam in the past, but no longer. In this episode I will show you how I solved this problem by using the Akismet web service.
Great stuff, Ryan.
Unfortunately Akismet is pretty pricey, if you want to use it commercially. I am working for a Blog-Host, and using Akismet would cost double of what we pay for server hosting.
But what I have seen in your screencast seems like it may be worth the money. Currently we have a custom CAPTCHA, which works fine for now, except that you can't user CAPTCHAs for Trackbacks.
Anyway, thanks for this plugin, I will try it out.
Just watched this again and noticed something curious.. what does an exclamation point do to an method? You have this one your mark_as_spam/ham actions.
There is also a Ruby on Rails Akismet project on Ruby Forge from Josh French. Josh French said by the README, "This plugin is based on David Czarnecki's Akismet.rb (http://www.blojsom.com/blog/david/nerdery/2005/12/02/Akismet-API-in-Ruby.html) found via Soaked and Soaped (http://sas.sparklingstudios.com/articles/2006/10/01/how-to-protect-a-rails-application-against-spam-with-akismet)"
If I'm not mistaken, the exclamation mark at the end of methods means that you are modifying a variable instead of just accessing it. For example, "foobar.sort" will return a sorted array, but leave foobar alone. "foobar.sort!" will actually replace foobar with the sorted array.
By adding an exclamation mark to the end of the method like that he's just noting that the method is changing a class variable. It's optional, but good practice.
Nice screencast. This is something I definitely need to do on a few of my sites. I've used Akismet before with PHP but never implemented it with rails yet.
IIRC, the exclamation is just a character and does not specifically modify the behavior of any method. It has become common in the Rails framework to denote that the method does something different (as mutle noted) as opposed to a method without it. And thus many people have adopted it's use.
Same as the equals in a method name. It doesn't really change the behavior of the method, but when creating getter/setter methods it makes you code more clear. Hence for the setter you can write 'mysetter=myvalue' instead of 'mysetter myvalue'.
I like to think of the exclamation mark as a warning that the method does something potentially dangerous/permanent. This way you think twice before using it and make sure that's really what you want.
Here I'm using it because the method is contacting Akismet in addition to changing the model's state. This method shouldn't be called lightly.
@Wes, are you asking where the "approved?" method comes from? This is generated automatically by Rails. Since there's a boolean "approved" attribute in the database, it gives us the "approved?" method which will return true/false.
There's also a class method called "approved" which just does a find for all comments which are approved. I use this for listing the comments for each episode. It's completely separate from the "approved?" instance method.
I've also been having the same issues with spam on my blog, I've followed your code down to the last line and implemented it in my app. I've also tested your code by pasting in some of the spam that has hit my comment (the spam comment before this one is mine) but as you can see the spam isn't getting blocked. Please advise on way forward.
Ryan, I decided to test this further by pasting the same spam comments on other blogs that use Akismet such as www.techcrunch.com (wordpress blog), none of the spam comments were saved. Probably I don't understand the inner workings of Akismet but I was of the impression that it also checks for certain patterns like a high number of links in the content in order to flag it as spam, may be you could correct me on this.
@john, Akismet learns as it goes, so it may have flagged your IP address as a possible spammer. That's my guess, but there could be a number of other factors coming into play.
Either way, I don't recommend testing this out because the last thing you want is to be considered a spammer by Akismet and then everything you post on other blogs will be flagged.
Not on-topic, but it would be awesome if you could do a video on how to create in place editing. The Rails built-in methods are apparently being deprecated in 2.0. There evidently will (is?) a plugin alternative, but I can't find any information.
No one seems to know what the best practice is right now in terms of in-place editing. Your my hero, Ryan! :)
Also good to see that someone has finally made a plugin available that wraps up the Akismet functionality. For a year or so now I've been using a heavily modified version of David Czarnecki's akismet.rb to fulfil this functionality.
@mutle - In the past I've attempted to create my own spam filtering, using an interface that accepts regexps, and monitoring the performance of these to make adjustments. In the end it was a lot of work both in implementation and time to administrate. I'm tempted to have a go again though.
Slightly off topic. Curious about the use of custom RESTful actions:
From reading the on-line literature - Purist seem to say you can always adhere to the default actions by adding new controllers.
In your case why aren't you following the pure RESTful pattern and if you were would you build an approval controller where "create" is approving and "destroy" is marking as spam ?
@ofer, good point. I could create a "comment_approval" RESTful controller and avoid the custom REST actions. However, I'm no REST purist. I try to weigh the practicality of the different approaches. I consider the approve/reject actions to be largely about the comment resource (just different forms of update) so I decided to keep them in that controller.
I also like to follow the "simplest thing that could possible work" principle. Adding custom actions feels simpler. However, it's also a bit of a smell and could require some refactoring. The stench isn't quite strong enough IMO though.
Wouldn't it be nice to include the approved filter in the relationship between the Post and the Comment. Like this:
has_many( :comments, :class_name => "Comment", :conditions => {:approved => true}, :dependent => :destroy )
Then the simplify the finding in the controller as the condition would be built into the association so a simple find(:all) would suffice. Just my $0.02. Anyway a great screencast and keep up the good work.
This is my first comment so I want to start by saying thanks for all the screencasts. They've been a huge help.
I'm in the process of implementing this and I was wondering what would happen if Akismet was offline. I disconnected from the intertubes in my dev environment and got a socket error crash so I'll need to catch that.
Also, I can't help noticing that a couple of helpless credit card user posts got through in December. I'm wondering if it would be worth running comments against Akismet more than once. Akismet's opinion of a comment might change after a day or two as it continues to learn. I'd just be concerned about abusing the TOS.
Thanks Ryan, great tutorial. I was trying to do it with the rakismet plugin but found it confusing. Your plugin seems easier and it comes with a screencast so that's easier!
Just a question, are you doing anything with the permalink attribute on the comments? Is that needed or is it optional?
@David, you may want to try integrating Facebook Connect comments. You can comment anonymously, if you do you have to pass a CAPTCHA. Or you can post a comment through your a Facebook login. It's very easy to integrate, free, and requires no knowledge other than copying and pasting. http://developers.facebook.com/ I love it and use it in every prototype I build.
This screencast really saved my day after recent spam comment attacked my blog.
I just put on the code in my blog and will see how good Akismet can stop the spam comments.
A big thank you to you, Ryan!
Just what I needed for my site. Thanks.
But you are still getting some smarter spam an this site. I advice you to make a list of all the new comments so you have a better view over them.
gaert!vrey good.
tkanhs
Great stuff, Ryan.
Unfortunately Akismet is pretty pricey, if you want to use it commercially. I am working for a Blog-Host, and using Akismet would cost double of what we pay for server hosting.
But what I have seen in your screencast seems like it may be worth the money. Currently we have a custom CAPTCHA, which works fine for now, except that you can't user CAPTCHAs for Trackbacks.
Anyway, thanks for this plugin, I will try it out.
*injects cable modem needle into arm*
Ahhhhhh...yeah that's the stuff.
*stays high on Rails for hours*
Just watched this again and noticed something curious.. what does an exclamation point do to an method? You have this one your mark_as_spam/ham actions.
There is also a Ruby on Rails Akismet project on Ruby Forge from Josh French. Josh French said by the README, "This plugin is based on David Czarnecki's Akismet.rb (http://www.blojsom.com/blog/david/nerdery/2005/12/02/Akismet-API-in-Ruby.html) found via Soaked and Soaped (http://sas.sparklingstudios.com/articles/2006/10/01/how-to-protect-a-rails-application-against-spam-with-akismet)"
If I'm not mistaken, the exclamation mark at the end of methods means that you are modifying a variable instead of just accessing it. For example, "foobar.sort" will return a sorted array, but leave foobar alone. "foobar.sort!" will actually replace foobar with the sorted array.
By adding an exclamation mark to the end of the method like that he's just noting that the method is changing a class variable. It's optional, but good practice.
Nice screencast. This is something I definitely need to do on a few of my sites. I've used Akismet before with PHP but never implemented it with rails yet.
I recommend using defensio. It's smarter than Akismet and learns as it goes. It also has a much better API for developers.
Check it out: http://www.defensio.com/
In ActiveRecord methods the exclamation mark is used to tell that it raises an exception if something goes wrong (like save!, create!).
Thanks for that concise explanation Saxon!
My Rails-fu went up a few points
IIRC, the exclamation is just a character and does not specifically modify the behavior of any method. It has become common in the Rails framework to denote that the method does something different (as mutle noted) as opposed to a method without it. And thus many people have adopted it's use.
Same as the equals in a method name. It doesn't really change the behavior of the method, but when creating getter/setter methods it makes you code more clear. Hence for the setter you can write 'mysetter=myvalue' instead of 'mysetter myvalue'.
I like to think of the exclamation mark as a warning that the method does something potentially dangerous/permanent. This way you think twice before using it and make sure that's really what you want.
Here I'm using it because the method is contacting Akismet in addition to changing the model's state. This method shouldn't be called lightly.
Ryan, shouldn't you have some kind of "is_admin?" before filter protecting your destroy_multiple/approve/reject actions?
@Wincent, that's already done. If you look in the controller on the screencast you can see there's an "authorize" before filter.
Did you add an index for comments.approved? Just curious.
Just to note, methods ending in ? typically return true or false. Again, it's convention, not a requirement. It just makes it easier to read.
@Wes, are you asking where the "approved?" method comes from? This is generated automatically by Rails. Since there's a boolean "approved" attribute in the database, it gives us the "approved?" method which will return true/false.
There's also a class method called "approved" which just does a find for all comments which are approved. I use this for listing the comments for each episode. It's completely separate from the "approved?" instance method.
Hi Ryan,
I've also been having the same issues with spam on my blog, I've followed your code down to the last line and implemented it in my app. I've also tested your code by pasting in some of the spam that has hit my comment (the spam comment before this one is mine) but as you can see the spam isn't getting blocked. Please advise on way forward.
Ryan, I decided to test this further by pasting the same spam comments on other blogs that use Akismet such as www.techcrunch.com (wordpress blog), none of the spam comments were saved. Probably I don't understand the inner workings of Akismet but I was of the impression that it also checks for certain patterns like a high number of links in the content in order to flag it as spam, may be you could correct me on this.
@john, Akismet learns as it goes, so it may have flagged your IP address as a possible spammer. That's my guess, but there could be a number of other factors coming into play.
Either way, I don't recommend testing this out because the last thing you want is to be considered a spammer by Akismet and then everything you post on other blogs will be flagged.
Not on-topic, but it would be awesome if you could do a video on how to create in place editing. The Rails built-in methods are apparently being deprecated in 2.0. There evidently will (is?) a plugin alternative, but I can't find any information.
No one seems to know what the best practice is right now in terms of in-place editing. Your my hero, Ryan! :)
Great screencast Ryan!
Also good to see that someone has finally made a plugin available that wraps up the Akismet functionality. For a year or so now I've been using a heavily modified version of David Czarnecki's akismet.rb to fulfil this functionality.
@mutle - In the past I've attempted to create my own spam filtering, using an interface that accepts regexps, and monitoring the performance of these to make adjustments. In the end it was a lot of work both in implementation and time to administrate. I'm tempted to have a go again though.
Slightly off topic. Curious about the use of custom RESTful actions:
From reading the on-line literature - Purist seem to say you can always adhere to the default actions by adding new controllers.
In your case why aren't you following the pure RESTful pattern and if you were would you build an approval controller where "create" is approving and "destroy" is marking as spam ?
@ofer, good point. I could create a "comment_approval" RESTful controller and avoid the custom REST actions. However, I'm no REST purist. I try to weigh the practicality of the different approaches. I consider the approve/reject actions to be largely about the comment resource (just different forms of update) so I decided to keep them in that controller.
I also like to follow the "simplest thing that could possible work" principle. Adding custom actions feels simpler. However, it's also a bit of a smell and could require some refactoring. The stench isn't quite strong enough IMO though.
Wouldn't it be nice to include the approved filter in the relationship between the Post and the Comment. Like this:
has_many( :comments, :class_name => "Comment", :conditions => {:approved => true}, :dependent => :destroy )
Then the simplify the finding in the controller as the condition would be built into the association so a simple find(:all) would suffice. Just my $0.02. Anyway a great screencast and keep up the good work.
This is my first comment so I want to start by saying thanks for all the screencasts. They've been a huge help.
I'm in the process of implementing this and I was wondering what would happen if Akismet was offline. I disconnected from the intertubes in my dev environment and got a socket error crash so I'll need to catch that.
Also, I can't help noticing that a couple of helpless credit card user posts got through in December. I'm wondering if it would be worth running comments against Akismet more than once. Akismet's opinion of a comment might change after a day or two as it continues to learn. I'd just be concerned about abusing the TOS.
Does anyone know if there is some kind of 'safe' spam comment you can put in?
I read somewhere that viagara_test_123 was always marked as spam, but if this comment shows up it's obviously not true..
Thanks Ryan, great tutorial. I was trying to do it with the rakismet plugin but found it confusing. Your plugin seems easier and it comes with a screencast so that's easier!
Just a question, are you doing anything with the permalink attribute on the comments? Is that needed or is it optional?
@David, you may want to try integrating Facebook Connect comments. You can comment anonymously, if you do you have to pass a CAPTCHA. Or you can post a comment through your a Facebook login. It's very easy to integrate, free, and requires no knowledge other than copying and pasting. http://developers.facebook.com/ I love it and use it in every prototype I build.
Awesome!
Thanks for the railscast! as usual superb!
This screencast really saved my day after recent spam comment attacked my blog.
I just put on the code in my blog and will see how good Akismet can stop the spam comments.
A big thank you to you, Ryan!
yum yum *gobbles*.
Just what I needed for my site. Thanks.
But you are still getting some smarter spam an this site. I advice you to make a list of all the new comments so you have a better view over them.
Are you still using Akismet? It's not working very well is it?
I really enjoyed the article. It proved to be Very helpful to me and I
am sure to all the comment here!
Hi!
Thanks for cool screencast.
I think, I found a security problem with your code. Please, correct me, if I'm wrong.
I think, it is possible to call 'destroy' and 'destroy_multiple' by modifying URL and remove any comments without being administrator.
Am I right?
Is it possible to prevent such attacks without adding 'if admin?' checks in nearly each method?
Regards,
Boris Brodski
Just wondering, being pretty old... is this railscast still viable or is there another tool we should be looking at?
thanks,
rich
can we believe this ?
This episode has been updated to Rails 5 as a blog post Integrating Google reCAPTCHA with Rails 5 App