Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
= FriendlyId
FriendlyId is the "Swiss Army bulldozer" of slugging and permalink plugins for Ruby on Rails. It allows you to create pretty URL's and work with human-friendly strings as if they were numeric ids for ActiveRecord models.
Using FriendlyId, it's easy to make your application use URL's like:
http://example.com/states/washington
instead of:
http://example.com/states/4323454
Want to find out more? Read on. The {most recent version of the FriendlyId RDocs}[http://friendly-id.rubyforge.org] can always be found on Rubyforge[http://www.rubyforge.org].
=== Why?
=== But...
FriendlyId tries to offer you the all the advantages, and avoid or soften the potential impact of the disadvantages.
== Typical Uses
=== User names ("non-slugged" models)
Usually users have unique user names stored in a column with a unique constraint or index. In this case, all you need to do is add this to your model:
has_friendly_id :login
and you can then write code like this:
@member = Member.find("joe") # the old Member.find(1) still works, too. @member.to_param # returns "joe" redirect_to @member # The URL would be /members/joe
=== Blog posts ("slugged" models)
Blog posts generally have titles which are distinctive but not necessarily unique. In this and similar cases, FriendlyId provides a Slug model separate from your Post model. The Slug model handles duplicate friendly_ids, as well as versioning.
Your model code would look something like this:
has_friendly_id :title, :use_slug => true
and you can then write code like this:
@post = Post.find("new-version-released") # Post.find(1) still works, too @post.to_param # returns "new-version-released" redirect_to @post # The URL would be /posts/new-version-released
Now in your controllers, if you want to prevent people from accessing your models by numeric id, you can detect whether they were found by the friendly_id:
@post = Post.find(params[:id]) raise "some error" if !@post.found_using_friendly_id?
or, you can 301 redirect if the model was found by the numeric id if you don't care about numeric access, but want the SEO value of the friendly_id:
@post = Post.find(params[:id]) redirect_to @post, :status => 301 if @post.has_better_id?
The "has_better_id?" method returns true if the model was found with the numeric id, or with an outdated slug.
== Extra Features
=== Slug Versioning
FriendlyId will record changes to slugs so that you can tell when the model is found with an older slug, or by the numeric id. This can be useful if you want to do a 301 redirect to your updated URL.
class PostsController < ApplicationController
before_filter ensure_current_post_url, :only => :show
...
def ensure_current_post_url
redirect_to @post, :status => :moved_permanently if @post.has_better_id?
end
end
This is particularly useful when implementing FrindlyId on an existing website that already has many URL's with the old numeric id listed on search engines. When the search engine spiders crawl your site, they will eventually pick up the new, more SEO-friendly URL's.
=== Non-unique Slug Names
FriendlyId will append a arbitrary number to the end of the id to keep it unique if necessary:
/posts/new-version-released /posts/new-version-released--2 /posts/new-version-released--3 ... etc.
Note that the number is preceeded by two dashes to distinguish it from the rest of the slug. This is important to enable having slugs like:
/cars/peugeot-206 /cars/peugeot-206--2
=== Reserved Names
You can mark off some strings as reserved so that, for example, you don't end up with this problem:
/users/joe-schmoe # A user chose "joe schmoe" as his user name - no worries. /users/new # A user chose "new" as his user name, and now no one can sign up.
Here's how to do it:
class Restaurant < ActiveRecord::Base belongs_to :city has_friendly_id :name, :use_slug => true, :reserved => ["my", "values"] end
As of FriendlyId version 2.0.2, "new" and "index" are reseved by default. When you attempt to store a reserved value, FriendlyId raises a FriendlyId::SlugGenerationError.
=== Scoped Slugs
FriendlyId can generate unique slugs within a given scope. For example:
class Restaurant < ActiveRecord::Base belongs_to :city has_friendly_id :name, :use_slug => true, :scope => :city end
class City < ActiveRecord::Base has_many :restaurants has_friendly_id :name, :use_slug => true end
http://example.org/cities/seattle/restaurants/joes-diner http://example.org/cities/chicago/restaurants/joes-diner
Restaurant.find("joes-diner", :scope => "seattle") # returns 1 record Restaurant.find("joes-diner", :scope => "chicago") # returns 1 record Restaurant.find("joes-diner") # returns both records
The value for the :scope key in your model can be a custom method you define,
or the name of a relation. If it's the name of a relation, then the scope's
text value will be the result of calling to_param
on the related
model record. In the example above, the city model also uses FriendlyId and so
its to_param
method returns its friendly_id: chicago or seattle.
This feature is new in FriendlyId 2 and should be considered of experimental quality. Please don't use this for code that needs to run on the Space Shuttle.
=== Text Normalization
FriendlyId's slugging can strip diacritics from Western European characters, so that you can have ASCII-only URL's; for example, conveting "ñøîéçü" to "noiecu."
has_friendly_id :title, :use_slug => true, :strip_diacritics => true
If you are not using slugs, you'll have to do this manually for whatever value you're using as the friendly_id.
=== Diacritic-sensitive normalization
FriendlyId can also normalize slug text while preserving accented characters, if you prefer to leave them in your URL's:
has_friendly_id :title, :use_slug => true ... @post = Post.create(:title => "¡Feliz Año!") @post.friendly_id # "feliz-año"
=== Unicode URL's
FriendlyId can generate slugs in any language that can be written with Unicode. It does its best to strip away punctuation regardless of the language being used. Since the authors only speak English, Spanish, Portuguese and German, this has not been extensively tested with anything like Chinese, Russian, Greek, etc, but it "should work." If you're a speaker of a language that uses a non-Roman writing system, your feedback would be most welcome.
has_friendly_id :title, :use_slug => true ... @post = Post.create(:title => "友好编号在中国") @post.friendly_id # "友好编号在中国" @post2 = Post.create(:title => "友好编号在中国") @post2.friendly_id # "友好编号在中国--2"
=== Custom Slug Generation
While FriendlyId's slug generation options work for most people, you may need something else. As of version 2.0.4 you can pass in your own custom slug generation block:
class Post < ActiveRecord::Base has_friendly_id :title, :use_slug => true do |text| MySlugGeneratorClass::my_slug_method(text) end end
FriendlyId will still respect your settings for max length and reserved words, but will use your block rather than the baked-in methods to normalize the friendly_id text.
== Getting it
FriendlyId is best installed as a Ruby Gem:
gem install friendly_id
Alternatively, you can install it as a Rails plugin, though this is discouraged:
./script/plugin install git://github.com/norman/friendly_id.git
== Setting it up
The current release works with Rails 2.2 and above, and is compatible with Ruby 1.8 and 1.9. If you need support for Rails 2.0 - 2.1, you need to install an older version of FriendlyId. Here's how to set it up.
sudo gem install friendly_id cd my_app script/generate friendly_id rake db:migrate
config.gem "friendly_id"
require 'friendly_id'
class Post < ActiveRecord::Base has_friendly_id :title, :use_slug => true end
rake friendly_id:make_slugs MODEL=MyModelName
If you eventually want to expire old slugs every so often, or perhaps every day via cron, you can do:
rake friendly_id:remove_old_slugs
The default is to remove dead slugs older than 45 days, but is configurable:
rake friendly_id:remove_old_slugs MODEL=MyModelName DAYS=60
== Installing an older version
If you are still on Rails 2.1 or lower, please install version 2.0.4:
gem install friendly_id --version 2.0.4
Note that this version depends on the Unicode gem, which doesn't compile on Windows and is incompatible with Ruby 1.9. It also can't be installed on Heroku. If these are problems, you'll need to update your application to Rails 2.2 or higher and use the current release of FriendlyId.
== Upgrading from an older version
If you installed an older version of FriendlyId and want to upgrade to 2.0.x, follow these steps:
==== Install the friendly_id Gem:
sudo gem install friendly_id
==== Add FriendlyId to environment.rb:
===== For Rails 2.1 and higher:
config.gem "friendly_id"
===== For Rails 2.0:
Add this to the bottom of environment.rb:
require 'friendly_id'
==== Remove the older version of FriendlyId:
git rm -rf vendor/plugins/friendly_id svn delete vendor/plugins/friendly_id
==== Generate the upgrade migration and run it
./script/generate friendly_id_20_upgrade rake db:migrate
== Hacking FriendlyId:
FriendlyId is {hosted on Github}[git://github.com/norman/friendly_id.git], and we love pull requests. :-)
== Bugs:
Please report them on Lighthouse[http://randomba.lighthouseapp.com/projects/14675-friendly_id].
== Credits:
FriendlyId was created by {Norman Clarke}[mailto:norman@randomba.org], {Adrian Mugnolo}[mailto:adrian@randomba.org], and {Emilio Tagua}[mailto:miloops@gmail.com].
Copyright (c) 2008 Norman Clarke, Adrian Mugnolo and Emilio Tagua, released under the MIT license.
FAQs
Unknown package
We found that nwp-friendly_id demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.