Chronicling my experiences with ruby on rails, web application development/management.

Monday, May 18, 2009

Spam Off - Acts_as_Snook Tutorial

You wrote an awesome blog post. You've enabled comments for this awesome blog post, only to find that some crappy spam turnkey operation has hit your post with 1000 cialis ads, and the other wang pill advertisements.

Listen, Comment Spam sucks ass. If you've ever searched for an answer to stopping it, your research points you to one of four ways to stop comment spam traditionally. These are:


  • No comment spam filters at all - Users are allowed to comment willy nilly.

  • Captchas - weird images that look as though your seeing through beer goggles, whose text usually has to be written into a text field to prove your comment is coming from a human.

  • Site Members can comment only - the site's membership, of which you are visiting, are only allowed to comment on stories. I am using that method in my blog, atm.

  • Moderated comments - your honest feedback about a post can only be posted, if a nameless/faceless drone 'approves' your comment worthy of being posted. Doesn't say much for transparency.


While all of these above methods work to keep spam to a minimum, (and really that's all you can hope for i.e., minimal spam), there are glaring flaws in each and everyone one of the above methods. Whether it's accessibility, too cumbersome to keep up with, or too communist, most of these methods have some kind of tradeoff.


Enter Acts_as_snook


From the README:ActsAsSnook is a simple and elegant method of handling comment spam that doesn’t rely on CAPTCHAS or Javascript or external web services. It uses a point based system to detect common traits of comment spam.


The acts_as_snook plugin is a great way to keep spam at a minimum. Rather then asking the user to do anything, acts_as_snook "scores" each comment based on a scoring system by Jonathon Snook. Once the comment has been saved into the database, the comment receives one of three possible spam statuses:


  • Spam - the comment didn't pass the scoring system and will have a status of spam, set in the database.

  • Ham - the comment passes the scoring system and it's status is set to ham in the db.

  • Moderate - the comment is too close to call spam or ham, so it's status is set to moderate and is awaiting moderation.


About the Plugin


If anyone trolls in #rubyonrails, the IRC chatroom, you'll know the author of the acts_as_snook plugin, rsl.


He is also responsible for getting this song in my head, at the time of writing. Bastard.


Tutorial - How to use the Plugin


Don't cut and paste!!! this stupid blogger crap is garbage! So if you want to cut and paste, you'll need to delete the spaces after and before each < or > bracket.

Assumptions - you know how to create a typical blog application that will use embedded comments at the end of each post. I'm assuming that you will have two models Post andComment. Before you begin, get a working Post model ready, as this tutorial is only about adding a Comment model that uses acts_as_snook. Lastly, you'll want to make it prettier once your done these steps, so that is up to you do after, post modem.



  1. You have a working Post model, right???

    Get your Post model working how you want it to work. The inner workings aren't important for this tutorial.


  2. Generate a Comments Model and a Post Migration

    We'll use the default settings in acts_as_snook, so that means we need our database to house these Comments table columns as outlined below.

    script/generate rspec_scaffold Comment author:string email:string url:string body:text spam_status:string post_id:integer


    Since we want to display how many GOOD comments we have (i.e, the ham), acts_as_snook will run a counter cache for each Post's ham comments. All you have to create the following migration file and add the migration column information to it:

    script/generate migration AddHamCountToPost


    In your new migration file add the following:

    class AddHamCountToPost < ActiveRecord::Migration
    def self.up
    add_column :posts, :ham_comments_count, :integer, :default => 0
    end

    def self.down
    remove_column :posts, :ham_comments_count
    end
    end



  3. Run Migration with
    rake db:migrate


  4. Add Acts_as_Snook functionality


    First make sure Post has_many Comments and Comments belongs_to Post.

    Next add this to your Comment.rb file
    acts_as_snook
    Damn, that was easy.



  5. Fix the Comment views


    Right now, your views are set to show all comments, that is not good. Let's make your views shine! So in your Post#show view, here is what i've done (since i show my comments at the end of each blog post, my @comments= @post.comments. If you don't understand that, read up on associations in the rails guide). I'll explain the code further afterwards.


    < h1 >Comments< / h1 >
    < % unless @comments.blank? %>
    < % @comments.ham.each do |comment| %>
    < p >
    < % if comment.url.blank? %>
    < %= comment.author %>
    < % else %>
    < %= link_to comment.author, comment.url %>
    < % end %>
    says:< br/ >
    < %= comment.body %>< /p>
    < % end %>
    < % else %>
    < p>Be the first person to comment!< /p>
    < % end %>


    Simply put, we only want to show the ham(the good comments) and not the spam(shitty comments). So we iterate through each ham comment, using the acts_as_snook supplied method ham.

    The rest of this code, just shows the comment in a certain way that i wanted it to appear, your mileage will vary.


  6. Showing a Comment Count


    One last little touch, is showing your readers how many comments each post has. I put this information on the Post#index page, usually. Here's one way you can implement a count for all ham comments.
    < % @posts.each do |post| % >
    < h2 >< %= link_to post.title, post % > ::~ < %= pluralize post.ham_comments_count, "Comment" % >< /h2 >
    < p >< %= truncate post.body, 25 % >< /p >
    < % end % >

    Notice again, we iterate through the posts and for each post, we add their ham_comments_count.



Conclusion


Acts_as_snook gives us a lot of easy to use methods, which i'll write more about in Acts_as_Snook part 2, which will include a moderation area for administrators. Hopefully Acts_as_snook will take care of your comment spam nightmares, while giving you more time to sing, same as it ever was. Letting the days go by....

Tuesday, May 5, 2009

Upcoming update to Theme support plugin

I'm updating the theme support plugin to work with 2.3.2 now. jystewart has done a tonne of work with it, but his plugin doesn't yet use the helpers, which my version does.


We'll either combine forks or go our separate ways depending on how things play out, so stay tuned!!!


Here is the theme support repo on github. Follow the fork link to jystewart's if interested.


UPDATE - Use jystewart's github theme support for 2.3.2 and mine for 2.2.2 rails app

For some stupid reason the core guys flip flopped on a code insertion that screwed up 2.2.2 and I had to fix it so that the helpers worked in rails 2.2.2.

However, now that they reverted to code from 2.1.2 in 2.3.2, the old helper methods, which the jystewart version didn't touch, work fine. So rather then mess with it, it's easier just to use jystwart's version.

Lastly, in rails 3.0 i think it will revert again, so stay tuned.

Friday, May 1, 2009

When in doubt... Factory it out...

Boy did i have a doozy of a day trying to get some basic tests to pass, after i added some validations to a basic rspec_scaffold. Using the barebones tests, adding the validates_presence_of and a validates_uniqueness_of both borked the test... which was fine, i expected that.


What i didn't expect was that when i commented out both validations, my create test spec method would still work. in essence, it didn't. It could have been something i did, whatever. I fixed the failing tests by simply switching to Factories, instead of fixtures, or whatever the base rspec scaffold would use in a model test.


If you are getting some weird errors, simply try to use factories instead. Try out Factory Girl by Thoughtbot for an example of one that I can say works. haven't tried too hard to get machinist to work yet, but heard good things too.