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':
    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(':');:
    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.

Restoring Rails session data when cookies aren't available

C is for Cookie
If you've ever needed to implement user-friendly upload, you know intimately what a pain it is to get right. The web just isn't built for uploading files from a browser. I mean, it kinda works, but even then only with a dozen or so limitations. Even the major photo and video sites have tried various solutions to make this easier for users. So when I built Dibs.net, I decided rather quickly to abandon all hope of getting it working flawlessly with plain ol' Javascript and HTML, and instead looked into using a fairly nonintrusive Flash uploader component. (Without Flash installed, it just falls back to a simple HTML-based file-upload form.)

That's not to say it was perfectly simple to get working with Rails. Because Dibs.net accepts uploads only from logged-in users, I ran into two limitations that would not allow me to use this solution:

  • Flash doesn't send the cookies from the browser (at least it doesn't in Firefox; it might in IE)
  • Rails doesn't support non-cookie sessions

Because Flash doesn't send the session cookie, Rails thinks the request is coming from a new, logged-out user and creates a new session for it. Adding a cookies feature to Flash was well out of my hands since I don't work for Adobe, so I looked into a way to restore the session from a session key passed as a URL parameter. After some experimentation, I found a solution that works great.

Assumptions

I use a modified version of the acts_as_authenticated plugin. Upon authentication, the plugin sets the :user session key to the authenticated user's id. You'll need to adapt for your own configuration.

Example Rails Code

In RAILS_ROOT/app/controllers/show_my_ip_controller.rb:

class ImagesController < ApplicationController
	session :off, :only => :create
	prepend_before_filter :restore_session_user_from_param, :only => :create
	requires_login :except => :index

	def create
	  # Handle the file upload here
	end

	private
	def restore_session_user_from_param
	    data = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager].session_class.find_session( params[:_session_id] ).data
	    sess_obj = Marshal.load( Base64.decode64( data ) )
	    @current_user = User.find( sess_obj[:user] )
  	rescue
    	authorization_required
  	end
end

Then we include the session id as a parameter in the form's action URL in the view:

<form action="<%= images_path(:_session_id => session.session_id) %>" method="post" id="photoupload" enctype="multipart/form-data">

How it works

Under normal circumstances the acts_as_authenticated plugin sets the @current_user instance variable to the current logged-in user at the start of each request. Since we have no session data when a Flash app hits the controller, there's effectively no current_user. Our goal is to get current_user working, so we:

  • turn sessions off for the relevant action; otherwise Rails will create useless sessions any time Flash hits that action
  • prepend a before filter to set the @current_user instance variable
  • require login for most of the actions, including create

In the before_filter, we grab the session data from whatever session store we're using, decode and unmarshall it, and set the @current_user instance variable to the User we find with the id we get from the session hash.

Simple? Not really. But it works!

Further Reading

I couldn't find much documentation on any of this beyond stomping through the Rails code & Ruby's CGI Standard Library docs.

Update: A Word of Caution

I forgot to mention when I published this earlier that there's a reason parameterized sessions is discouraged: browsers will send the entire current link, including the session id, in referer headers to offsite hosts. This doesn't affect Dibs.net's Flash upload, but in other scenarios use the above with caution, or with SSL.

Feedback and Article Ideas

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

De-tangling attr_reader, attr_writer and attr_accessor from attr_protected & attr_accessible (Part 1)

Car crashWhat a mess. You have undoubtedly run across these methods sprinkled throughout the Ruby and Rails world. If you've been working with Rails for even a short time, you've probably read a little about security and attr_accessible. But do you really understand what each of them do and when to use them?
I'm not a fan of whoever made these methods so closely named, especially because they serve very different purposes: two are specific to Rails (or more accurately, ActiveRecord), while the other three are Ruby core methods. When I have a need for any of them, I still have to really think about which one I actually want to use. Often I still have to glance at the rdocs to be reassured my choice is the right one. So let's dive in and figure out what the heck these are supposed to do, and when to use them.

Forget Rails, Let's Talk Ruby Attributes

I've noticed a fair number of folks try to write Rails apps without much knowledge of Ruby. That's a noble thing to try, but to be a truly effective Rails developer, you'll need to be intimately knowledgeable about the Ruby language.

