How to limit users to one vote per IP address

A reader Aaron J. writes in response to the short article How to obtain the IP address of the current user:

Do you have any advice on how I can take this a step further? I am looking to limit a given user to one vote per session but I'm not sure how to achieve this. I'd appreciate any help you can offer. Thanks for your time.

Good question, Aaron. Remember that a session is simply tied to your browser cookie, so if we allow one vote per session, all the user has to do is clear their cookies and then vote again. And again. And again! So I think what you mean is how do we allow only one vote per IP address? To enforce that, we'll need to save a list of the IP addresses that have already sent us a vote. And before we record a vote, we need to make sure their IP address isn't already on that list. Further, we should remember which poll number they have voted in so we can have more than one poll in our application. We're going to need a simple table:

# lib/db/migrate/001_voter_log.rb
create_table :vote_log do |t|
  t.column :poll_id, :integer
  t.column :client_ip, :string
end

For simplicity of this example, we'll put all of our logic in the controller. (I won't show the VoteLog model class because it's empty.)

# RAILS_ROOT/app/controllers/poll_controller.rb
def record_a_vote
  poll_id = params[:poll_id]
  client_ip = request.remote_ip

  unless VoteLog.count(:all, :conditions => ['poll_id = ? AND client_ip = ?', poll_id, client_ip]) == 0
    redirect_to :already_voted
  end

  Poll.find(poll_id).record_vote(params[:candidate]) # Or however you count votes
  VoteLog.create(:poll_id => poll_id, :client_ip => client_ip)
end

def already_voted
  render :text => "You already voted, no cheating!"
end

Just keep in mind this approach might not be appropriate in all situations. Due to Network Address Translation (NAT) firewalls, many thousands of people will appear to have the same client_ip. This is particularly true in corporate environments. If that's a concern, you'll need to go with a full-blown registered-user approach.

Further Reading

How to obtain the IP address of the current user

Feedback and Article Ideas

Want to see a topic explored here? Send Me a Message.

Ruby, Rails and hash's with_indifferent_access

Long Names

Ruby developers like to be clear in their naming of variables, modules, classes, methods, and so on. I believe this comes from the idea that code should need very few inline comments if it is written clearly and descriptively. This descriptive naming strategy, combined with the decidedly non-English origins of Ruby (Japanese) and Rails (Danish), often result in oddly-named creations such as #execute_query_as_logged_in_user_without_transaction_logging (which I just made up), and #validates_numericality_of (which is real).

A newcomer might find these tongue-twisting, hyper-descriptive naming conventions maddening, but most of the time the intent becomes so much clearer as a result, it becomes infectious; in no time you find yourself writing your own 30-letter, borderline-semantically-correct method names and producing highly maintainable, readable, self-documenting code. That doesn't mean it happens all the time, though...

When I first ran across #with_indifferent_access I had no choice but to look it up as I had no hope of figuring out what it meant. "Indifferent" is how I feel when my wife asks me if I want chicken & snow peas or chicken & broccoli for dinner, not how I feel about ruby hashes. Admittedly, I was a little disappointed then to discover HashWithIndifferentAccess doesn't do much: it just extends a Hash so that you can retrieve values with either string or symbol keys. The Hash is "indifferent" as to which type of key you send it.

>> { :first => 'Kevin', :last => 'Hunt' }['last']
=> nil
>> require 'active_support' # Adds with_indifferent_access to Hash
=> true
>> { :first => 'Kevin', :last => 'Hunt' }.with_indifferent_access['last']
=> "Hunt"

This appears related to Hash#symbolize_keys, but also converts hashes within the hash.

