Friendly_id is a plugin for Ruby on Rails which allows you to work with human-friendly strings as well as numeric ids for ActiveRecords, so that you can have urls like “/members/joe” rather than “/members/123” or “/members/123-joe”.
This provides your application with better search engine optimization (SEO), better data security, and more human-friendly URL’s.It can optionally keep track of modified ids, so that you can do 301 redirects the current URL’s of updated resources.
Read more in the RDoc, or download it directly from its subversion repository.
All patches and recommendations posted in the comments have now been applied to svn. Thanks again to suntzu, W. Andrew Loe III, and Emilio Tagua for their contributions.
I’m having trouble when running the ‘rake riendly_id:make_slugs’ task. It fails on line 250 of friendly_id.rb (”I give up, damnit!”) when trying to make a slug for an item named “Post”. I’ve traced the issue to another item named “Post 2/4″, which gets slugged to “post-2-4″, then trips up the generate_friendly_id_with_extension method. What’s the best way to handle this type of case?
Thanks!
Thanks for the bug report. I’ve committed a fix to svn that should resolve this issue for you. Let me know how it works out.
Thanks, Norman, the new code looks to be working like a charm!
Suggestions for instruction set:
Was reading http://agilewebdevelopment.com/plugins/friendly_id and got quite confused at one point when under Scenario 2 (for sluggables) it says:
has_friendly_id :posts, :use_slug => true
This should say something more like:
has_friendly_id :post_title, :use_slug => true
Also -
Ran ‘rake friendly_id:make_slugs MODEL=MyModelName’ on one of my models and for some reason it came back telling me I had validation errors based on the validations I was using in my model:
Validation failed: Tag list Can only be letters and numbers
Validation failed: Advice wording is too short (minimum is 10 characters)
Not sure why that happened since it shouldn’t be creating any new records outside of the slugs table, right?
Sorry to post yet again -
I got around that validation problem on the last part by just removing the validations from my model, running the rake task and then putting them back. Everything seems to work fine.
The rdoc isn’t in sync with the source… noticed during ‘Setup’ instructions in readme. The rdoc points to a tags/1_0 but that doesn’t exist. The readme in truck points to trunk.
Just noticed the discrepancy between these two statements in the README
“Also, any person can still simply increment the id part of the url, and gain a level of access to your data that you may not wish them to have.”
“# Post.find(1) still works, too”
So one can still hit http://www.example.org/profiles/12 and have it call up Joe since a Profile.find(12) was sent through… What do you recommend to prevent people calling up records by id?
@Chris Nolan.ca
You can always wrap the find method and use the wrapped version in your controllers.
def find_friendly(id)
if( id.to_i == 0 ) # 0 is default and not a valid id
find( id ) # or throw an error
else
find_by_id( id ) || find( id )
end || raise (ActionController::RoutingError, “unkown resource: #{id}”)
FYI Model.exists? fails when using the slug.
I’m running into trouble updating a record which is the first of 19 identically-named objects, i.e. the latest slug is “name-19″. The call to Slug.count_matches in generate_friendly_id_with_extension returns 18 (because the id of the record being updated is excluded), this gets incremented to 19, then a SlugGenerationError gets thrown at friendly_id.rb:251 due to the collision with the existing name-19 slug. I haven’t had time yet to trace exactly what logic is failing, but maybe the regexp in Slug.count_matches should capture the highest extension number rather than doing a simple count and increment? Or maybe the increment logic just needs a tweak?
Thanks!
I’ve incoroportated the documentation changes suggested above. Thanks!
@Jeff, we are working on your problem. Sorry, I was on vacation for a while, got back and have been playing catch-up at work. We’ll have a solution posted soon.
when I use friendly id without :use_slug => true on a model, it doesn’t sanitize the friendly id. In order to fix this and make it behave properly I just did,
Slug::normalize(send(friendly_id_options[:method].to_sym))
on line 104 of lib/friendly_id.rb
I’m not sure whether this is the best solution, but it’s the best five minute solution I could come up with.
Max, the non-sanitizing behavior when not using slugs is actually intentional, so as not to overwrite people’s data. Not using slugs is sort of a “roll your own” option; to be used if you already have your own way to sanitize strings and ensure their uniqueness.
I think the documentation is probably lacking, also perhaps I could make it an available option for people that do want to use it.
ah, gotcha. That makes sense. I wouldn’t mind having the option, but that’s good to know. I thought it was a little bit too much of a blatant oversight, considering that it’s tested pretty well.
So when I am my main User model with a unique username that I will be using with friendly_id, I don’t need to run the rake/migrate tasks? It won’t use the slogs table?
@Jeremy
Yes, exactly.
I am using friendly_id on my user model. I get an error when there is more than one user with the same name. When I have for example users: John, John-1, John-2 and than John tries to login(updates the user model), I get: “I give up, damnit!”.
John-2 can update his info with no problems.
If I understand correctly this problem is similar to the problem Jeff described.
How can I get this to work?
Miha, I’ve committed a fix to svn that should hopefully resolve this issue.
Is it possible to use this on more than one model at the same time, and particularly on nested routes?
I have categories and subcategories (url: /categories/:id/subcategories/:id/) and would like to change both :ids with slugs.
@Flavio
Yeah, it works fine. I am using the plugin in several applications with nested routes and url’s similar to what you described. Just let me know if you encounter any difficulties.
Eager loading does not work with the plugin (or am I missing something?).
If I’m following the code correctly, the actual object is obtained from the “belongs_to” association to the Slug model. Due to my limited knowledge of the ActiveRecord code, I’m wondering if there’s a relatively simple way to add back support for eager loading?
Actually I believe this applies to any additional options passed to the finder method (i.e. the options are ignored).
svn is down, will it come back or is it all git now?
Sorry about that, svn is now back online.
I’ll keep svn up until Rails 2.1 has been out for a few months. At that point I’ll stop supporting svn unless I hear from a lot of people who still need it.
If I base the friendly url on my title “first day in London” it gets turned into first+day+in+London instead of first-day-in-London. Is there a way to fix this?
Thanks in advance.
Is it possible to append anything to the title in the URL? In my case, I’d like to add the username to the post titles in the URL to make the URLs for unique and less chance of having to increment the title.
e.g. http:www.website.com/post/joe_This_is_a_title where “joe” is the user that created the post. Of course, I don’t want the username actually prepended to the post title in my posts table.
[...] friendly_id - Ruby on Rails plugin for human-readable id’s | randomba (tags: rubyonrails plugin url) [...]
Thanks for the plugin but I couldn’t get it to work with models that use STI.
@c.y.
Try using slugs.
But I’ll look into adding better support for “slugless” mode, you’re not the first person to ask about this.
@Keith Pitty
I’ll look into this. If you want, send me an email to norman@randomba.org and give me some more specifics on what fails; I’ll work on a fix.
@Jim
You can use any method to generate the friendly id, it doesn’t just have to be a column. So you coud do something like this:
has_friendly_id :title_and_username, :use_slug => true
def title_and_username
“#{user.name}-#{title}”
end
Hey,
We ran into the problem of Eager Loading not workin on on the project I work on (http://www.feistypiranha.com) so we decided to fix it up.
I posted the solution that we came up with on our blog (http://www.feistypiranha.com/getting-friendly_id-to-work/). I have only done limited testing with it, but in theory all parameters should work now… So you can do :conditions => “1 = 2″, and it will correctly fail (Not return any results. Since, as far as I know, 1 != 2)
The method we changed was “find_using_friendly_id” in the SluggableClassMethods module. (Line 127)
And the updated code goes as follows:
# Finds the record using only the friendly id. If it can’t be found
# using the friendly id, then it returns false. If you pass in any
# argument other than an instance of String, then it also returns false.
def find_using_friendly_id(*args)
return false unless args.first.kind_of?(String)
slug = Slug.find_by_name_and_sluggable_type(args.first, self.to_s)
begin
# Get the class and pass our options
object_class = Module.const_get(slug.sluggable_type)
options = args.extract_options!
return object_class.find(slug.sluggable_id, options)
rescue # The class was bad, use the old slug way
return false if !slug
return false if !slug.sluggable
slug.sluggable.send(:finder_slug=, slug)
slug.sluggable
end
end
Take care everyone, hope this helps!
Robert
Thanks a lot for that, Robert. I’ll try to get that integrated as soon as possible!