For this reason, and to get our feet wet, we'll ignore the Rails methods for a moment and focus on the Ruby, since that's the source of three important attr_* methods: attr_accessor, attr_reader, and attr_writer. Put these three methods together in a bucket inside your head, as they are closely related.

Say we have a simple little Ruby class (remember we're ignoring Rails at the moment, so this is just a basic Ruby Object class, not an ActiveRecord model).

In address.rb:

class Address

   def initialize(line1, line2, city, state, zip)
     @line1 = line1
     @line2 = line2
     @city = city
     @state = state
     @zip = zip
   end

   def to_s
     "#{@line1}\n#{@line2}\n#{@city}, #{@state} #{@zip}"
   end

end

Our class doesn't do much yet, but we can create an Address instance and print it as a string using the to_s method:

irb(main):001:0> require 'address'
=> true
irb(main):002:0> address = Address.new("Centropy", "PO Box 1236", "Santa Clara", "CA", "95052")
=> #<Address:0x85908 @line2="PO Box 1236" @zip="95052" @line1="Centropy" @state="CA" @city="Santa Clara">
irb(main):003:0> puts address.to_s
Centropy
PO Box 1236
Santa Clara, CA 95052

What if we want to be able to get each of those attributes separately from the full address, say to print out only the zip code? Trying to print the zip attribute with our current code won't work:

irb(main):004:0> puts address.zip
NoMethodError: undefined method `zip' for #<Address:0x85908>
        from (irb):4
        from :0

In order to call address.zip, we need an attribute getter to make the @zip instance variable visible outside the instance. If you're from the world of Java you're probably intimately familiar with these so-called "getter" methods. So, you'd go and write your "getter" method for @zip:

class Address

   def initialize(line1, line2, city, state, zip)
     @line1 = line1
     @line2 = line2
     @city = city
     @state = state
     @zip = zip
   end

   def zip
     @zip
   end

   def to_s
     "#{@line1}\n#{@line2}\n#{@city}, #{@state} #{@zip}"
   end

end

Okay, only four more methods to go and you can head over to the water cooler for a well-deserved break! (You get paid by the line, after all!) It feels kinda silly, though, since all these methods follow an identical pattern. Since this is such a common pattern, Ruby gives us a little shortcut: anytime you would add an attribute getter as above, you can and should use the handy attr_reader method instead, which effectively creates the attribute getter methods for us behind the scenes, without us having to explicitly define each method in detail. Our new class looks like this:

class Address

   def initialize(line1, line2, city, state, zip)
     @line1 = line1
     @line2 = line2
     @city = city
     @state = state
     @zip = zip
   end

   # Note we can give attr_reader multiple attributes,
   # and they are specified as :symbols
   attr_reader :line1, :line2, :city, :state, :zip

   def to_s
     "#{@line1}\n#{@line2}\n#{@city}, #{@state} #{@zip}"
   end

end

Now we can grab all of our attributes apart from the full address:

irb(main):001:0> require 'address'
=> true
irb(main):002:0> address = Address.new("Centropy", "PO Box 1236", "Santa Clara", "CA", "95052")
=> #<Address:0x854bc @line2="PO Box 1236" @zip="95052" @line1="Centropy" @state="CA" @city="Santa Clara">
irb(main):003:0> puts address.zip
95052

But what about setting the zip code?

irb(main):004:0> address.zip='95050'
NoMethodError: undefined method `zip=' for #<Address:0x854bc>
        from (irb):4
        from :0

Nope, doesn't work. Maybe that's what you want: if an attribute should not be settable after the object is instantiated, you wouldn't want the ability to set the value of an attribute like this. (If you ever hear the term "immutable", that is what we're talking about here: Address instances are currently immutable because they can not be "mutated," or changed, after instantiation.) But if you do want to set your attribute values, you'll need to create a way for code outside the instance to change the value of that somewhat elusive @zip instance variable. Again, if you are coming from Java programming, you'd attempt to write a "setter" method here:

class Address

   def initialize(line1, line2, city, state, zip)
     @line1 = line1
     @line2 = line2
     @city = city
     @state = state
     @zip = zip
   end

   # Note we can give attr_reader multiple attributes,
   # and they are specified as :symbols
   attr_reader :line1, :line2, :city, :state, :zip

   # Allow setting the instance attribute to a new value
   def zip=(new_zip)
     @zip = new_zip
   end

   def to_s
     "#{@line1}\n#{@line2}\n#{@city}, #{@state} #{@zip}"
   end