>> { :name => { :first => 'Kevin', :last => 'Hunt' } }['name']['last']
NoMethodError: undefined method `[]' for nil:NilClass
        from (irb):4
>> { :name => { :first => 'Kevin', :last => 'Hunt' } }.with_indifferent_access['name']['last']
=> "Hunt"

Further Reading

Rails' HashWithIndifferentAccess docs

Feedback and Article Ideas

Want to see a topic explored here? Send Me a Message.

Using dp.SyntaxHighlighter with Valid XHTML

Over at Rails Authority I use a great client-side syntax highlighter called SyntaxHighlighter to make code examples look nice, but it doesn’t support Valid XHTML , so I enhanced it to handle an XHTML-compatible style, e.g.:

  <code class="code xml:nogutter">
    Your IP address is <%= @client_ip %>
  </code>

(Inspired by Ernest's post, linked above.)

To make this work, I made three changes to the shCore.js file (all within the HighlightAll function):

  1. In FindTagsByName, change line 618 that says:
      if(tags[i].getAttribute('name') == name)

    ... to:

     if(tags[i].getAttribute('name') == name || tags[i].className.indexOf(name)==0)
     
  2. Down a few lines at line 627, insert another FindByTags call for 'code':</p>
      FindTagsByName(elements, name, 'code'); // Add this line
      FindTagsByName(elements, name, 'pre');
      FindTagsByName(elements, name, 'textarea');
  3. Further down, at line ~ 657 insert this right before options = options.split(':');:</p>
      options = options.replace(new RegExp("^"+name+"\s"), ''); // Turn 'code ruby:option1:option2' into 'ruby:option1:option2'

This should be backward-compatible with the existing pre and textarea methods.

And here's the diff of the above steps:

$ diff shCore.js.orig shCore.js

618c618
<                       if(tags[i].getAttribute('name') == name)
---
>                       if(tags[i].getAttribute('name') == name || tags[i].className.indexOf(name)==0)
627a628
>       FindTagsByName(elements, name, 'code');
657a659
>               options = options.replace(new RegExp("^"+name+"\s"), ''); // Turn 'dp-hilite ruby:option1:option2' into 'ruby:option1:option2'

Thanksgiving: Thanks for Radio Lab

8C3343F4-4489-48A2-92AD-AD00CCE923E4.jpg

While getting fat on turkey and mashed potatoes in Minnesota over the holiday, I discovered a great little radio show called Radio Lab, which is also published as a podcast. I had originally heard a clip of their show on This American Life a couple months ago, but despite Ira Glass's ringing endorsement, I hadn't sought out a listen until the night before flying. Boy, am I glad I did.

What's Radio Lab? Think Car Talk (but more production and no phone calls) meets Bill Nye the Science Guy. What I like about the show is that the topics are science-focused, and the hosts (Jad and Robert) banter and discuss things in a lively way. It's not boring; rather, it's engaging and the pace moves swiftly. The production seems almost built for the attention-deficit world we live in: some key phrases are repeated for emphasis, there is some narration on top of recorded stories to provide clarification, and there is discussion in a conversational tone. It all adds up to a really fun and enlightening experience.

I managed to listen to eleven podcasts on four flights and while drifting off to sleep each night over the last few days. If you're looking for a place to start, my favorite episodes so far have been Morality, Musical Language, Memory and Forgetting, and Who Am I?. It's a fairly deep-thinking, hour-long show, so I'm not sure how it would hold up under the demands of driving (I often find my mind wanders as I drive regardless of what I listen to). But if you can manage to concentrate fully and follow along, you'll be rewarded with some truly mind-enhancing moments.

And, if nothing else, you'll be able to spout off random, newly-learned fun-facts at parties. Did you know that some animals sleep half of their brain at a time?

Indispensable Rails Plugins

Obie Fernandez's post about his most favored plugins prompted me to consider how much I use not only the Rails core framework, but also some excellent plugins that let me focus more on solving domain problems rather than technical ones. Below, the plugins I find indispensable in my work on Dibs.net.

Dibs.net relies on these great plugins, some hacked slightly to suit my needs. These plugins have saved me months of work and enabled far greater functionality to be built-in from the start.

  • acts_as_slugable - Easy way to create permalinks from a title, without numeric IDs.
  • acts_as_solr - Running Solr behind your ActiveRecord models gives you excellent "Google-like" searching. IMO, better than acts_as_ferret for many uses where you can handle the added architectural complexity (particularly, running a Java-based Solr server).
  • acts_as_state_machine - This is probably my favorite plugin. So good, I think it should be a part of ActiveRecord itself. If you have objects in "draft," "published" or "active" states you might want to check this out.
  • attachment_fu - If file uploads give you a severe headache, attachment_fu is like Advil.
  • betternestedset - Gives you higher performance selection methods like all_children. The API takes some getting used to and isn't suitable to replace existing solutions in all cases.
  • debug_view_helper - Adds a button to your page that pops up debugging info about the request, cookies, session and instance variables.
  • exception_notification - Sends a ticket to my support queue whenever a user experiences an error.
  • geokit - Andre Lewis and Bill Eisenhauer have done some amazing stuff here. Adds geo-intelligence (street address and IP geocoding) to ActiveRecords; also adds query-by-proximity, though I don't use that piece.
  • paginating_find - Another one that should be embedded into core ActiveRecord to replace the existing pagination that everyone recommends never to use.

Do you use any great plugins you think people would love?

Feedback and Article Ideas

Want to see a topic explored here? Send Me a Message.