views:

367

answers:

1

I have a URL encoded resource such as:

http://myurl/users/Joe%20Bloggs/index.xml

This is for a RESTful webservice which uses user logins in the path. The problem is that the controller in rails doesn't seem to decode the %20. I get the following error:

ActionController::RoutingError (No route matches "/Joe%20Bloggs/index.xml" with {:method=>:post}):

What I'm actually trying to do is achieve one of 2 options (using authlogic as my registrations handler):

  • Either (preferably) allow users to register user names with spaces in them, and have these get routed correctly to my controller. Authlogic by default allows spaces & @/. characters - which is just fine with me if I can make it work...

  • Or I can restrict authlogic to dissallow the spaces. I know I can do this with:

    .merge_validates_format_of_login_field_options...

but I'm not entirely sure of the correct syntax to provide the new regex and return message on failure...

Any suggestions greatly appreciated!

+1  A: 

Generally it's a better idea to have a URL-safe "slug" field in your models for situations like this. For example:

class User < ActiveRecord::Base
  before_validation :assign_slug

  def to_param
    # Can't use alias_method on methods not already defined,
    # ActiveRecord creates accessors after DB is connected.
    self.slug
  end

  def unique_slug?
    return false if (self.slug.blank?)

    if (new_record?)
      return self.class.count(:conditions => [ 'slug=?', self.slug ]) == 0
    else
      return self.class.count(:conditions => [ 'slug=? AND id!=?', self.slug, self.id ]) == 0
    end
  end

  def assign_slug
    return if (slug.present?)

    base_slug = self.name.gsub(/\s+/, '-').gsub(/[^\w\-]/, '')
    self.slug = base_slug
    count = 1

    # Hunt for a unique slug starting with slug1 .. slugNNN

    while (!unique_slug?)
      self.slug = base_slug + count.to_s
      count += 1
    end
  end
end

This may solve the problem of having non-URL-friendly names. Rails is particularly ornery when it comes to having dots in the output of to_param.

tadman
OK, you just blew beyond my knowledge of Rails..! Can you describe what this code is doing? I guess it is trying to somehow convert the URL id I pass with the encoding in it into a login name it can search for?
cmaughan
After staring at this for a while, I can see that you are taking the username supplied and turning it into a 'friendly' string. But does this solve the problem of the arriving URL having bad encoding? I don't think it also does the conversion from the path I specified to the slug, right? I assume you intend for the slug to go back to the client in some way?
cmaughan
Although I just kind of adapted an existing bit of code to produce that example, the idea is that you create a URL-friendly "slug" for any resource that requires one, which in your case is presumably User. The assignment function is used to generate a sufficiently unique slug where there might be conflict ('Bob Jones' vs. 'Bob-Jones') and uses that as the result of to_param for the purposes of putting it into a URL. This dictates that you'll have to use User.find_by_slug!(params[:id]) from that point forward.
tadman
If you prepend the record's `id` to the slug, you don't have to go through the trouble of ensuring the slug is unique: `def to_param "#{id}-#{name.gsub(/\s+/, '-').gsub(/[^\w\-]/, '').slice(0..75)}" end`. That's all you need.
Jonathan Julian
This is a very easy way to get around having to create unique slugs, and works very reliably even when renaming resources, but for some applications it is not sufficient. The advantage of including the numerical ID first is that you don't have to do anything fancy to find the record as the .to_i call will extract it properly.
tadman