end

But, if you're starting to hear harp music right now, you might be thinking that Ruby might give us an equally simple, declarative way to make attribute setter methods as it did for getters. Lo and behold, attr_writer comes to the rescue:

class Address

   def initialize(line1, line2, city, state, zip)
     @line1 = line1
     @line2 = line2
     @city = city
     @state = state
     @zip = zip
   end

   # Note we can give attr_reader multiple attributes,
   # and they are specified as :symbols
   attr_reader :line1, :line2, :city, :state, :zip

   # Those attributes are writable, too
   attr_writer :line1, :line2, :city, :state, :zip

   def to_s
     "#{@line1}\n#{@line2}\n#{@city}, #{@state} #{@zip}"
   end

end

And now we can set or get any attribute value:

irb(main):001:0> require 'address'
=> true
irb(main):002:0> address = Address.new("Centropy", "PO Box 1236", "Santa Clara", "CA", "95052")
=> #<Address:0x8519c @line2="PO Box 1236" @zip="95052" @line1="Centropy" @state="CA" @city="Santa Clara">
irb(main):003:0> address.zip='95050'
=> "95050"
irb(main):004:0> puts address.zip
95050

Now, you might think we're in good shape: we've consolidated what would have been ten different method definitions into a two lines of declarative, readable code. Not so fast! There's a bit of redundancy in those two lines, isn't there? Specifying all your fields in two places is clearly less maintainable than if we could tell Ruby "we want getters and setters for this list of attributes." It might not be a big deal for our little Address class, but when you have hundreds or thousands of classes that you haven't looked at in a year, every extraneous line matters.

Again, this is such a common scenario in programming that Ruby offers the third attribute method, attr_accessor, which does exactly what two separate calls to attr_reader and attr_writer would do.

So, let's get rid of that repetition now:

class Address

   def initialize(line1, line2, city, state, zip)
     @line1 = line1
     @line2 = line2
     @city = city
     @state = state
     @zip = zip
   end

   # attr_accessor can take multiple attributes like the others,
   # and they are specified as :symbols
   attr_accessor :line1, :line2, :city, :state, :zip

   def to_s
     "#{@line1}\n#{@line2}\n#{@city}, #{@state} #{@zip}"
   end

end

And this works exactly like the previous example:

irb(main):001:0> require 'address'
=> true
irb(main):002:0> address = Address.new("Centropy", "PO Box 1236", "Santa Clara", "CA", "95052")
=> #<Address:0x8519c @line2="PO Box 1236" @zip="95052" @line1="Centropy" @state="CA" @city="Santa Clara">
irb(main):003:0> address.zip='95050'
=> "95050"
irb(main):004:0> puts address.zip
95050

Hopefully this clarifies what these three methods are for. Next time, we'll explore the similarly-named-but-entirely-different-purposed attr_protected and attr_accessible methods brought to us by Rails' ActiveRecord models.

Further Reading

The attr_accessor, attr_reader, and attr_writer methods are fairly well documented in the Ruby core rdocs.

Feedback and Article Ideas

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

How to obtain the IP address of the current user

Some house address

Web applications can receive requests directly, via a CGI process, through proxy servers, relayed from front-end web servers, and so on. This can complicate how you might find out where the request originated if you, for example, wanted to limit an online poll to one vote per IP address. Luckily, Rails consolidates most of the ways to get this info into a single convenience method on the request object for us.

The Convenience Method: #remote_ip

Without the request.remote_ip method, you'd have to look for specific headers that are used to carry this data in the HTTP request beyond the server where the actual client's connection was terminated.

Rails' request.remote_ip method is pretty smart: it looks for and parses the headers HTTP_CLIENT_IP, HTTP_X_FORWARDED_FOR and REMOTE_ADDR and parse the value which are commonly used for this purpose.

Example Rails Code

In RAILS_ROOT/app/controllers/show_my_ip_controller.rb:

class ShowMyIpController > ApplicationController

  def index
    @client_ip = request.remote_ip
  end

end

In RAILS_ROOT/app/views/show_my_ip/index.html.erb:

Your IP address is <%= @client_ip >

Further Reading

The request.remote_ip method is documented in the Rails Framework rdocs.

Feedback and Article Ideas

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

Microsoft adCenter vs Google Adwords (Round 1)

