module FriendlyId::Scoped

@guide begin

## Unique Slugs by Scope

The {FriendlyId::Scoped} module allows FriendlyId to generate unique slugs within a scope.

This allows, for example, two restaurants in different cities to have the slug `joes-diner`:

class Restaurant < ActiveRecord::Base
  extend FriendlyId
  belongs_to :city
  friendly_id :name, :use => :scoped, :scope => :city
end

class City < ActiveRecord::Base
  extend FriendlyId
  has_many :restaurants
  friendly_id :name, :use => :slugged
end

City.friendly.find("seattle").restaurants.friendly.find("joes-diner")
City.friendly.find("chicago").restaurants.friendly.find("joes-diner")

Without :scoped in this case, one of the restaurants would have the slug `joes-diner` and the other would have `joes-diner-f9f3789a-daec-4156-af1d-fab81aa16ee5`.

The value for the `:scope` option can be the name of a `belongs_to` relation, or a column.

Additionally, the `:scope` option can receive an array of scope values:

class Cuisine < ActiveRecord::Base
  extend FriendlyId
  has_many :restaurants
  friendly_id :name, :use => :slugged
end

class City < ActiveRecord::Base
  extend FriendlyId
  has_many :restaurants
  friendly_id :name, :use => :slugged
end

class Restaurant < ActiveRecord::Base
  extend FriendlyId
  belongs_to :city
  friendly_id :name, :use => :scoped, :scope => [:city, :cuisine]
end

All supplied values will be used to determine scope.

### Finding Records by Friendly ID

If you are using scopes your friendly ids may not be unique, so a simple find like:

Restaurant.friendly.find("joes-diner")

may return the wrong record. In these cases it's best to query through the relation:

@city.restaurants.friendly.find("joes-diner")

Alternatively, you could pass the scope value as a query parameter:

Restaurant.where(:city_id => @city.id).friendly.find("joes-diner")

### Finding All Records That Match a Scoped ID

Query the slug column directly:

Restaurant.where(:slug => "joes-diner")

### Routes for Scoped Models

Recall that FriendlyId is a database-centric library, and does not set up any routes for scoped models. You must do this yourself in your application. Here's an example of one way to set this up:

# in routes.rb
resources :cities do
  resources :restaurants
end

# in views
<%= link_to 'Show', [@city, @restaurant] %>

# in controllers
@city = City.friendly.find(params[:city_id])
@restaurant = @city.restaurants.friendly.find(params[:id])

# URLs:
http://example.org/cities/seattle/restaurants/joes-diner
http://example.org/cities/chicago/restaurants/joes-diner

@guide end

Public Class Methods

included(model_class) click to toggle source

Sets up behavior and configuration options for FriendlyId's scoped slugs feature.

# File lib/friendly_id/scoped.rb, line 112
def self.included(model_class)
  model_class.class_eval do
    friendly_id_config.class.send :include, Configuration
  end
end
setup(model_class) click to toggle source

FriendlyId::Config.use will invoke this method when present, to allow loading dependent modules prior to overriding them when necessary.

# File lib/friendly_id/scoped.rb, line 106
def self.setup(model_class)
  model_class.friendly_id_config.use :slugged
end

Public Instance Methods

serialized_scope() click to toggle source
# File lib/friendly_id/scoped.rb, line 118
def serialized_scope
  friendly_id_config.scope_columns.sort.map { |column| "#{column}:#{send(column)}" }.join(",")
end
should_generate_new_friendly_id?() click to toggle source
Calls superclass method
# File lib/friendly_id/scoped.rb, line 140
def should_generate_new_friendly_id?
  (changed & friendly_id_config.scope_columns).any? || super
end

Private Instance Methods

scope_for_slug_generator() click to toggle source
Calls superclass method
# File lib/friendly_id/scoped.rb, line 122
def scope_for_slug_generator
  if friendly_id_config.uses?(:History)
    return super
  end
  relation = self.class.base_class.unscoped.friendly
  friendly_id_config.scope_columns.each do |column|
    relation = relation.where(column => send(column))
  end
  primary_key_name = self.class.primary_key
  relation.where(self.class.arel_table[primary_key_name].not_eq(send(primary_key_name)))
end
slug_generator() click to toggle source
# File lib/friendly_id/scoped.rb, line 135
def slug_generator
  friendly_id_config.slug_generator_class.new(scope_for_slug_generator, friendly_id_config)
end