At the risk of being called a keyword Nazi and having to endure accusations that I care just a little too much about this stuff: Microsoft just managed surpass my experience with Yahoo.

As I mentioned, I've been advertising my online community classifieds site, Dibs.net, on Google for almost a month. Last night I decided to try out Microsoft's advertising network for my community classifieds site. I copied some of my better performing ads from my Classifieds Ad Group on Google into my Classifieds Ad Group on Microsoft.

Here's representative ad from the bunch:
Ad on Microsoft

I was in business fairly quickly -- or so I thought. Microsoft has a similar "editorial guidelines" process to Yahoo's. But at Microsoft, the process must go something like this:

  1. Take break from huffing paint fumes and watching Scooby-Doo
  2. Flip coin
  3. Heads, you get to advertise with your keyword; tails, you don't

Microsoft rejected what I consider to the the most important keywords from my advertising, including "classifieds" and "community classifieds."

I've given them a chance to respond before I start calling them names here ;)

(PS - Google's success in advertising appears to be not without reason.)

Update: Microsoft took only a few hours to respond, and did so positively by reinstating the classifieds-related keywords. Good for them (and me). They blamed the problem on their automated editorial process, which raises the question: why would anyone automate such a thing? I think we can all agree computers are great at some things, but applying subjective and vaguely defined editorial guidelines isn't one of them. Either way, I take back my paint fumes and Scooby-Doo comment above, since it appears they aren't nuts after all.

Yahoo Marketing vs Google Adwords (Round 1)

Yahoo Ad
I've been advertising Dibs.net on both the Google and Yahoo ad networks for three weeks. I'll have more to say later on, but I did want to share some notes about Yahoo's "editorial process" regarding what search terms they let you advertise against. This process is allegedly for quality purposes, so that more relevant advertising is displayed to searchers.

Google has taken a more free-market approach to their editorializing of search terms you can advertise against. I'm sure they have a quality-control staff to keep ads clean, but they tend to let you spend your money however you want. I'm no Google fan, but I think Google's approach is far superior, mainly because the market actually does self-correct mispositioned advertising: as people find their advertising for certain keywords isn't working, they pare back the selection of keywords to be more laser-focused. I did this very thing; it works.

Yahoo, on the other hand, voluntarily protects certain company names (though who knows which ones) and is awfully serious about the relevancy of keywords you choose. But it's all so inconsistent and confusing that you never know what they will allow. For example, they apparently allow some misspellings of brand names, but not others. And today I got notice they decided a term wasn't relevant enough to Dibs.net -- which surprised me, since I often describe Dibs.net using the exact term they rejected.

So, I just fired off an email to Yahoo support to see if they can make this situation clearer than the Mississippi mud they've given me till now.

Can you give me more detail as to why you believe the keyword "flea markets" isn't applicable to my company, which is itself an online flea market (dibs.net)?

Removing the keyword from my campaign strikes me as an overly aggressive editorial decision, especially after conducting this search on Yahoo and noting NOT A SINGLE AD relates even remotely to flea markets. In fact, mine is the MOST RELEVANT of any company there. Of the most absurd listings you apparently allow, BizRate, Shopping.com, PalmersUniforms.com and Restaurant.com are advertised on the "flea markets" keyword.

Yes, Restaurant.com advertises under "flea markets" but dibs.net cannot? Come on, Yahoo! I'm trying to like your ad service. I really am. But this sort of random, haphazard enforcement of "editorial rules" leaves me plain confounded.

I have paused my advertising until I receive a response explaining how Yahoo can possibly hope to apply such picky rules consistently.

Thanks,
Kevin Hunt

We'll see what they have to say about Restaurant.com's flea market. Mmmmm, makes you hungry just thinking about it, doesn't it?

Update (8/23): Yahoo responded this morning. They explained that because Dibs.net's content is not about flea markets, the keyword is not allowed. Ok, fair enough, if those are the actual rules -- but they're not. That would be stupid. I responded and asked if Amazon would be allowed to advertise with the keywork "book stores," since they have no information about actual book stores. Also, Yahoo's response did not address how a site about restaurants can use the keyword they are disallowing for me. I'm still confused.

Come on, Yahoo! I really want to like you. Granted, this isn't such a big deal in the grand scheme of things; but if I'm to commit an ad budget to you, you'd better clarify your rules and apply them consistently.