friendly_id-5.3.0/0000755000004100000410000000000013611022220014024 5ustar www-datawww-datafriendly_id-5.3.0/bench.rb0000644000004100000410000000356413611022220015440 0ustar www-datawww-datarequire File.expand_path("../test/helper", __FILE__) require "ffaker" N = 10000 def transaction ActiveRecord::Base.transaction { yield ; raise ActiveRecord::Rollback } end class Array def rand self[Kernel.rand(length)] end end Book = Class.new ActiveRecord::Base class Journalist < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :slugged end class Manual < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :history end class Restaurant < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :finders end BOOKS = [] JOURNALISTS = [] MANUALS = [] RESTAURANTS = [] 100.times do name = FFaker::Name.name BOOKS << (Book.create! :name => name).id JOURNALISTS << (Journalist.create! :name => name).friendly_id MANUALS << (Manual.create! :name => name).friendly_id RESTAURANTS << (Restaurant.create! :name => name).friendly_id end ActiveRecord::Base.connection.execute "UPDATE manuals SET slug = NULL" Benchmark.bmbm do |x| x.report 'find (without FriendlyId)' do N.times {Book.find BOOKS.rand} end x.report 'find (in-table slug)' do N.times {Journalist.friendly.find JOURNALISTS.rand} end x.report 'find (in-table slug; using finders module)' do N.times {Restaurant.find RESTAURANTS.rand} end x.report 'find (external slug)' do N.times {Manual.friendly.find MANUALS.rand} end x.report 'insert (without FriendlyId)' do N.times {transaction {Book.create :name => FFaker::Name.name}} end x.report 'insert (in-table-slug)' do N.times {transaction {Journalist.create :name => FFaker::Name.name}} end x.report 'insert (in-table-slug; using finders module)' do N.times {transaction {Restaurant.create :name => FFaker::Name.name}} end x.report 'insert (external slug)' do N.times {transaction {Manual.create :name => FFaker::Name.name}} end end friendly_id-5.3.0/.travis.yml0000644000004100000410000000443313611022220016141 0ustar www-datawww-datalanguage: ruby cache: bundler services: - postgresql - mysql dist: trusty addons: postgresql: "9.6" rvm: - 2.6.3 - 2.5.5 - 2.4.6 - 2.3.8 - 2.2.0 - 2.1.0 - jruby-9.1.13.0 env: global: - COVERALLS=true matrix: - DB=postgres - DB=mysql gemfile: - gemfiles/Gemfile.rails-4.0.rb - gemfiles/Gemfile.rails-4.1.rb - gemfiles/Gemfile.rails-4.2.rb - gemfiles/Gemfile.rails-5.0.rb - gemfiles/Gemfile.rails-5.1.rb - gemfiles/Gemfile.rails-5.2.rb - gemfiles/Gemfile.rails-6.0.rb before_install: - gem update bundler matrix: exclude: - rvm: 2.1.0 gemfile: gemfiles/Gemfile.rails-5.2.rb - rvm: 2.1.0 gemfile: gemfiles/Gemfile.rails-6.0.rb - rvm: 2.2.0 gemfile: gemfiles/Gemfile.rails-5.2.rb - rvm: 2.2.0 gemfile: gemfiles/Gemfile.rails-6.0.rb - rvm: jruby-9.1.13.0 gemfile: gemfiles/Gemfile.rails-5.2.rb - rvm: jruby-9.1.13.0 gemfile: gemfiles/Gemfile.rails-6.0.rb - rvm: 2.1.0 gemfile: gemfiles/Gemfile.rails-5.0.rb - rvm: 2.1.0 gemfile: gemfiles/Gemfile.rails-5.1.rb - rvm: 2.1.0 gemfile: gemfiles/Gemfile.rails-6.0.rb - rvm: 2.2.0 gemfile: gemfiles/Gemfile.rails-5.1.rb - rvm: 2.2.0 gemfile: gemfiles/Gemfile.rails-5.0.rb - rvm: 2.2.0 gemfile: gemfiles/Gemfile.rails-6.0.rb - rvm: 2.3.8 gemfile: gemfiles/Gemfile.rails-6.0.rb - rvm: 2.4.6 gemfile: gemfiles/Gemfile.rails-4.0.rb - rvm: 2.4.6 gemfile: gemfiles/Gemfile.rails-4.1.rb - rvm: 2.4.6 gemfile: gemfiles/Gemfile.rails-6.0.rb - rvm: 2.5.5 gemfile: gemfiles/Gemfile.rails-4.0.rb - rvm: 2.5.5 gemfile: gemfiles/Gemfile.rails-4.1.rb - rvm: 2.5.5 gemfile: gemfiles/Gemfile.rails-5.0.rb - rvm: 2.5.5 gemfile: gemfiles/Gemfile.rails-5.1.rb - rvm: 2.6.3 gemfile: gemfiles/Gemfile.rails-4.0.rb - rvm: 2.6.3 gemfile: gemfiles/Gemfile.rails-4.1.rb - rvm: 2.6.3 gemfile: gemfiles/Gemfile.rails-5.0.rb - rvm: 2.6.3 gemfile: gemfiles/Gemfile.rails-5.1.rb allow_failures: - rvm: jruby-9.1.13.0 gemfile: gemfiles/Gemfile.rails-5.1.rb - rvm: jruby-9.1.13.0 gemfile: gemfiles/Gemfile.rails-5.0.rb before_script: - bundle exec rake db:create db:up script: 'bundle exec rake test' friendly_id-5.3.0/test/0000755000004100000410000000000013611022220015003 5ustar www-datawww-datafriendly_id-5.3.0/test/helper.rb0000644000004100000410000000472413611022220016616 0ustar www-datawww-datarequire "bundler/setup" if ENV['COVERALLS'] || ENV['COVERAGE'] require 'simplecov' if ENV['COVERALLS'] require 'coveralls' SimpleCov.formatter = Coveralls::SimpleCov::Formatter end SimpleCov.start do add_filter 'test' add_filter 'friendly_id/migration' end end begin require 'minitest' rescue LoadError require 'minitest/unit' end begin TestCaseClass = MiniTest::Test rescue NameError TestCaseClass = MiniTest::Unit::TestCase end require "mocha/setup" require "active_record" require 'active_support/core_ext/time/conversions' I18n.enforce_available_locales = false require "friendly_id" # If you want to see the ActiveRecord log, invoke the tests using `rake test LOG=true` if ENV["LOG"] require "logger" ActiveRecord::Base.logger = Logger.new($stdout) end if ActiveSupport::VERSION::STRING >= '4.2' ActiveSupport.test_order = :random end module FriendlyId module Test def self.included(base) if Minitest.respond_to?(:autorun) Minitest.autorun else require 'minitest/autorun' end rescue LoadError end def transaction ActiveRecord::Base.transaction { yield ; raise ActiveRecord::Rollback } end def with_instance_of(*args) model_class = args.shift args[0] ||= {:name => "a b c"} transaction { yield model_class.create!(*args) } end module Database extend self def connect version = ActiveRecord::VERSION::STRING engine = RUBY_ENGINE rescue "ruby" ActiveRecord::Base.establish_connection config[driver] message = "Using #{engine} #{RUBY_VERSION} AR #{version} with #{driver}" puts "-" * 72 if in_memory? ActiveRecord::Migration.verbose = false Schema.migrate :up puts "#{message} (in-memory)" else puts message end end def config @config ||= YAML::load(File.open(File.expand_path("../databases.yml", __FILE__))) end def driver _driver = ENV.fetch('DB', 'sqlite3').downcase _driver = "postgres" if %w(postgresql pg).include?(_driver) _driver end def in_memory? config[driver]["database"] == ":memory:" end end end end class Module def test(name, &block) define_method("test_#{name.gsub(/[^a-z0-9']/i, "_")}".to_sym, &block) end end require "schema" require "shared" FriendlyId::Test::Database.connect at_exit {ActiveRecord::Base.connection.disconnect!} friendly_id-5.3.0/test/object_utils_test.rb0000644000004100000410000000121713611022220021056 0ustar www-datawww-datarequire "helper" class ObjectUtilsTest < TestCaseClass include FriendlyId::Test test "strings with letters are friendly_ids" do assert "a".friendly_id? end test "integers should be unfriendly ids" do assert 1.unfriendly_id? end test "numeric strings are neither friendly nor unfriendly" do assert_nil "1".friendly_id? assert_nil "1".unfriendly_id? end test "ActiveRecord::Base instances should be unfriendly_ids" do FriendlyId.mark_as_unfriendly(ActiveRecord::Base) model_class = Class.new(ActiveRecord::Base) do self.table_name = "authors" end assert model_class.new.unfriendly_id? end end friendly_id-5.3.0/test/databases.yml0000644000004100000410000000046613611022220017463 0ustar www-datawww-datamysql: adapter: mysql2 database: friendly_id_test username: root hostname: localhost encoding: utf8 postgres: adapter: postgresql host: localhost port: 5432 username: postgres database: friendly_id_test encoding: utf8 sqlite3: adapter: sqlite3 database: ":memory:" encoding: utf8 friendly_id-5.3.0/test/schema.rb0000644000004100000410000000625113611022220016574 0ustar www-datawww-datarequire "friendly_id/migration" module FriendlyId module Test migration_class = if ActiveRecord::VERSION::MAJOR >= 5 ActiveRecord::Migration[4.2] else ActiveRecord::Migration end class Schema < migration_class class << self def down CreateFriendlyIdSlugs.down tables.each do |name| drop_table name end end def up # TODO: use schema version to avoid ugly hacks like this return if @done CreateFriendlyIdSlugs.migrate :up tables.each do |table_name| create_table table_name do |t| t.string :name t.boolean :active end end tables_with_uuid_primary_key.each do |table_name| create_table table_name, primary_key: :uuid_key, id: false do |t| t.string :name t.string :uuid_key, null: false t.string :slug end add_index table_name, :slug, unique: true end slugged_tables.each do |table_name| add_column table_name, :slug, :string add_index table_name, :slug, :unique => true if 'novels' != table_name end scoped_tables.each do |table_name| add_column table_name, :slug, :string end paranoid_tables.each do |table_name| add_column table_name, :slug, :string add_column table_name, :deleted_at, :datetime add_index table_name, :deleted_at end # This will be used to test scopes add_column :novels, :novelist_id, :integer add_column :novels, :publisher_id, :integer add_index :novels, [:slug, :publisher_id, :novelist_id], :unique => true # This will be used to test column name quoting add_column :journalists, "strange name", :string # This will be used to test STI add_column :journalists, "type", :string # These will be used to test i18n add_column :journalists, "slug_en", :string add_column :journalists, "slug_es", :string add_column :journalists, "slug_de", :string # This will be used to test relationships add_column :books, :author_id, :integer # Used to test :scoped and :history together add_column :restaurants, :city_id, :integer # Used to test candidates add_column :cities, :code, :string, :limit => 3 # Used as a non-default slug_column add_column :authors, :subdomain, :string @done = true end private def slugged_tables %w[journalists articles novelists novels manuals cities] end def paranoid_tables ["paranoid_records"] end def tables_with_uuid_primary_key ["menu_items"] end def scoped_tables ["restaurants"] end def simple_tables %w[authors books publishers] end def tables simple_tables + slugged_tables + scoped_tables + paranoid_tables end end end end end friendly_id-5.3.0/test/slugged_test.rb0000644000004100000410000003351613611022220020031 0ustar www-datawww-datarequire "helper" class Journalist < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :slugged end class Article < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :slugged end class Novelist < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :slugged, :sequence_separator => '_' def normalize_friendly_id(string) super.gsub("-", "_") end end class SluggedTest < TestCaseClass include FriendlyId::Test include FriendlyId::Test::Shared::Core include FriendlyId::Test::Shared::Slugged def model_class Journalist end test "should allow validations on the slug" do model_class = Class.new(ActiveRecord::Base) do self.table_name = "articles" extend FriendlyId friendly_id :name, :use => :slugged validates_length_of :slug, :maximum => 1 def self.name "Article" end end instance = model_class.new :name => "hello" refute instance.valid? end test "should allow nil slugs" do transaction do m1 = model_class.create! model_class.create! assert_nil m1.slug end end test "should not break validates_uniqueness_of" do model_class = Class.new(ActiveRecord::Base) do self.table_name = "journalists" extend FriendlyId friendly_id :name, :use => :slugged validates_uniqueness_of :slug_en def self.name "Journalist" end end transaction do instance = model_class.create! :name => "hello", :slug_en => "hello" instance2 = model_class.create :name => "hello", :slug_en => "hello" assert instance.valid? refute instance2.valid? end end test 'should allow a record to reuse its own slug' do with_instance_of(model_class) do |record| old_id = record.friendly_id record.slug = nil record.save! assert_equal old_id, record.friendly_id end end test "should not update matching slug" do klass = Class.new model_class do def should_generate_new_friendly_id? name_changed? end end with_instance_of klass do |record| old_id = record.friendly_id record.name += " " record.save! assert_equal old_id, record.friendly_id end end test "should not set slug on create if unrelated validations fail" do klass = Class.new model_class do validates_presence_of :active friendly_id :name, :use => :slugged def self.name "Journalist" end end transaction do instance = klass.new :name => 'foo' refute instance.save refute instance.valid? assert_nil instance.slug end end test "should not set slug on create if unrelated validations fail with custom slug_column" do klass = Class.new(ActiveRecord::Base) do self.table_name = 'authors' extend FriendlyId validates_presence_of :active friendly_id :name, :use => :slugged, :slug_column => :subdomain def self.name "Author" end end transaction do instance = klass.new :name => 'foo' refute instance.save refute instance.valid? assert_nil instance.subdomain end end test "should not update slug on save if unrelated validations fail" do klass = Class.new model_class do validates_presence_of :active friendly_id :name, :use => :slugged def self.name "Journalist" end end transaction do instance = klass.new :name => 'foo', :active => true assert instance.save assert instance.valid? instance.name = 'foobar' instance.slug = nil instance.active = nil refute instance.save refute instance.valid? assert_equal 'foo', instance.slug end end end class SlugGeneratorTest < TestCaseClass include FriendlyId::Test def model_class Journalist end test "should quote column names" do model_class = Class.new(ActiveRecord::Base) do # This has been added in 635731bb to fix MySQL/Rubinius. It may still # be necessary, but causes an exception to be raised on Rails 4, so I'm # commenting it out. If it causes MySQL/Rubinius to fail again we'll # look for another solution. # self.abstract_class = true self.table_name = "journalists" extend FriendlyId friendly_id :name, :use => :slugged, :slug_column => "strange name" end begin with_instance_of(model_class) {|record| assert model_class.friendly.find(record.friendly_id)} rescue ActiveRecord::StatementInvalid flunk "column name was not quoted" end end test "should not resequence lower sequences on update" do transaction do m1 = model_class.create! :name => "a b c d" assert_equal "a-b-c-d", m1.slug model_class.create! :name => "a b c d" m1 = model_class.friendly.find(m1.id) m1.save! assert_equal "a-b-c-d", m1.slug end end test "should correctly sequence slugs that end with numbers" do transaction do record1 = model_class.create! :name => "Peugeot 206" assert_equal "peugeot-206", record1.slug record2 = model_class.create! :name => "Peugeot 206" assert_match(/\Apeugeot-206-([a-z0-9]+\-){4}[a-z0-9]+\z/, record2.slug) end end test "should correctly sequence slugs with underscores" do transaction do Novelist.create! :name => 'wordsfail, buildings tumble' record2 = Novelist.create! :name => 'word fail' assert_equal 'word_fail', record2.slug end end test "should correctly sequence numeric slugs" do transaction do n2 = 2.times.map {Article.create :name => '123'}.last assert_match(/\A123-.*/, n2.friendly_id) end end end class SlugSeparatorTest < TestCaseClass include FriendlyId::Test class Journalist < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :slugged, :sequence_separator => ":" end def model_class Journalist end test "should sequence with configured sequence separator" do with_instance_of model_class do |record| record2 = model_class.create! :name => record.name assert record2.friendly_id.match(/:.*\z/) end end test "should detect when a stored slug has been cleared" do with_instance_of model_class do |record| record.slug = nil assert record.should_generate_new_friendly_id? end end test "should correctly sequence slugs that uses single dashes as sequence separator" do model_class = Class.new(ActiveRecord::Base) do self.table_name = "journalists" extend FriendlyId friendly_id :name, :use => :slugged, :sequence_separator => '-' def self.name "Journalist" end end transaction do record1 = model_class.create! :name => "Peugeot 206" assert_equal "peugeot-206", record1.slug record2 = model_class.create! :name => "Peugeot 206" assert_match(/\Apeugeot-206-([a-z0-9]+\-){4}[a-z0-9]+\z/, record2.slug) end end test "should sequence blank slugs without a separator" do with_instance_of model_class, :name => "" do |record| assert_match(/\A([a-z0-9]+\-){4}[a-z0-9]+\z/, record.slug) end end end class SlugLimitTest < TestCaseClass include FriendlyId::Test class Journalist < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :slugged, :slug_limit => 40 end def model_class Journalist end test "should limit slug size" do transaction do m1 = model_class.create! :name => 'a' * 50 assert_equal m1.slug, 'a' * 40 m2 = model_class.create! :name => m1.name m2.save! # "aaa-" assert_match(/\Aa{3}\-/, m2.slug) end end end class DefaultScopeTest < TestCaseClass include FriendlyId::Test class Journalist < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :slugged default_scope -> { where(:active => true).order('id ASC') } end test "friendly_id should correctly sequence a default_scoped ordered table" do transaction do 3.times { assert Journalist.create :name => "a", :active => true } end end test "friendly_id should correctly sequence a default_scoped scoped table" do transaction do assert Journalist.create :name => "a", :active => false assert Journalist.create :name => "a", :active => true end end end class UuidAsPrimaryKeyFindTest < TestCaseClass include FriendlyId::Test class MenuItem < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :slugged before_create :init_primary_key def self.primary_key "uuid_key" end # Overwrite the method added by FriendlyId def self.primary_key_type :uuid end private def init_primary_key self.uuid_key = SecureRandom.uuid end end def model_class MenuItem end test "should have a uuid_key as a primary key" do assert_equal "uuid_key", model_class.primary_key assert_equal :uuid, model_class.primary_key_type end test "should be findable by the UUID primary key" do with_instance_of(model_class) do |record| assert model_class.friendly.find record.id end end test "should handle a string that simply contains a UUID correctly" do with_instance_of(model_class) do |record| assert_raises(ActiveRecord::RecordNotFound) do model_class.friendly.find "test-#{SecureRandom.uuid}" end end end end class UnderscoreAsSequenceSeparatorRegressionTest < TestCaseClass include FriendlyId::Test class Manual < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :slugged, :sequence_separator => "_" end test "should not create duplicate slugs" do 3.times do transaction do begin assert Manual.create! :name => "foo" rescue flunk "Tried to insert duplicate slug" end end end end end # https://github.com/norman/friendly_id/issues/148 class FailedValidationAfterUpdateRegressionTest < TestCaseClass include FriendlyId::Test class Journalist < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :slugged validates_presence_of :slug_de end test "to_param should return the unchanged value if the slug changes before validation fails" do transaction do journalist = Journalist.create! :name => "Joseph Pulitzer", :slug_de => "value" assert_equal "joseph-pulitzer", journalist.to_param assert journalist.valid? assert journalist.persisted? journalist.name = "Joe Pulitzer" journalist.slug_de = nil assert !journalist.valid? assert_equal "joseph-pulitzer", journalist.to_param end end end class ToParamTest < TestCaseClass include FriendlyId::Test class Journalist < ActiveRecord::Base extend FriendlyId validates_presence_of :active friendly_id :name, :use => :slugged attr_accessor :to_param_in_callback after_save do self.to_param_in_callback = to_param end end test "to_param should return nil if record is unpersisted" do assert_nil Journalist.new.to_param end test "to_param should return nil if record failed validation" do journalist = Journalist.new :name => 'Clark Kent', :active => nil refute journalist.save assert_nil journalist.to_param end test "to_param should use slugged attribute if record saved successfully" do transaction do journalist = Journalist.new :name => 'Clark Kent', :active => true assert journalist.save assert_equal 'clark-kent', journalist.to_param end end test "to_param should use original slug if existing record changes but fails to save" do transaction do journalist = Journalist.new :name => 'Clark Kent', :active => true assert journalist.save journalist.name = 'Superman' journalist.slug = nil journalist.active = nil refute journalist.save assert_equal 'clark-kent', journalist.to_param end end test "to_param should use new slug if existing record changes successfully" do transaction do journalist = Journalist.new :name => 'Clark Kent', :active => true assert journalist.save journalist.name = 'Superman' journalist.slug = nil assert journalist.save assert_equal 'superman', journalist.to_param end end test "to_param should use new slug within callbacks if new record is saved successfully" do transaction do journalist = Journalist.new :name => 'Clark Kent', :active => true assert journalist.save assert_equal 'clark-kent', journalist.to_param_in_callback, "value of to_param in callback should use the new slug value" end end test "to_param should use new slug within callbacks if existing record changes successfully" do transaction do journalist = Journalist.new :name => 'Clark Kent', :active => true assert journalist.save assert journalist.valid? journalist.name = 'Superman' journalist.slug = nil assert journalist.save, "save should be successful" assert_equal 'superman', journalist.to_param_in_callback, "value of to_param in callback should use the new slug value" end end end class ConfigurableRoutesTest < TestCaseClass include FriendlyId::Test class Article < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :slugged, :routes => :friendly end class Novel < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :slugged, :routes => :default end test "to_param should return a friendly id when the routes option is set to :friendly" do transaction do article = Article.create! :name => "Titanic Hits; Iceberg Sinks" assert_equal "titanic-hits-iceberg-sinks", article.to_param end end test "to_param should return the id when the routes option is set to anything but friendly" do transaction do novel = Novel.create! :name => "Don Quixote" assert_equal novel.id.to_s, novel.to_param end end end friendly_id-5.3.0/test/candidates_test.rb0000644000004100000410000000672513611022220020500 0ustar www-datawww-datarequire "helper" class CandidatesTest < TestCaseClass include FriendlyId::Test class City < ActiveRecord::Base extend FriendlyId friendly_id :slug_candidates, use: :slugged alias_attribute :slug_candidates, :name end def model_class City end def with_instances_of(klass = model_class, &block) transaction do city1 = klass.create! :name => "New York", :code => "JFK" city2 = klass.create! :name => "New York", :code => "EWR" yield city1, city2 end end alias_method :with_instances, :with_instances_of test "resolves conflict with candidate" do with_instances do |city1, city2| assert_equal "new-york", city1.slug assert_match(/\Anew-york-([a-z0-9]+\-){4}[a-z0-9]+\z/, city2.slug) end end test "accepts candidate as symbol" do klass = Class.new model_class do def slug_candidates :name end end with_instances_of klass do |_, city| assert_match(/\Anew-york-([a-z0-9]+\-){4}[a-z0-9]+\z/, city.slug) end end test "accepts multiple candidates" do klass = Class.new model_class do def slug_candidates [name, code] end end with_instances_of klass do |_, city| assert_equal "ewr", city.slug end end test "ignores blank candidate" do klass = Class.new model_class do def slug_candidates [name, ""] end end with_instances_of klass do |_, city| assert_match(/\Anew-york-([a-z0-9]+\-){4}[a-z0-9]+\z/, city.slug) end end test "ignores nil candidate" do klass = Class.new model_class do def slug_candidates [name, nil] end end with_instances_of klass do |_, city| assert_match(/\Anew-york-([a-z0-9]+\-){4}[a-z0-9]+\z/, city.slug) end end test "accepts candidate with nested array" do klass = Class.new model_class do def slug_candidates [name, [name, code]] end end with_instances_of klass do |_, city| assert_equal "new-york-ewr", city.slug end end test "accepts candidate with lambda" do klass = Class.new City do def slug_candidates [name, [name, ->{ rand 1000 }]] end end with_instances_of klass do |_, city| assert_match(/\Anew-york-\d{,3}\z/, city.friendly_id) end end test "accepts candidate with object" do klass = Class.new City do class Airport def initialize(code) @code = code end attr_reader :code alias_method :to_s, :code end def slug_candidates [name, [name, Airport.new(code)]] end end with_instances_of klass do |_, city| assert_equal "new-york-ewr", city.friendly_id end end test "allows to iterate through candidates without passing block" do klass = Class.new model_class do def slug_candidates :name end end with_instances_of klass do |_, city| candidates = FriendlyId::Candidates.new(city, city.slug_candidates) assert_equal candidates.each, ['new-york'] end end test "iterates through candidates with passed block" do klass = Class.new model_class do def slug_candidates :name end end with_instances_of klass do |_, city| collected_candidates = [] candidates = FriendlyId::Candidates.new(city, city.slug_candidates) candidates.each { |candidate| collected_candidates << candidate } assert_equal collected_candidates, ['new-york'] end end end friendly_id-5.3.0/test/scoped_test.rb0000644000004100000410000000622313611022220017647 0ustar www-datawww-datarequire "helper" class Novelist < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :slugged end class Novel < ActiveRecord::Base extend FriendlyId belongs_to :novelist belongs_to :publisher friendly_id :name, :use => :scoped, :scope => [:publisher, :novelist] def should_generate_new_friendly_id? new_record? || super end end class Publisher < ActiveRecord::Base has_many :novels end class ScopedTest < TestCaseClass include FriendlyId::Test include FriendlyId::Test::Shared::Core def model_class Novel end test "should detect scope column from belongs_to relation" do assert_equal ["publisher_id", "novelist_id"], Novel.friendly_id_config.scope_columns end test "should detect scope column from explicit column name" do model_class = Class.new(ActiveRecord::Base) do self.abstract_class = true extend FriendlyId friendly_id :empty, :use => :scoped, :scope => :dummy end assert_equal ["dummy"], model_class.friendly_id_config.scope_columns end test "should allow duplicate slugs outside scope" do transaction do novel1 = Novel.create! :name => "a", :novelist => Novelist.create!(:name => "a") novel2 = Novel.create! :name => "a", :novelist => Novelist.create!(:name => "b") assert_equal novel1.friendly_id, novel2.friendly_id end end test "should not allow duplicate slugs inside scope" do with_instance_of Novelist do |novelist| novel1 = Novel.create! :name => "a", :novelist => novelist novel2 = Novel.create! :name => "a", :novelist => novelist assert novel1.friendly_id != novel2.friendly_id end end test "should apply scope with multiple columns" do transaction do novelist = Novelist.create! :name => "a" publisher = Publisher.create! :name => "b" novel1 = Novel.create! :name => "c", :novelist => novelist, :publisher => publisher novel2 = Novel.create! :name => "c", :novelist => novelist, :publisher => Publisher.create(:name => "d") novel3 = Novel.create! :name => "c", :novelist => Novelist.create(:name => "e"), :publisher => publisher novel4 = Novel.create! :name => "c", :novelist => novelist, :publisher => publisher assert_equal novel1.friendly_id, novel2.friendly_id assert_equal novel2.friendly_id, novel3.friendly_id assert novel3.friendly_id != novel4.friendly_id end end test 'should allow a record to reuse its own slug' do with_instance_of(model_class) do |record| old_id = record.friendly_id record.slug = nil record.save! assert_equal old_id, record.friendly_id end end test "should generate new slug when scope changes" do transaction do novelist = Novelist.create! :name => "a" publisher = Publisher.create! :name => "b" novel1 = Novel.create! :name => "c", :novelist => novelist, :publisher => publisher novel2 = Novel.create! :name => "c", :novelist => novelist, :publisher => Publisher.create(:name => "d") assert_equal novel1.friendly_id, novel2.friendly_id novel2.publisher = publisher novel2.save! assert novel2.friendly_id != novel1.friendly_id end end end friendly_id-5.3.0/test/core_test.rb0000644000004100000410000000136113611022220017320 0ustar www-datawww-datarequire "helper" class Book < ActiveRecord::Base extend FriendlyId friendly_id :name end class Author < ActiveRecord::Base extend FriendlyId friendly_id :name has_many :books end class CoreTest < TestCaseClass include FriendlyId::Test include FriendlyId::Test::Shared::Core def model_class Author end test "models don't use friendly_id by default" do assert !Class.new(ActiveRecord::Base) { self.abstract_class = true }.respond_to?(:friendly_id) end test "model classes should have a friendly id config" do assert model_class.friendly_id(:name).friendly_id_config end test "instances should have a friendly id" do with_instance_of(model_class) {|record| assert record.friendly_id} end end friendly_id-5.3.0/test/numeric_slug_test.rb0000644000004100000410000000126613611022220021070 0ustar www-datawww-datarequire 'helper' class NumericSlugTest < TestCaseClass include FriendlyId::Test include FriendlyId::Test::Shared::Core def model_class Article end test "should generate numeric slugs" do transaction do record = model_class.create! :name => "123" assert_equal "123", record.slug end end test "should find by numeric slug" do transaction do record = model_class.create! :name => "123" assert_equal model_class.friendly.find("123").id, record.id end end test "should exist? by numeric slug" do transaction do record = model_class.create! :name => "123" assert model_class.friendly.exists?("123") end end end friendly_id-5.3.0/test/sequentially_slugged_test.rb0000644000004100000410000001351113611022220022621 0ustar www-datawww-datarequire 'helper' class Article < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :sequentially_slugged end class SequentiallySluggedTest < TestCaseClass include FriendlyId::Test include FriendlyId::Test::Shared::Core def model_class Article end test "should generate numerically sequential slugs" do transaction do records = 12.times.map { model_class.create! :name => "Some news" } assert_equal "some-news", records[0].slug (1...12).each {|i| assert_equal "some-news-#{i + 1}", records[i].slug} end end test "should cope when slugs are missing from the sequence" do transaction do record_1 = model_class.create!(:name => 'A thing') record_2 = model_class.create!(:name => 'A thing') record_3 = model_class.create!(:name => 'A thing') assert_equal 'a-thing', record_1.slug assert_equal 'a-thing-2', record_2.slug assert_equal 'a-thing-3', record_3.slug record_2.destroy record_4 = model_class.create!(:name => 'A thing') assert_equal 'a-thing-4', record_4.slug end end test "should cope with strange column names" do model_class = Class.new(ActiveRecord::Base) do self.table_name = "journalists" extend FriendlyId friendly_id :name, :use => :sequentially_slugged, :slug_column => "strange name" end transaction do record_1 = model_class.create! name: "Julian Assange" record_2 = model_class.create! name: "Julian Assange" assert_equal 'julian-assange', record_1.attributes["strange name"] assert_equal 'julian-assange-2', record_2.attributes["strange name"] end end test "should correctly sequence slugs that end in a number" do transaction do record1 = model_class.create! :name => "Peugeuot 206" assert_equal "peugeuot-206", record1.slug record2 = model_class.create! :name => "Peugeuot 206" assert_equal "peugeuot-206-2", record2.slug end end test "should correctly sequence slugs that begin with a number" do transaction do record1 = model_class.create! :name => "2010 to 2015 Records" assert_equal "2010-to-2015-records", record1.slug record2 = model_class.create! :name => "2010 to 2015 Records" assert_equal "2010-to-2015-records-2", record2.slug end end test "should sequence with a custom sequence separator" do model_class = Class.new(ActiveRecord::Base) do self.table_name = "novelists" extend FriendlyId friendly_id :name, :use => :sequentially_slugged, :sequence_separator => ':' end transaction do record_1 = model_class.create! name: "Julian Barnes" record_2 = model_class.create! name: "Julian Barnes" assert_equal 'julian-barnes', record_1.slug assert_equal 'julian-barnes:2', record_2.slug end end test "should not generate a slug when canidates set is empty" do model_class = Class.new(ActiveRecord::Base) do self.table_name = "cities" extend FriendlyId friendly_id :slug_candidates, :use => [ :sequentially_slugged ] def slug_candidates [name, [name, code]] end end transaction do record = model_class.create!(:name => nil, :code => nil) assert_nil record.slug end end test "should not generate a slug when the sluggable attribute is blank" do record = model_class.create!(:name => '') assert_nil record.slug end end class SequentiallySluggedTestWithHistory < TestCaseClass include FriendlyId::Test include FriendlyId::Test::Shared::Core class Article < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => [:sequentially_slugged, :history] end def model_class Article end test "should work with regeneration with history when slug already exists" do transaction do record1 = model_class.create! :name => "Test name" record2 = model_class.create! :name => "Another test name" assert_equal 'test-name', record1.slug assert_equal 'another-test-name', record2.slug record2.name = "Test name" record2.slug = nil record2.save! assert_equal 'test-name-2', record2.slug end end test "should work with regeneration with history when 2 slugs already exists and the second is changed" do transaction do record1 = model_class.create! :name => "Test name" record2 = model_class.create! :name => "Test name" record3 = model_class.create! :name => "Another test name" assert_equal 'test-name', record1.slug assert_equal 'test-name-2', record2.slug assert_equal 'another-test-name', record3.slug record2.name = "One more test name" record2.slug = nil record2.save! assert_equal 'one-more-test-name', record2.slug record3.name = "Test name" record3.slug = nil record3.save! assert_equal 'test-name-3', record3.slug end end end class City < ActiveRecord::Base has_many :restaurants end class Restaurant < ActiveRecord::Base extend FriendlyId belongs_to :city friendly_id :name, :use => [:sequentially_slugged, :scoped, :history], :scope => :city end class SequentiallySluggedTestWithScopedHistory < TestCaseClass include FriendlyId::Test include FriendlyId::Test::Shared::Core def model_class Restaurant end test "should work with regeneration with scoped history" do transaction do city1 = City.create! city2 = City.create! record1 = model_class.create! :name => "Test name", :city => city1 record2 = model_class.create! :name => "Test name", :city => city1 assert_equal 'test-name', record1.slug assert_equal 'test-name-2', record2.slug record2.name = 'Another test name' record2.slug = nil record2.save! record3 = model_class.create! :name => "Test name", :city => city1 assert_equal 'test-name-3', record3.slug end end end friendly_id-5.3.0/test/finders_test.rb0000644000004100000410000000126413611022220020024 0ustar www-datawww-datarequire "helper" class JournalistWithFriendlyFinders < ActiveRecord::Base self.table_name = 'journalists' extend FriendlyId scope :existing, -> {where('1 = 1')} friendly_id :name, use: [:slugged, :finders] end class Finders < TestCaseClass include FriendlyId::Test def model_class JournalistWithFriendlyFinders end test 'should find records with finders as class methods' do with_instance_of(model_class) do |record| assert model_class.find(record.friendly_id) end end test 'should find records with finders on relations' do with_instance_of(model_class) do |record| assert model_class.existing.find(record.friendly_id) end end end friendly_id-5.3.0/test/reserved_test.rb0000644000004100000410000000372113611022220020211 0ustar www-datawww-datarequire "helper" class ReservedTest < TestCaseClass include FriendlyId::Test class Journalist < ActiveRecord::Base extend FriendlyId friendly_id :slug_candidates, :use => [:slugged, :reserved], :reserved_words => %w(new edit) after_validation :move_friendly_id_error_to_name def move_friendly_id_error_to_name errors.add :name, *errors.delete(:friendly_id) if errors[:friendly_id].present? end def slug_candidates name end end def model_class Journalist end test "should reserve words" do %w(new edit NEW Edit).each do |word| transaction do assert_raises(ActiveRecord::RecordInvalid) {model_class.create! :name => word} end end end test "should move friendly_id error to name" do with_instance_of(model_class) do |record| record.errors.add :name, "xxx" record.errors.add :friendly_id, "yyy" record.move_friendly_id_error_to_name assert record.errors[:name].present? && record.errors[:friendly_id].blank? assert_equal 2, record.errors.count end end test "should reject reserved candidates" do transaction do record = model_class.new(:name => 'new') def record.slug_candidates [:name, "foo"] end record.save! assert_equal "foo", record.friendly_id end end test "should be invalid if all candidates are reserved" do transaction do record = model_class.new(:name => 'new') def record.slug_candidates ["edit", "new"] end assert_raises(ActiveRecord::RecordInvalid) {record.save!} end end test "should optionally treat reserved words as conflict" do klass = Class.new(model_class) do friendly_id :slug_candidates, :use => [:slugged, :reserved], :reserved_words => %w(new edit), :treat_reserved_as_conflict => true end with_instance_of(klass, name: 'new') do |record| assert_match(/new-([0-9a-z]+\-){4}[0-9a-z]+\z/, record.slug) end end end friendly_id-5.3.0/test/sti_test.rb0000644000004100000410000000657213611022220017200 0ustar www-datawww-datarequire "helper" class StiTest < TestCaseClass include FriendlyId::Test include FriendlyId::Test::Shared::Core include FriendlyId::Test::Shared::Slugged class Journalist < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => [:slugged] end class Editorialist < Journalist end def model_class Editorialist end test "friendly_id should accept a base and a hash with single table inheritance" do abstract_klass = Class.new(ActiveRecord::Base) do def self.table_exists?; false end extend FriendlyId friendly_id :foo, :use => :slugged, :slug_column => :bar end klass = Class.new(abstract_klass) assert klass < FriendlyId::Slugged assert_equal :foo, klass.friendly_id_config.base assert_equal :bar, klass.friendly_id_config.slug_column end test "the configuration's model_class should be the class, not the base_class" do assert_equal model_class, model_class.friendly_id_config.model_class end test "friendly_id should accept a block with single table inheritance" do abstract_klass = Class.new(ActiveRecord::Base) do def self.table_exists?; false end extend FriendlyId friendly_id :foo do |config| config.use :slugged config.base = :foo config.slug_column = :bar end end klass = Class.new(abstract_klass) assert klass < FriendlyId::Slugged assert_equal :foo, klass.friendly_id_config.base assert_equal :bar, klass.friendly_id_config.slug_column end test "friendly_id slugs should not clash with each other" do transaction do journalist = model_class.base_class.create! :name => 'foo bar' editoralist = model_class.create! :name => 'foo bar' assert_equal 'foo-bar', journalist.slug assert_match(/foo-bar-.+/, editoralist.slug) end end end class StiTestWithHistory < StiTest class Journalist < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => [:slugged, :history] end class Editorialist < Journalist end def model_class Editorialist end end class StiTestWithFinders < TestCaseClass include FriendlyId::Test class Journalist < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => [:slugged, :finders] end class Editorialist < Journalist extend FriendlyId friendly_id :name, :use => [:slugged, :finders] end def model_class Editorialist end test "friendly_id slugs should be looked up from subclass with friendly" do transaction do editoralist = model_class.create! :name => 'foo bar' assert_equal editoralist, model_class.friendly.find(editoralist.slug) end end test "friendly_id slugs should be looked up from subclass" do transaction do editoralist = model_class.create! :name => 'foo bar' assert_equal editoralist, model_class.find(editoralist.slug) end end end class StiTestSubClass < TestCaseClass include FriendlyId::Test class Journalist < ActiveRecord::Base extend FriendlyId end class Editorialist < Journalist extend FriendlyId friendly_id :name, :use => [:slugged, :finders] end def model_class Editorialist end test "friendly_id slugs can be created and looked up from subclass" do transaction do editoralist = model_class.create! :name => 'foo bar' assert_equal editoralist, model_class.find(editoralist.slug) end end endfriendly_id-5.3.0/test/simple_i18n_test.rb0000644000004100000410000001006513611022220020521 0ustar www-datawww-datarequire "helper" class SimpleI18nTest < TestCaseClass include FriendlyId::Test class Journalist < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :simple_i18n end def setup I18n.locale = :en end test "friendly_id should return the current locale's slug" do journalist = Journalist.new(:name => "John Doe") journalist.slug_es = "juan-fulano" journalist.valid? I18n.with_locale(I18n.default_locale) do assert_equal "john-doe", journalist.friendly_id end I18n.with_locale(:es) do assert_equal "juan-fulano", journalist.friendly_id end end test "should create record with slug in column for the current locale" do I18n.with_locale(I18n.default_locale) do journalist = Journalist.new(:name => "John Doe") journalist.valid? assert_equal "john-doe", journalist.slug_en assert_nil journalist.slug_es end I18n.with_locale(:es) do journalist = Journalist.new(:name => "John Doe") journalist.valid? assert_equal "john-doe", journalist.slug_es assert_nil journalist.slug_en end end test "to_param should return the numeric id when there's no slug for the current locale" do transaction do journalist = Journalist.new(:name => "Juan Fulano") I18n.with_locale(:es) do journalist.save! assert_equal "juan-fulano", journalist.to_param end assert_equal journalist.id.to_s, journalist.to_param end end test "should set friendly id for locale" do transaction do journalist = Journalist.create!(:name => "John Smith") journalist.set_friendly_id("Juan Fulano", :es) journalist.save! assert_equal "juan-fulano", journalist.slug_es I18n.with_locale(:es) do assert_equal "juan-fulano", journalist.to_param end end end test "set friendly_id should fall back default locale when none is given" do transaction do journalist = I18n.with_locale(:es) do Journalist.create!(:name => "Juan Fulano") end journalist.set_friendly_id("John Doe") journalist.save! assert_equal "john-doe", journalist.slug_en end end test "should sequence localized slugs" do transaction do journalist = Journalist.create!(:name => "John Smith") I18n.with_locale(:es) do Journalist.create!(:name => "Juan Fulano") end journalist.set_friendly_id("Juan Fulano", :es) journalist.save! assert_equal "john-smith", journalist.to_param I18n.with_locale(:es) do assert_match(/juan-fulano-.+/, journalist.to_param) end end end class RegressionTest < TestCaseClass include FriendlyId::Test test "should not overwrite other locale's slugs on update_attributes" do transaction do journalist = Journalist.create!(:name => "John Smith") journalist.set_friendly_id("Juan Fulano", :es) journalist.save! assert_equal "john-smith", journalist.to_param journalist.slug = nil journalist.update_attributes :name => "Johnny Smith" assert_equal "johnny-smith", journalist.to_param I18n.with_locale(:es) do assert_equal "juan-fulano", journalist.to_param end end end end class ConfigurationTest < TestCaseClass test "should add locale to slug column for a non-default locale" do I18n.with_locale :es do assert_equal "slug_es", Journalist.friendly_id_config.slug_column end end test "should add locale to non-default slug column and non-default locale" do model_class = Class.new(ActiveRecord::Base) do self.abstract_class = true extend FriendlyId friendly_id :name, :use => :simple_i18n, :slug_column => :foo end I18n.with_locale :es do assert_equal "foo_es", model_class.friendly_id_config.slug_column end end test "should add locale to slug column for default locale" do I18n.with_locale(I18n.default_locale) do assert_equal "slug_en", Journalist.friendly_id_config.slug_column end end end end friendly_id-5.3.0/test/generator_test.rb0000644000004100000410000000217313611022220020360 0ustar www-datawww-datarequire "helper" require "rails/generators" require "generators/friendly_id_generator" class FriendlyIdGeneratorTest < Rails::Generators::TestCase tests FriendlyIdGenerator destination File.expand_path("../../tmp", __FILE__) setup :prepare_destination test "should generate a migration" do begin run_generator assert_migration "db/migrate/create_friendly_id_slugs" ensure FileUtils.rm_rf self.destination_root end end test "should skip the migration when told to do so" do begin run_generator ['--skip-migration'] assert_no_migration "db/migrate/create_friendly_id_slugs" ensure FileUtils.rm_rf self.destination_root end end test "should generate an initializer" do begin run_generator assert_file "config/initializers/friendly_id.rb" ensure FileUtils.rm_rf self.destination_root end end test "should skip the initializer when told to do so" do begin run_generator ['--skip-initializer'] assert_no_file "config/initializers/friendly_id.rb" ensure FileUtils.rm_rf self.destination_root end end end friendly_id-5.3.0/test/benchmarks/0000755000004100000410000000000013611022220017120 5ustar www-datawww-datafriendly_id-5.3.0/test/benchmarks/finders.rb0000644000004100000410000000463513611022220021107 0ustar www-datawww-datarequire File.expand_path("../../helper", __FILE__) require "ffaker" # This benchmark tests ActiveRecord and FriendlyId methods for performing a find # # ActiveRecord: where.first 8.970000 0.040000 9.010000 ( 9.029544) # ActiveRecord: where.take 8.100000 0.030000 8.130000 ( 8.157024) # ActiveRecord: find 2.720000 0.010000 2.730000 ( 2.733527) # ActiveRecord: find_by(:id) 2.920000 0.000000 2.920000 ( 2.926318) # ActiveRecord: find_by(:slug) 2.650000 0.020000 2.670000 ( 2.662677) # FriendlyId: find (in-table slug w/ finders) 9.820000 0.030000 9.850000 ( 9.873358) # FriendlyId: friendly.find (in-table slug) 12.890000 0.050000 12.940000 ( 12.951156) N = 50000 def transaction ActiveRecord::Base.transaction { yield ; raise ActiveRecord::Rollback } end class Array def rand self[Kernel.rand(length)] end end Book = Class.new ActiveRecord::Base class Journalist < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :slugged end class Manual < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :history end class Restaurant < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :finders end BOOKS = [] JOURNALISTS = [] MANUALS = [] RESTAURANTS = [] 100.times do name = FFaker::Name.name BOOKS << (Book.create! :name => name).id JOURNALISTS << (Journalist.create! :name => name).friendly_id MANUALS << (Manual.create! :name => name).friendly_id RESTAURANTS << (Restaurant.create! :name => name).friendly_id end ActiveRecord::Base.connection.execute "UPDATE manuals SET slug = NULL" Benchmark.bmbm do |x| x.report 'ActiveRecord: where.first' do N.times {Book.where(:id=>BOOKS.rand).first} end x.report 'ActiveRecord: where.take' do N.times {Book.where(:id=>BOOKS.rand).take} end x.report 'ActiveRecord: find' do N.times {Book.find BOOKS.rand} end x.report 'ActiveRecord: find_by(:id)' do N.times {Book.find_by(:id=>BOOKS.rand)} end x.report 'ActiveRecord: find_by(:slug)' do N.times {Restaurant.find_by(:slug=>RESTAURANTS.rand)} end x.report 'FriendlyId: find (in-table slug w/ finders)' do N.times {Restaurant.find RESTAURANTS.rand} end x.report 'FriendlyId: friendly.find (in-table slug)' do N.times {Restaurant.friendly.find RESTAURANTS.rand} end end friendly_id-5.3.0/test/benchmarks/object_utils.rb0000644000004100000410000000514113611022220022134 0ustar www-datawww-datarequire File.expand_path("../../helper", __FILE__) # This benchmark compares the timings of the friendly_id? and unfriendly_id? on various objects # # integer friendly_id? 6.370000 0.000000 6.370000 ( 6.380925) # integer unfriendly_id? 6.640000 0.010000 6.650000 ( 6.646057) # AR::Base friendly_id? 2.340000 0.000000 2.340000 ( 2.340743) # AR::Base unfriendly_id? 2.560000 0.000000 2.560000 ( 2.560039) # hash friendly_id? 5.090000 0.010000 5.100000 ( 5.097662) # hash unfriendly_id? 5.430000 0.000000 5.430000 ( 5.437160) # nil friendly_id? 5.610000 0.010000 5.620000 ( 5.611487) # nil unfriendly_id? 5.870000 0.000000 5.870000 ( 5.880484) # numeric string friendly_id? 9.270000 0.030000 9.300000 ( 9.308452) # numeric string unfriendly_id? 9.190000 0.040000 9.230000 ( 9.252890) # test_string friendly_id? 8.380000 0.010000 8.390000 ( 8.411762) # test_string unfriendly_id? 8.450000 0.010000 8.460000 ( 8.463662) # From the ObjectUtils docs... # 123.friendly_id? #=> false # :id.friendly_id? #=> false # {:name => 'joe'}.friendly_id? #=> false # ['name = ?', 'joe'].friendly_id? #=> false # nil.friendly_id? #=> false # "123".friendly_id? #=> nil # "abc123".friendly_id? #=> true Book = Class.new ActiveRecord::Base test_integer = 123 test_active_record_object = Book.new test_hash = {:name=>'joe'} test_nil = nil test_numeric_string = "123" test_string = "abc123" N = 5_000_000 Benchmark.bmbm do |x| x.report('integer friendly_id?') { N.times {test_integer.friendly_id?} } x.report('integer unfriendly_id?') { N.times {test_integer.unfriendly_id?} } x.report('AR::Base friendly_id?') { N.times {test_active_record_object.friendly_id?} } x.report('AR::Base unfriendly_id?') { N.times {test_active_record_object.unfriendly_id?} } x.report('hash friendly_id?') { N.times {test_hash.friendly_id?} } x.report('hash unfriendly_id?') { N.times {test_hash.unfriendly_id?} } x.report('nil friendly_id?') { N.times {test_nil.friendly_id?} } x.report('nil unfriendly_id?') { N.times {test_nil.unfriendly_id?} } x.report('numeric string friendly_id?') { N.times {test_numeric_string.friendly_id?} } x.report('numeric string unfriendly_id?') { N.times {test_numeric_string.unfriendly_id?} } x.report('test_string friendly_id?') { N.times {test_string.friendly_id?} } x.report('test_string unfriendly_id?') { N.times {test_string.unfriendly_id?} } end friendly_id-5.3.0/test/configuration_test.rb0000644000004100000410000000312413611022220021236 0ustar www-datawww-datarequire "helper" class ConfigurationTest < TestCaseClass include FriendlyId::Test def setup @model_class = Class.new(ActiveRecord::Base) do self.abstract_class = true end end test "should set model class on initialization" do config = FriendlyId::Configuration.new @model_class assert_equal @model_class, config.model_class end test "should set options on initialization if present" do config = FriendlyId::Configuration.new @model_class, :base => "hello" assert_equal "hello", config.base end test "should raise error if passed unrecognized option" do assert_raises NoMethodError do FriendlyId::Configuration.new @model_class, :foo => "bar" end end test "#use should accept a name that resolves to a module" do refute @model_class < FriendlyId::Slugged @model_class.class_eval do extend FriendlyId friendly_id :hello, :use => :slugged end assert @model_class < FriendlyId::Slugged end test "#use should accept a module" do my_module = Module.new refute @model_class < my_module @model_class.class_eval do extend FriendlyId friendly_id :hello, :use => my_module end assert @model_class < my_module end test "#base should optionally set a value" do config = FriendlyId::Configuration.new @model_class assert_nil config.base config.base = 'foo' assert_equal 'foo', config.base end test "#base can set the value to nil" do config = FriendlyId::Configuration.new @model_class config.base 'foo' config.base nil assert_nil config.base end end friendly_id-5.3.0/test/shared.rb0000644000004100000410000001534513611022220016606 0ustar www-datawww-datamodule FriendlyId module Test module Shared module Slugged test "configuration should have a sequence_separator" do assert !model_class.friendly_id_config.sequence_separator.empty? end test "should make a new slug if the slug has been set to nil changed" do with_instance_of model_class do |record| record.name = "Changed Value" record.slug = nil record.save! assert_equal "changed-value", record.slug end end test "should add a UUID for duplicate friendly ids" do with_instance_of model_class do |record| record2 = model_class.create! :name => record.name assert record2.friendly_id.match(/([0-9a-z]+\-){4}[0-9a-z]+\z/) end end test "should not add slug sequence on update after other conflicting slugs were added" do with_instance_of model_class do |record| old = record.friendly_id model_class.create! :name => record.name record.save! record.reload assert_equal old, record.to_param end end test "should not change the sequence on save" do with_instance_of model_class do |record| record2 = model_class.create! :name => record.name friendly_id = record2.friendly_id record2.active = !record2.active record2.save! assert_equal friendly_id, record2.reload.friendly_id end end test "should create slug on save if the slug is nil" do with_instance_of model_class do |record| record.slug = nil record.save! refute_nil record.slug end end test "should set the slug to nil on dup" do with_instance_of model_class do |record| record2 = record.dup assert_nil record2.slug end end test "when validations block save, to_param should return friendly_id rather than nil" do my_model_class = Class.new(model_class) self.class.const_set("Foo", my_model_class) with_instance_of my_model_class do |record| record.update_attributes my_model_class.friendly_id_config.slug_column => nil record = my_model_class.friendly.find(record.id) record.class.validate Proc.new {errors.add(:name, "FAIL")} record.save assert_equal record.to_param, record.friendly_id end end end module Core test "finds should respect conditions" do with_instance_of(model_class) do |record| assert_raises(ActiveRecord::RecordNotFound) do model_class.where("1 = 2").friendly.find record.friendly_id end assert_raises(ActiveRecord::RecordNotFound) do model_class.where("1 = 2").friendly.find record.id end end end test "should be findable by friendly id" do with_instance_of(model_class) {|record| assert model_class.friendly.find record.friendly_id} end test "should exist? by friendly id" do with_instance_of(model_class) do |record| assert model_class.friendly.exists? record.id assert model_class.friendly.exists? record.id.to_s assert model_class.friendly.exists? record.friendly_id assert model_class.friendly.exists?({:id => record.id}) assert model_class.friendly.exists?(['id = ?', record.id]) assert !model_class.friendly.exists?(record.friendly_id + "-hello") assert !model_class.friendly.exists?(0) end end test "should be findable by id as integer" do with_instance_of(model_class) {|record| assert model_class.friendly.find record.id.to_i} end test "should be findable by id as string" do with_instance_of(model_class) {|record| assert model_class.friendly.find record.id.to_s} end test "should treat numeric part of string as an integer id" do with_instance_of(model_class) do |record| assert_raises(ActiveRecord::RecordNotFound) do model_class.friendly.find "#{record.id}-foo" end end end test "should be findable by numeric friendly_id" do with_instance_of(model_class, :name => "206") {|record| assert model_class.friendly.find record.friendly_id} end test "to_param should return the friendly_id" do with_instance_of(model_class) {|record| assert_equal record.friendly_id, record.to_param} end if ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR < 2 test "should be findable by themselves" do with_instance_of(model_class) {|record| assert_equal record, model_class.friendly.find(record)} end end test "updating record's other values should not change the friendly_id" do with_instance_of model_class do |record| old = record.friendly_id record.update_attributes! :active => false assert model_class.friendly.find old end end test "instances found by a single id should not be read-only" do with_instance_of(model_class) {|record| assert !model_class.friendly.find(record.friendly_id).readonly?} end test "failing finds with unfriendly_id should raise errors normally" do assert_raises(ActiveRecord::RecordNotFound) {model_class.friendly.find 0} end test "should return numeric id if the friendly_id is nil" do with_instance_of(model_class) do |record| record.expects(:friendly_id).returns(nil) assert_equal record.id.to_s, record.to_param end end test "should return numeric id if the friendly_id is an empty string" do with_instance_of(model_class) do |record| record.expects(:friendly_id).returns("") assert_equal record.id.to_s, record.to_param end end test "should return the friendly_id as a string" do with_instance_of(model_class) do |record| record.expects(:friendly_id).returns(5) assert_equal "5", record.to_param end end test "should return numeric id if the friendly_id is blank" do with_instance_of(model_class) do |record| record.expects(:friendly_id).returns(" ") assert_equal record.id.to_s, record.to_param end end test "should return nil for to_param with a new record" do assert_nil model_class.new.to_param end end end end end friendly_id-5.3.0/test/history_test.rb0000644000004100000410000002645613611022220020105 0ustar www-datawww-datarequire "helper" class HistoryTest < TestCaseClass include FriendlyId::Test include FriendlyId::Test::Shared::Core class Manual < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => [:slugged, :history] end def model_class Manual end test "should insert record in slugs table on create" do with_instance_of(model_class) {|record| assert record.slugs.any?} end test "should not create new slug record if friendly_id is not changed" do with_instance_of(model_class) do |record| record.active = true record.save! assert_equal 1, FriendlyId::Slug.count end end test "should create new slug record when friendly_id changes" do with_instance_of(model_class) do |record| record.name = record.name + "b" record.slug = nil record.save! assert_equal 2, FriendlyId::Slug.count end end test "should be findable by old slugs" do with_instance_of(model_class) do |record| old_friendly_id = record.friendly_id record.name = record.name + "b" record.slug = nil record.save! begin assert model_class.friendly.find(old_friendly_id) assert model_class.friendly.exists?(old_friendly_id), "should exist? by old id" rescue ActiveRecord::RecordNotFound flunk "Could not find record by old id" end end end test "should create slug records on each change" do transaction do record = model_class.create! :name => "hello" assert_equal 1, FriendlyId::Slug.count record = model_class.friendly.find("hello") record.name = "hello again" record.slug = nil record.save! assert_equal 2, FriendlyId::Slug.count end end test "should not be read only when found by slug" do with_instance_of(model_class) do |record| refute model_class.friendly.find(record.friendly_id).readonly? assert record.update_attribute :name, 'foo' assert record.update_attributes name: 'foo' end end test "should not be read only when found by old slug" do with_instance_of(model_class) do |record| old_friendly_id = record.friendly_id record.name = record.name + "b" record.save! assert !model_class.friendly.find(old_friendly_id).readonly? end end test "should handle renames" do with_instance_of(model_class) do |record| record.name = 'x' record.slug = nil assert record.save record.name = 'y' record.slug = nil assert record.save record.name = 'x' record.slug = nil assert record.save end end test "should not create new slugs that match old slugs" do transaction do first_record = model_class.create! :name => "foo" first_record.name = "bar" first_record.save! second_record = model_class.create! :name => "foo" assert second_record.slug != "foo" assert_match(/foo-.+/, second_record.slug) end end test 'should not fail when updating historic slugs' do transaction do first_record = model_class.create! :name => "foo" second_record = model_class.create! :name => 'another' second_record.update_attributes :name => 'foo', :slug => nil assert_match(/foo-.*/, second_record.slug) first_record.update_attributes :name => 'another', :slug => nil assert_match(/another-.*/, first_record.slug) end end test "should prefer product that used slug most recently" do transaction do first_record = model_class.create! name: "foo" second_record = model_class.create! name: "bar" first_record.update! slug: "not_foo" second_record.update! slug: "foo" #now both records have used foo; second_record most recently second_record.update! slug: "not_bar" assert_equal model_class.friendly.find("foo"), second_record end end test 'should name table according to prefix and suffix' do transaction do begin prefix = "prefix_" without_prefix = FriendlyId::Slug.table_name ActiveRecord::Base.table_name_prefix = prefix FriendlyId::Slug.reset_table_name assert_equal prefix + without_prefix, FriendlyId::Slug.table_name ensure ActiveRecord::Base.table_name_prefix = "" FriendlyId::Slug.table_name = without_prefix end end end end class HistoryTestWithAutomaticSlugRegeneration < HistoryTest class Manual < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => [:slugged, :history] def should_generate_new_friendly_id? slug.blank? or name_changed? end end def model_class Manual end test 'should allow reversion back to a previously used slug' do with_instance_of(model_class, name: 'foo') do |record| record.name = 'bar' record.save! assert_equal 'bar', record.friendly_id record.name = 'foo' record.save! assert_equal 'foo', record.friendly_id end end end class DependentDestroyTest < TestCaseClass include FriendlyId::Test class FalseManual < ActiveRecord::Base self.table_name = 'manuals' extend FriendlyId friendly_id :name, :use => :history, :dependent => false end class DefaultManual < ActiveRecord::Base self.table_name = 'manuals' extend FriendlyId friendly_id :name, :use => :history end test 'should allow disabling of dependent destroy' do transaction do assert FriendlyId::Slug.find_by_slug('foo').nil? l = FalseManual.create! :name => 'foo' assert FriendlyId::Slug.find_by_slug('foo').present? l.destroy assert FriendlyId::Slug.find_by_slug('foo').present? end end test 'should dependently destroy by default' do transaction do assert FriendlyId::Slug.find_by_slug('baz').nil? l = DefaultManual.create! :name => 'baz' assert FriendlyId::Slug.find_by_slug('baz').present? l.destroy assert FriendlyId::Slug.find_by_slug('baz').nil? end end end if ActiveRecord::VERSION::STRING >= '5.0' class HistoryTestWithParanoidDeletes < HistoryTest class ParanoidRecord < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :history, :dependent => false default_scope { where(deleted_at: nil) } end def model_class ParanoidRecord end test 'slug should have a sluggable even when soft deleted by a library' do transaction do assert FriendlyId::Slug.find_by_slug('paranoid').nil? record = model_class.create(name: 'paranoid') assert FriendlyId::Slug.find_by_slug('paranoid').present? record.update_attribute(:deleted_at, Time.now) orphan_slug = FriendlyId::Slug.find_by_slug('paranoid') assert orphan_slug.present?, 'Orphaned slug should exist' assert orphan_slug.valid?, "Errors: #{orphan_slug.errors.full_messages}" assert orphan_slug.sluggable.present?, 'Orphaned slug should still find corresponding paranoid sluggable' end end end end class HistoryTestWithSti < HistoryTest class Journalist < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => [:slugged, :history] end class Editorialist < Journalist end def model_class Editorialist end end class HistoryTestWithFriendlyFinders < HistoryTest class Journalist < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => [:slugged, :finders, :history] end class Restaurant < ActiveRecord::Base extend FriendlyId belongs_to :city friendly_id :name, :use => [:slugged, :history, :finders] end test "should be findable by old slugs" do [Journalist, Restaurant].each do |model_class| with_instance_of(model_class) do |record| old_friendly_id = record.friendly_id record.name = record.name + "b" record.slug = nil record.save! begin assert model_class.find(old_friendly_id) assert model_class.exists?(old_friendly_id), "should exist? by old id for #{model_class.name}" rescue ActiveRecord::RecordNotFound flunk "Could not find record by old id for #{model_class.name}" end end end end end class HistoryTestWithFindersBeforeHistory < HistoryTest class Novelist < ActiveRecord::Base has_many :novels end class Novel < ActiveRecord::Base extend FriendlyId belongs_to :novelist friendly_id :name, :use => [:finders, :history] def should_generate_new_friendly_id? slug.blank? || name_changed? end end test "should be findable by old slug through has_many association" do transaction do novelist = Novelist.create!(:name => "Stephen King") novel = novelist.novels.create(:name => "Rita Hayworth and Shawshank Redemption") slug = novel.slug novel.name = "Shawshank Redemption" novel.save! assert_equal novel, Novel.find(slug) assert_equal novel, novelist.novels.find(slug) end end end class City < ActiveRecord::Base has_many :restaurants end class Restaurant < ActiveRecord::Base extend FriendlyId belongs_to :city friendly_id :name, :use => [:scoped, :history], :scope => :city end class ScopedHistoryTest < TestCaseClass include FriendlyId::Test include FriendlyId::Test::Shared::Core def model_class Restaurant end test "should find old scoped slugs" do transaction do city = City.create! with_instance_of(Restaurant) do |record| record.city = city record.name = "x" record.slug = nil record.save! record.name = "y" record.slug = nil record.save! assert_equal city.restaurants.friendly.find("x"), city.restaurants.friendly.find("y") end end end test "should consider old scoped slugs when creating slugs" do transaction do city = City.create! with_instance_of(Restaurant) do |record| record.city = city record.name = "x" record.slug = nil record.save! record.name = "y" record.slug = nil record.save! second_record = model_class.create! :city => city, :name => 'x' assert_match(/x-.+/, second_record.friendly_id) third_record = model_class.create! :city => city, :name => 'y' assert_match(/y-.+/, third_record.friendly_id) end end end test "should record history when scope changes" do transaction do city1 = City.create! city2 = City.create! with_instance_of(Restaurant) do |record| record.name = "x" record.slug = nil record.city = city1 record.save! assert_equal("city_id:#{city1.id}", record.slugs.reload.first.scope) assert_equal("x", record.slugs.reload.first.slug) record.city = city2 record.save! assert_equal("city_id:#{city2.id}", record.slugs.reload.first.scope) record.name = "y" record.slug = nil record.city = city1 record.save! assert_equal("city_id:#{city1.id}", record.slugs.reload.first.scope) assert_equal("y", record.slugs.reload.first.slug) end end end test "should allow equal slugs in different scopes" do transaction do city = City.create! second_city = City.create! record = model_class.create! :city => city, :name => 'x' second_record = model_class.create! :city => second_city, :name => 'x' assert_equal record.slug, second_record.slug end end end friendly_id-5.3.0/test/base_test.rb0000644000004100000410000000371213611022220017304 0ustar www-datawww-datarequire "helper" class CoreTest < TestCaseClass include FriendlyId::Test test "friendly_id can be added using 'extend'" do klass = Class.new(ActiveRecord::Base) do extend FriendlyId end assert klass.respond_to? :friendly_id end test "friendly_id can be added using 'include'" do klass = Class.new(ActiveRecord::Base) do include FriendlyId end assert klass.respond_to? :friendly_id end test "friendly_id should accept a base and a hash" do klass = Class.new(ActiveRecord::Base) do self.abstract_class = true extend FriendlyId friendly_id :foo, :use => :slugged, :slug_column => :bar end assert klass < FriendlyId::Slugged assert_equal :foo, klass.friendly_id_config.base assert_equal :bar, klass.friendly_id_config.slug_column end test "friendly_id should accept a block" do klass = Class.new(ActiveRecord::Base) do self.abstract_class = true extend FriendlyId friendly_id :foo do |config| config.use :slugged config.base = :foo config.slug_column = :bar end end assert klass < FriendlyId::Slugged assert_equal :foo, klass.friendly_id_config.base assert_equal :bar, klass.friendly_id_config.slug_column end test "the block passed to friendly_id should be evaluated before arguments" do klass = Class.new(ActiveRecord::Base) do self.abstract_class = true extend FriendlyId friendly_id :foo do |config| config.base = :bar end end assert_equal :foo, klass.friendly_id_config.base end test "should allow defaults to be set via a block" do begin FriendlyId.defaults do |config| config.base = :foo end klass = Class.new(ActiveRecord::Base) do self.abstract_class = true extend FriendlyId end assert_equal :foo, klass.friendly_id_config.base ensure FriendlyId.instance_variable_set :@defaults, nil end end end friendly_id-5.3.0/README.md0000644000004100000410000001134613611022220015310 0ustar www-datawww-data[![Build Status](https://travis-ci.org/norman/friendly_id.svg)](https://travis-ci.org/norman/friendly_id) [![Code Climate](https://codeclimate.com/github/norman/friendly_id.svg)](https://codeclimate.com/github/norman/friendly_id) [![Inline docs](http://inch-ci.org/github/norman/friendly_id.svg?branch=master)](http://inch-ci.org/github/norman/friendly_id) # FriendlyId **For the most complete, user-friendly documentation, see the [FriendlyId Guide](http://norman.github.io/friendly_id/file.Guide.html).** FriendlyId is the "Swiss Army bulldozer" of slugging and permalink plugins for Active Record. It lets you create pretty URLs and work with human-friendly strings as if they were numeric ids. With FriendlyId, it's easy to make your application use URLs like: http://example.com/states/washington instead of: http://example.com/states/4323454 ## Getting Help Ask questions on [Stack Overflow](http://stackoverflow.com/questions/tagged/friendly-id) using the "friendly-id" tag, and for bugs have a look at [the bug section](https://github.com/norman/friendly_id#bugs) ## FriendlyId Features FriendlyId offers many advanced features, including: * slug history and versioning * i18n * scoped slugs * reserved words * custom slug generators ## Usage Add this line to your application's Gemfile: ```ruby gem 'friendly_id', '~> 5.2.4' # Note: You MUST use 5.0.0 or greater for Rails 4.0+ ``` And then execute: ```shell bundle install ``` Add a `slug` column to the desired table (e.g. `Users`) ```shell rails g migration AddSlugToUsers slug:uniq ``` Generate the friendly configuration file and a new migration ```shell rails generate friendly_id ``` Note: You can delete the `CreateFriendlyIdSlugs` migration if you won't use the slug history feature. ([Read more](https://norman.github.io/friendly_id/FriendlyId/History.html)) Run the migration scripts ```shell rails db:migrate ``` Edit the `app/models/user.rb` file as the following: ```ruby class User < ApplicationRecord extend FriendlyId friendly_id :name, use: :slugged end ``` Edit the `app/controllers/users_controller.rb` file and replace `User.find` by `User.friendly.find` ```ruby class UserController < ApplicationController def show @user = User.friendly.find(params[:id]) end end ``` Now when you create a new user like the following: ```ruby User.create! name: "Joe Schmoe" ``` You can then access the user show page using the URL http://localhost:3000/users/joe-schmoe. If you're adding FriendlyId to an existing app and need to generate slugs for existing users, do this from the console, runner, or add a Rake task: ```ruby User.find_each(&:save) ``` ## Bugs Please report them on the [Github issue tracker](http://github.com/norman/friendly_id/issues) for this project. If you have a bug to report, please include the following information: * **Version information for FriendlyId, Rails and Ruby.** * Full stack trace and error message (if you have them). * Any snippets of relevant model, view or controller code that shows how you are using FriendlyId. If you are able to, it helps even more if you can fork FriendlyId on Github, and add a test that reproduces the error you are experiencing. For more inspiration on how to report bugs, please see [this article](https://www.chiark.greenend.org.uk/~sgtatham/bugs.html). ## Thanks and Credits FriendlyId was originally created by Norman Clarke and Adrian Mugnolo, with significant help early in its life by Emilio Tagua. It is now maintained by Norman Clarke and Philip Arndt. We're deeply grateful for the generous contributions over the years from [many volunteers](https://github.com/norman/friendly_id/contributors). ## License Copyright (c) 2008-2016 Norman Clarke and contributors, released under the MIT license. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. friendly_id-5.3.0/gemfiles/0000755000004100000410000000000013611022220015617 5ustar www-datawww-datafriendly_id-5.3.0/gemfiles/Gemfile.rails-5.0.rb0000644000004100000410000000103313611022220021122 0ustar www-datawww-datasource 'https://rubygems.org' gemspec path: '../' gem 'activerecord', '~> 5.0.0' gem 'railties', '~> 5.0.0' gem 'i18n', '~> 0.7.0' # Database Configuration group :development, :test do platforms :jruby do gem 'activerecord-jdbcmysql-adapter', '~> 50.1' gem 'activerecord-jdbcpostgresql-adapter', '~> 50.1' gem 'kramdown' end platforms :ruby, :rbx do gem 'sqlite3' gem 'mysql2' gem 'pg' gem 'redcarpet' end platforms :rbx do gem 'rubysl', '~> 2.0' gem 'rubinius-developer_tools' end end friendly_id-5.3.0/gemfiles/Gemfile.rails-5.1.rb0000644000004100000410000000100413611022220021121 0ustar www-datawww-datasource 'https://rubygems.org' gemspec path: '../' gem 'activerecord', '~> 5.1.0' gem 'railties', '~> 5.1.0' # Database Configuration group :development, :test do platforms :jruby do gem 'activerecord-jdbcmysql-adapter', '~> 50.1' gem 'activerecord-jdbcpostgresql-adapter', '~> 50.1' gem 'kramdown' end platforms :ruby, :rbx do gem 'sqlite3' gem 'mysql2' gem 'pg' gem 'redcarpet' end platforms :rbx do gem 'rubysl', '~> 2.0' gem 'rubinius-developer_tools' end end friendly_id-5.3.0/gemfiles/Gemfile.rails-4.1.rb0000644000004100000410000000114713611022220021130 0ustar www-datawww-datasource 'https://rubygems.org' gemspec path: '../' gem 'activerecord', '~> 4.1.9' gem 'railties', '~> 4.1.9' # Database Configuration group :development, :test do platforms :jruby do gem 'activerecord-jdbcsqlite3-adapter', '~> 1.3.14' gem 'activerecord-jdbcmysql-adapter', '~> 1.3.14' gem 'activerecord-jdbcpostgresql-adapter', '~> 1.3.14' gem 'kramdown' end platforms :ruby, :rbx do gem 'sqlite3' gem 'mysql2', '~> 0.3.13' gem 'pg', '~> 0.15' gem 'redcarpet' end platforms :rbx do gem 'rubysl', '~> 2.0' gem 'rubinius-developer_tools' gem 'json' end end friendly_id-5.3.0/gemfiles/Gemfile.rails-6.0.rb0000644000004100000410000000100413611022220021121 0ustar www-datawww-datasource 'https://rubygems.org' gemspec path: '../' gem 'activerecord', '~> 6.0.0' gem 'railties', '~> 6.0.0' # Database Configuration group :development, :test do platforms :jruby do gem 'activerecord-jdbcmysql-adapter', '~> 51.1' gem 'activerecord-jdbcpostgresql-adapter', '~> 51.1' gem 'kramdown' end platforms :ruby, :rbx do gem 'sqlite3' gem 'mysql2' gem 'pg' gem 'redcarpet' end platforms :rbx do gem 'rubysl', '~> 2.0' gem 'rubinius-developer_tools' end end friendly_id-5.3.0/gemfiles/Gemfile.rails-5.2.rb0000644000004100000410000000100413611022220021122 0ustar www-datawww-datasource 'https://rubygems.org' gemspec path: '../' gem 'activerecord', '~> 5.2.0' gem 'railties', '~> 5.2.0' # Database Configuration group :development, :test do platforms :jruby do gem 'activerecord-jdbcmysql-adapter', '~> 51.1' gem 'activerecord-jdbcpostgresql-adapter', '~> 51.1' gem 'kramdown' end platforms :ruby, :rbx do gem 'sqlite3' gem 'mysql2' gem 'pg' gem 'redcarpet' end platforms :rbx do gem 'rubysl', '~> 2.0' gem 'rubinius-developer_tools' end end friendly_id-5.3.0/gemfiles/Gemfile.rails-4.0.rb0000644000004100000410000000120413611022220021121 0ustar www-datawww-datasource 'https://rubygems.org' gemspec path: '../' gem 'activerecord', '~> 4.0.13' gem 'railties', '~> 4.0.13' gem 'minitest', '~> 4.5.0' # Database Configuration group :development, :test do platforms :jruby do gem 'activerecord-jdbcsqlite3-adapter', '~> 1.3.14' gem 'activerecord-jdbcmysql-adapter', '~> 1.3.14' gem 'activerecord-jdbcpostgresql-adapter', '~> 1.3.14' gem 'kramdown' end platforms :ruby, :rbx do gem 'sqlite3' gem 'mysql2', '~> 0.3.10' gem 'pg', '~> 0.15' gem 'redcarpet' end platforms :rbx do gem 'rubysl', '~> 2.0' gem 'rubinius-developer_tools' gem 'json' end end friendly_id-5.3.0/gemfiles/Gemfile.rails-4.2.rb0000644000004100000410000000106713611022220021132 0ustar www-datawww-datasource 'https://rubygems.org' gemspec path: '../' gem 'activerecord', '~> 4.2.1' gem 'railties', '~> 4.2.1' gem 'i18n', '~> 0.7.0' # Database Configuration group :development, :test do platforms :jruby do gem 'activerecord-jdbcmysql-adapter', '~> 1.3.14' gem 'activerecord-jdbcpostgresql-adapter', '~> 1.3.14' gem 'kramdown' end platforms :ruby, :rbx do gem 'sqlite3' gem 'mysql2', '~> 0.4.10' gem 'pg', '~> 0.15' gem 'redcarpet' end platforms :rbx do gem 'rubysl', '~> 2.0' gem 'rubinius-developer_tools' end end friendly_id-5.3.0/.gitignore0000644000004100000410000000015513611022220016015 0ustar www-datawww-dataGemfile.lock doc docs pkg .DS_Store coverage .yardoc *.gem *.sqlite3 *.rbc *.lock .rbx Guide.md .friendly_id friendly_id-5.3.0/guide.rb0000755000004100000410000000111613611022220015450 0ustar www-datawww-data#!/usr/bin/env ruby # This script generates the Guide.md file included in the Yard docs. def comments_from path path = File.expand_path("../lib/friendly_id/#{path}", __FILE__) match = File.read(path).match(/\n=begin(.*)\n=end/m)[1].to_s match.split("\n").reject {|x| x =~ /^@/}.join("\n").strip end File.open(File.expand_path('../Guide.md', __FILE__), 'w:utf-8') do |guide| ['../friendly_id.rb', 'base.rb', 'finders.rb', 'slugged.rb', 'history.rb', 'scoped.rb', 'simple_i18n.rb', 'reserved.rb'].each do |file| guide.write comments_from file guide.write "\n" end end friendly_id-5.3.0/.gemtest0000644000004100000410000000000013611022220015463 0ustar www-datawww-datafriendly_id-5.3.0/Rakefile0000644000004100000410000000505513611022220015476 0ustar www-datawww-datarequire "rubygems" require "rake/testtask" task :default => :test task :load_path do %w(lib test).each do |path| $LOAD_PATH.unshift(File.expand_path("../#{path}", __FILE__)) end end Rake::TestTask.new do |t| t.libs << "test" t.test_files = FileList['test/*_test.rb'] t.verbose = true end desc "Remove temporary files" task :clean do %x{rm -rf *.gem doc pkg coverage} %x{rm -f `find . -name '*.rbc'`} end desc "Build the gem" task :gem do %x{gem build friendly_id.gemspec} end desc "Build YARD documentation" task :yard do puts %x{bundle exec yard} end desc "Run benchmarks" task :bench => :load_path do require File.expand_path("../bench", __FILE__) end desc "Run benchmarks on finders" task :bench_finders => :load_path do require File.expand_path("../test/benchmarks/finders", __FILE__) end desc "Run benchmarks on ObjectUtils" task :bench_object_utils => :load_path do require File.expand_path("../test/benchmarks/object_utils", __FILE__) end desc "Generate Guide.md" task :guide do load File.expand_path('../guide.rb', __FILE__) end namespace :test do desc "Run each test class in a separate process" task :isolated do dir = File.expand_path("../test", __FILE__) Dir["#{dir}/*_test.rb"].each do |test| puts "Running #{test}:" puts %x{ruby -Ilib -Itest #{test}} end end end namespace :db do desc "Create the database" task :create => :load_path do require "helper" driver = FriendlyId::Test::Database.driver config = FriendlyId::Test::Database.config[driver] commands = { "mysql" => "mysql -u #{config['username']} -e 'create database #{config["database"]};' >/dev/null", "postgres" => "psql -c 'create database #{config['database']};' -U #{config['username']} >/dev/null" } %x{#{commands[driver] || true}} end desc "Drop the database" task :drop => :load_path do require "helper" driver = FriendlyId::Test::Database.driver config = FriendlyId::Test::Database.config[driver] commands = { "mysql" => "mysql -u #{config['username']} -e 'drop database #{config["database"]};' >/dev/null", "postgres" => "psql -c 'drop database #{config['database']};' -U #{config['username']} >/dev/null" } %x{#{commands[driver] || true}} end desc "Set up the database schema" task :up => :load_path do require "helper" FriendlyId::Test::Schema.up end desc "Drop and recreate the database schema" task :reset => [:drop, :create] end task :doc => :yard task :docs do sh %{git checkout gh-pages && rake doc && git checkout @{-1}} end friendly_id-5.3.0/lib/0000755000004100000410000000000013611022220014572 5ustar www-datawww-datafriendly_id-5.3.0/lib/generators/0000755000004100000410000000000013611022220016743 5ustar www-datawww-datafriendly_id-5.3.0/lib/generators/friendly_id_generator.rb0000644000004100000410000000212413611022220023625 0ustar www-datawww-datarequire 'rails/generators' require "rails/generators/active_record" # This generator adds a migration for the {FriendlyId::History # FriendlyId::History} addon. class FriendlyIdGenerator < ActiveRecord::Generators::Base # ActiveRecord::Generators::Base inherits from Rails::Generators::NamedBase which requires a NAME parameter for the # new table name. Our generator always uses 'friendly_id_slugs', so we just set a random name here. argument :name, type: :string, default: 'random_name' class_option :'skip-migration', :type => :boolean, :desc => "Don't generate a migration for the slugs table" class_option :'skip-initializer', :type => :boolean, :desc => "Don't generate an initializer" source_root File.expand_path('../../friendly_id', __FILE__) # Copies the migration template to db/migrate. def copy_files return if options['skip-migration'] migration_template 'migration.rb', 'db/migrate/create_friendly_id_slugs.rb' end def create_initializer return if options['skip-initializer'] copy_file 'initializer.rb', 'config/initializers/friendly_id.rb' end end friendly_id-5.3.0/lib/friendly_id.rb0000644000004100000410000000771613611022220017422 0ustar www-datawww-data# encoding: utf-8 require 'active_record' require "friendly_id/base" require "friendly_id/object_utils" require "friendly_id/configuration" require "friendly_id/finder_methods" =begin ## About FriendlyId FriendlyId is an add-on to Ruby's Active Record that allows you to replace ids in your URLs with strings: # without FriendlyId http://example.com/states/4323454 # with FriendlyId http://example.com/states/washington It requires few changes to your application code and offers flexibility, performance and a well-documented codebase. ### Core Concepts #### Slugs The concept of *slugs* is at the heart of FriendlyId. A slug is the part of a URL which identifies a page using human-readable keywords, rather than an opaque identifier such as a numeric id. This can make your application more friendly both for users and search engines. #### Finders: Slugs Act Like Numeric IDs To the extent possible, FriendlyId lets you treat text-based identifiers like normal IDs. This means that you can perform finds with slugs just like you do with numeric ids: Person.find(82542335) Person.friendly.find("joe") =end module FriendlyId autoload :History, "friendly_id/history" autoload :Slug, "friendly_id/slug" autoload :SimpleI18n, "friendly_id/simple_i18n" autoload :Reserved, "friendly_id/reserved" autoload :Scoped, "friendly_id/scoped" autoload :Slugged, "friendly_id/slugged" autoload :Finders, "friendly_id/finders" autoload :SequentiallySlugged, "friendly_id/sequentially_slugged" # FriendlyId takes advantage of `extended` to do basic model setup, primarily # extending {FriendlyId::Base} to add {FriendlyId::Base#friendly_id # friendly_id} as a class method. # # Previous versions of FriendlyId simply patched ActiveRecord::Base, but this # version tries to be less invasive. # # In addition to adding {FriendlyId::Base#friendly_id friendly_id}, the class # instance variable +@friendly_id_config+ is added. This variable is an # instance of an anonymous subclass of {FriendlyId::Configuration}. This # allows subsequently loaded modules like {FriendlyId::Slugged} and # {FriendlyId::Scoped} to add functionality to the configuration class only # for the current class, rather than monkey patching # {FriendlyId::Configuration} directly. This isolates other models from large # feature changes an addon to FriendlyId could potentially introduce. # # The upshot of this is, you can have two Active Record models that both have # a @friendly_id_config, but each config object can have different methods # and behaviors depending on what modules have been loaded, without # conflicts. Keep this in mind if you're hacking on FriendlyId. # # For examples of this, see the source for {Scoped.included}. def self.extended(model_class) return if model_class.respond_to? :friendly_id class << model_class alias relation_without_friendly_id relation end model_class.class_eval do extend Base @friendly_id_config = Class.new(Configuration).new(self) FriendlyId.defaults.call @friendly_id_config include Model end end # Allow developers to `include` FriendlyId or `extend` it. def self.included(model_class) model_class.extend self end # Set global defaults for all models using FriendlyId. # # The default defaults are to use the `:reserved` module and nothing else. # # @example # FriendlyId.defaults do |config| # config.base :name # config.use :slugged # end def self.defaults(&block) @defaults = block if block_given? @defaults ||= ->(config) {config.use :reserved} end # Set the ActiveRecord table name prefix to friendly_id_ # # This makes 'slugs' into 'friendly_id_slugs' and also respects any # 'global' table_name_prefix set on ActiveRecord::Base. def self.table_name_prefix "#{ActiveRecord::Base.table_name_prefix}friendly_id_" end end friendly_id-5.3.0/lib/friendly_id/0000755000004100000410000000000013611022220017062 5ustar www-datawww-datafriendly_id-5.3.0/lib/friendly_id/history.rb0000644000004100000410000001015613611022220021113 0ustar www-datawww-datamodule FriendlyId =begin ## History: Avoiding 404's When Slugs Change FriendlyId's {FriendlyId::History History} module adds the ability to store a log of a model's slugs, so that when its friendly id changes, it's still possible to perform finds by the old id. The primary use case for this is avoiding broken URLs. ### Setup In order to use this module, you must add a table to your database schema to store the slug records. FriendlyId provides a generator for this purpose: rails generate friendly_id rake db:migrate This will add a table named `friendly_id_slugs`, used by the {FriendlyId::Slug} model. ### Considerations Because recording slug history requires creating additional database records, this module has an impact on the performance of the associated model's `create` method. ### Example class Post < ActiveRecord::Base extend FriendlyId friendly_id :title, :use => :history end class PostsController < ApplicationController before_filter :find_post ... def find_post @post = Post.friendly.find params[:id] # If an old id or a numeric id was used to find the record, then # the request path will not match the post_path, and we should do # a 301 redirect that uses the current friendly id. if request.path != post_path(@post) return redirect_to @post, :status => :moved_permanently end end end =end module History module Configuration def dependent_value dependent.nil? ? :destroy : dependent end end def self.setup(model_class) model_class.instance_eval do friendly_id_config.use :slugged friendly_id_config.class.send :include, History::Configuration friendly_id_config.finder_methods = FriendlyId::History::FinderMethods FriendlyId::Finders.setup(model_class) if friendly_id_config.uses? :finders end end # Configures the model instance to use the History add-on. def self.included(model_class) model_class.class_eval do has_many :slugs, -> {order(id: :desc)}, { :as => :sluggable, :dependent => @friendly_id_config.dependent_value, :class_name => Slug.to_s } after_save :create_slug end end module FinderMethods include ::FriendlyId::FinderMethods def exists_by_friendly_id?(id) super || joins(:slugs).where(slug_history_clause(id)).exists? end private def first_by_friendly_id(id) super || slug_table_record(id) end def slug_table_record(id) select(quoted_table_name + '.*').joins(:slugs).where(slug_history_clause(id)).order(Slug.arel_table[:id].desc).first end def slug_history_clause(id) Slug.arel_table[:sluggable_type].eq(base_class.to_s).and(Slug.arel_table[:slug].eq(id)) end end private # If we're updating, don't consider historic slugs for the same record # to be conflicts. This will allow a record to revert to a previously # used slug. def scope_for_slug_generator relation = super.joins(:slugs) unless new_record? relation = relation.merge(Slug.where('sluggable_id <> ?', id)) end if friendly_id_config.uses?(:scoped) relation = relation.where(Slug.arel_table[:scope].eq(serialized_scope)) end relation end def create_slug return unless friendly_id return if history_is_up_to_date? # Allow reversion back to a previously used slug relation = slugs.where(:slug => friendly_id) if friendly_id_config.uses?(:scoped) relation = relation.where(:scope => serialized_scope) end relation.delete_all slugs.create! do |record| record.slug = friendly_id record.scope = serialized_scope if friendly_id_config.uses?(:scoped) end end def history_is_up_to_date? latest_history = slugs.first check = latest_history.try(:slug) == friendly_id if friendly_id_config.uses?(:scoped) check = check && latest_history.scope == serialized_scope end check end end end friendly_id-5.3.0/lib/friendly_id/scoped.rb0000644000004100000410000001212713611022220020667 0ustar www-datawww-datarequire "friendly_id/slugged" module FriendlyId =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 =end module Scoped # FriendlyId::Config.use will invoke this method when present, to allow # loading dependent modules prior to overriding them when necessary. def self.setup(model_class) model_class.friendly_id_config.use :slugged end # Sets up behavior and configuration options for FriendlyId's scoped slugs # feature. def self.included(model_class) model_class.class_eval do friendly_id_config.class.send :include, Configuration end end def serialized_scope friendly_id_config.scope_columns.sort.map { |column| "#{column}:#{send(column)}" }.join(",") end 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 private :scope_for_slug_generator def slug_generator friendly_id_config.slug_generator_class.new(scope_for_slug_generator, friendly_id_config) end private :slug_generator def should_generate_new_friendly_id? (changed & friendly_id_config.scope_columns).any? || super end # This module adds the `:scope` configuration option to # {FriendlyId::Configuration FriendlyId::Configuration}. module Configuration # Gets the scope value. # # When setting this value, the argument should be a symbol referencing a # `belongs_to` relation, or a column. # # @return Symbol The scope value attr_accessor :scope # Gets the scope columns. # # Checks to see if the `:scope` option passed to # {FriendlyId::Base#friendly_id} refers to a relation, and if so, returns # the realtion's foreign key. Otherwise it assumes the option value was # the name of column and returns it cast to a String. # # @return String The scope column def scope_columns [@scope].flatten.map { |s| (reflection_foreign_key(s) or s).to_s } end private def reflection_foreign_key(scope) reflection = model_class.reflections[scope] || model_class.reflections[scope.to_s] reflection.try(:foreign_key) end end end end friendly_id-5.3.0/lib/friendly_id/version.rb0000644000004100000410000000006113611022220021071 0ustar www-datawww-datamodule FriendlyId VERSION = '5.3.0'.freeze end friendly_id-5.3.0/lib/friendly_id/slug.rb0000644000004100000410000000046713611022220020370 0ustar www-datawww-datamodule FriendlyId # A FriendlyId slug stored in an external table. # # @see FriendlyId::History class Slug < ActiveRecord::Base belongs_to :sluggable, :polymorphic => true def sluggable sluggable_type.constantize.unscoped { super } end def to_param slug end end end friendly_id-5.3.0/lib/friendly_id/finder_methods.rb0000644000004100000410000000450413611022220022404 0ustar www-datawww-datamodule FriendlyId module FinderMethods # Finds a record using the given id. # # If the id is "unfriendly", it will call the original find method. # If the id is a numeric string like '123' it will first look for a friendly # id matching '123' and then fall back to looking for a record with the # numeric id '123'. # # Since FriendlyId 5.0, if the id is a nonnumeric string like '123-foo' it # will *only* search by friendly id and not fall back to the regular find # method. # # If you want to search only by the friendly id, use {#find_by_friendly_id}. # @raise ActiveRecord::RecordNotFound def find(*args) id = args.first return super if args.count != 1 || id.unfriendly_id? first_by_friendly_id(id).tap {|result| return result unless result.nil?} return super if potential_primary_key?(id) raise_not_found_exception id end # Returns true if a record with the given id exists. def exists?(conditions = :none) return super if conditions.unfriendly_id? return true if exists_by_friendly_id?(conditions) super end # Finds exclusively by the friendly id, completely bypassing original # `find`. # @raise ActiveRecord::RecordNotFound def find_by_friendly_id(id) first_by_friendly_id(id) or raise raise_not_found_exception(id) end def exists_by_friendly_id?(id) where(friendly_id_config.query_field => id).exists? end private def potential_primary_key?(id) key_type = primary_key_type # Hook for "ActiveModel::Type::Integer" instance. key_type = key_type.type if key_type.respond_to?(:type) case key_type when :integer Integer(id, 10) rescue false when :uuid id.match(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/) else true end end def first_by_friendly_id(id) find_by(friendly_id_config.query_field => id) end def raise_not_found_exception(id) message = "can't find record with friendly id: #{id.inspect}" if ActiveRecord.version < Gem::Version.create('5.0') then raise ActiveRecord::RecordNotFound.new(message) else raise ActiveRecord::RecordNotFound.new(message, name, friendly_id_config.query_field, id) end end end end friendly_id-5.3.0/lib/friendly_id/finders.rb0000644000004100000410000000636713611022220021055 0ustar www-datawww-datamodule FriendlyId =begin ## Performing Finds with FriendlyId FriendlyId offers enhanced finders which will search for your record by friendly id, and fall back to the numeric id if necessary. This makes it easy to add FriendlyId to an existing application with minimal code modification. By default, these methods are available only on the `friendly` scope: Restaurant.friendly.find('plaza-diner') #=> works Restaurant.friendly.find(23) #=> also works Restaurant.find(23) #=> still works Restaurant.find('plaza-diner') #=> will not work ### Restoring FriendlyId 4.0-style finders Prior to version 5.0, FriendlyId overrode the default finder methods to perform friendly finds all the time. This required modifying parts of Rails that did not have a public API, which was harder to maintain and at times caused compatiblity problems. In 5.0 we decided to change the library's defaults and add the friendly finder methods only to the `friendly` scope in order to boost compatiblity. However, you can still opt-in to original functionality very easily by using the `:finders` addon: class Restaurant < ActiveRecord::Base extend FriendlyId scope :active, -> {where(:active => true)} friendly_id :name, :use => [:slugged, :finders] end Restaurant.friendly.find('plaza-diner') #=> works Restaurant.find('plaza-diner') #=> now also works Restaurant.active.find('plaza-diner') #=> now also works ### Updating your application to use FriendlyId's finders Unless you've chosen to use the `:finders` addon, be sure to modify the finders in your controllers to use the `friendly` scope. For example: # before def set_restaurant @restaurant = Restaurant.find(params[:id]) end # after def set_restaurant @restaurant = Restaurant.friendly.find(params[:id]) end #### Active Admin Unless you use the `:finders` addon, you should modify your admin controllers for models that use FriendlyId with something similar to the following: controller do def find_resource scoped_collection.friendly.find(params[:id]) end end =end module Finders module ClassMethods if (ActiveRecord::VERSION::MAJOR == 4) && (ActiveRecord::VERSION::MINOR == 0) def relation_delegate_class(klass) relation_class_name = :"#{klass.to_s.gsub('::', '_')}_#{self.to_s.gsub('::', '_')}" klass.const_get(relation_class_name) end end end def self.setup(model_class) model_class.instance_eval do relation.class.send(:include, friendly_id_config.finder_methods) if (ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR == 2) || ActiveRecord::VERSION::MAJOR >= 5 model_class.send(:extend, friendly_id_config.finder_methods) end end # Support for friendly finds on associations for Rails 4.0.1 and above. if ::ActiveRecord.const_defined?('AssociationRelation') model_class.extend(ClassMethods) association_relation_delegate_class = model_class.relation_delegate_class(::ActiveRecord::AssociationRelation) association_relation_delegate_class.send(:include, model_class.friendly_id_config.finder_methods) end end end end friendly_id-5.3.0/lib/friendly_id/base.rb0000644000004100000410000002536113611022220020330 0ustar www-datawww-datamodule FriendlyId =begin ## Setting Up FriendlyId in Your Model To use FriendlyId in your ActiveRecord models, you must first either extend or include the FriendlyId module (it makes no difference), then invoke the {FriendlyId::Base#friendly_id friendly_id} method to configure your desired options: class Foo < ActiveRecord::Base include FriendlyId friendly_id :bar, :use => [:slugged, :simple_i18n] end The most important option is `:use`, which you use to tell FriendlyId which addons it should use. See the documentation for {FriendlyId::Base#friendly_id} for a list of all available addons, or skim through the rest of the docs to get a high-level overview. *A note about single table inheritance (STI): you must extend FriendlyId in all classes that participate in STI, both your parent classes and their children.* ### The Default Setup: Simple Models The simplest way to use FriendlyId is with a model that has a uniquely indexed column with no spaces or special characters, and that is seldom or never updated. The most common example of this is a user name: class User < ActiveRecord::Base extend FriendlyId friendly_id :login validates_format_of :login, :with => /\A[a-z0-9]+\z/i end @user = User.friendly.find "joe" # the old User.find(1) still works, too @user.to_param # returns "joe" redirect_to @user # the URL will be /users/joe In this case, FriendlyId assumes you want to use the column as-is; it will never modify the value of the column, and your application should ensure that the value is unique and admissible in a URL: class City < ActiveRecord::Base extend FriendlyId friendly_id :name end @city.friendly.find "Viña del Mar" redirect_to @city # the URL will be /cities/Viña%20del%20Mar Writing the code to process an arbitrary string into a good identifier for use in a URL can be repetitive and surprisingly tricky, so for this reason it's often better and easier to use {FriendlyId::Slugged slugs}. =end module Base # Configure FriendlyId's behavior in a model. # # class Post < ActiveRecord::Base # extend FriendlyId # friendly_id :title, :use => :slugged # end # # When given the optional block, this method will yield the class's instance # of {FriendlyId::Configuration} to the block before evaluating other # arguments, so configuration values set in the block may be overwritten by # the arguments. This order was chosen to allow passing the same proc to # multiple models, while being able to override the values it sets. Here is # a contrived example: # # $friendly_id_config_proc = Proc.new do |config| # config.base = :name # config.use :slugged # end # # class Foo < ActiveRecord::Base # extend FriendlyId # friendly_id &$friendly_id_config_proc # end # # class Bar < ActiveRecord::Base # extend FriendlyId # friendly_id :title, &$friendly_id_config_proc # end # # However, it's usually better to use {FriendlyId.defaults} for this: # # FriendlyId.defaults do |config| # config.base = :name # config.use :slugged # end # # class Foo < ActiveRecord::Base # extend FriendlyId # end # # class Bar < ActiveRecord::Base # extend FriendlyId # friendly_id :title # end # # In general you should use the block syntax either because of your personal # aesthetic preference, or because you need to share some functionality # between multiple models that can't be well encapsulated by # {FriendlyId.defaults}. # # ### Order Method Calls in a Block vs Ordering Options # # When calling this method without a block, you may set the hash options in # any order. # # However, when using block-style invocation, be sure to call # FriendlyId::Configuration's {FriendlyId::Configuration#use use} method # *prior* to the associated configuration options, because it will include # modules into your class, and these modules in turn may add required # configuration options to the `@friendly_id_configuraton`'s class: # # class Person < ActiveRecord::Base # friendly_id do |config| # # This will work # config.use :slugged # config.sequence_separator = ":" # end # end # # class Person < ActiveRecord::Base # friendly_id do |config| # # This will fail # config.sequence_separator = ":" # config.use :slugged # end # end # # ### Including Your Own Modules # # Because :use can accept a name or a Module, {FriendlyId.defaults defaults} # can be a convenient place to set up behavior common to all classes using # FriendlyId. You can include any module, or more conveniently, define one # on-the-fly. For example, let's say you want to make # [Babosa](http://github.com/norman/babosa) the default slugging library in # place of Active Support, and transliterate all slugs from Russian Cyrillic # to ASCII: # # require "babosa" # # FriendlyId.defaults do |config| # config.base = :name # config.use :slugged # config.use Module.new { # def normalize_friendly_id(text) # text.to_slug.normalize! :transliterations => [:russian, :latin] # end # } # end # # # @option options [Symbol,Module] :use The addon or name of an addon to use. # By default, FriendlyId provides {FriendlyId::Slugged :slugged}, # {FriendlyId::History :history}, {FriendlyId::Reserved :reserved}, and # {FriendlyId::Scoped :scoped}, and {FriendlyId::SimpleI18n :simple_i18n}. # # @option options [Array] :reserved_words Available when using `:reserved`, # which is loaded by default. Sets an array of words banned for use as # the basis of a friendly_id. By default this includes "edit" and "new". # # @option options [Symbol] :scope Available when using `:scoped`. # Sets the relation or column used to scope generated friendly ids. This # option has no default value. # # @option options [Symbol] :sequence_separator Available when using `:slugged`. # Configures the sequence of characters used to separate a slug from a # sequence. Defaults to `-`. # # @option options [Symbol] :slug_column Available when using `:slugged`. # Configures the name of the column where FriendlyId will store the slug. # Defaults to `:slug`. # # @option options [Integer] :slug_limit Available when using `:slugged`. # Configures the limit of the slug. This option has no default value. # # @option options [Symbol] :slug_generator_class Available when using `:slugged`. # Sets the class used to generate unique slugs. You should not specify this # unless you're doing some extensive hacking on FriendlyId. Defaults to # {FriendlyId::SlugGenerator}. # # @yield Provides access to the model class's friendly_id_config, which # allows an alternate configuration syntax, and conditional configuration # logic. # # @option options [Symbol,Boolean] :dependent Available when using `:history`. # Sets the value used for the slugged association's dependent option. Use # `false` if you do not want to dependently destroy the associated slugged # record. Defaults to `:destroy`. # # @option options [Symbol] :routes When set to anything other than :friendly, # ensures that all routes generated by default do *not* use the slug. This # allows `form_for` and `polymorphic_path` to continue to generate paths like # `/team/1` instead of `/team/number-one`. You can still generate paths # like the latter using: team_path(team.slug). When set to :friendly, or # omitted, the default friendly_id behavior is maintained. # # @yieldparam config The model class's {FriendlyId::Configuration friendly_id_config}. def friendly_id(base = nil, options = {}, &block) yield friendly_id_config if block_given? friendly_id_config.dependent = options.delete :dependent friendly_id_config.use options.delete :use friendly_id_config.send :set, base ? options.merge(:base => base) : options include Model end # Returns a scope that includes the friendly finders. # @see FriendlyId::FinderMethods def friendly # Guess what? This causes Rails to invoke `extend` on the scope, which has # the well-known effect of blowing away Ruby's method cache. It would be # possible to make this more performant by subclassing the model's # relation class, extending that, and returning an instance of it in this # method. FriendlyId 4.0 did something similar. However in 5.0 I've # decided to only use Rails's public API in order to improve compatibility # and maintainability. If you'd like to improve the performance, your # efforts would be best directed at improving it at the root cause # of the problem - in Rails - because it would benefit more people. all.extending(friendly_id_config.finder_methods) end # Returns the model class's {FriendlyId::Configuration friendly_id_config}. # @note In the case of Single Table Inheritance (STI), this method will # duplicate the parent class's FriendlyId::Configuration and relation class # on first access. If you're concerned about thread safety, then be sure # to invoke {#friendly_id} in your class for each model. def friendly_id_config @friendly_id_config ||= base_class.friendly_id_config.dup.tap do |config| config.model_class = self end end def primary_key_type @primary_key_type ||= columns_hash[primary_key].type end end # Instance methods that will be added to all classes using FriendlyId. module Model def self.included(model_class) return if model_class.respond_to?(:friendly) end # Convenience method for accessing the class method of the same name. def friendly_id_config self.class.friendly_id_config end # Get the instance's friendly_id. def friendly_id send friendly_id_config.query_field end # Either the friendly_id, or the numeric id cast to a string. def to_param if friendly_id_config.routes == :friendly friendly_id.presence.to_param || super else super end end # Clears slug on duplicate records when calling `dup`. def dup super.tap { |duplicate| duplicate.slug = nil if duplicate.respond_to?('slug=') } end end end friendly_id-5.3.0/lib/friendly_id/candidates.rb0000644000004100000410000000320513611022220021506 0ustar www-datawww-datarequire 'securerandom' module FriendlyId # This class provides the slug candidate functionality. # @see FriendlyId::Slugged class Candidates include Enumerable def initialize(object, *array) @object = object @raw_candidates = to_candidate_array(object, array.flatten(1)) end def each(*args, &block) return candidates unless block_given? candidates.each{ |candidate| yield candidate } end private def candidates @candidates ||= begin candidates = normalize(@raw_candidates) filter(candidates) end end def normalize(candidates) candidates.map do |candidate| @object.normalize_friendly_id(candidate.map(&:call).join(' ')) end.select {|x| wanted?(x)} end def filter(candidates) unless candidates.all? {|x| reserved?(x)} candidates.reject! {|x| reserved?(x)} end candidates end def to_candidate_array(object, array) array.map do |candidate| case candidate when String [->{candidate}] when Array to_candidate_array(object, candidate).flatten when Symbol [object.method(candidate)] else if candidate.respond_to?(:call) [candidate] else [->{candidate.to_s}] end end end end def wanted?(slug) slug.present? end def reserved?(slug) config = @object.friendly_id_config return false unless config.uses? ::FriendlyId::Reserved return false unless config.reserved_words config.reserved_words.include?(slug) end end end friendly_id-5.3.0/lib/friendly_id/reserved.rb0000644000004100000410000000315313611022220021230 0ustar www-datawww-datamodule FriendlyId =begin ## Reserved Words The {FriendlyId::Reserved Reserved} module adds the ability to exclude a list of words from use as FriendlyId slugs. With Ruby on Rails, FriendlyId's generator generates an initializer that reserves some words such as "new" and "edit" using {FriendlyId.defaults FriendlyId.defaults}. Note that the error messages for fields will appear on the field `:friendly_id`. If you are using Rails's scaffolded form errors display, then it will have no field to highlight. If you'd like to change this so that scaffolding works as expected, one way to accomplish this is to move the error message to a different field. For example: class Person < ActiveRecord::Base extend FriendlyId friendly_id :name, use: :slugged after_validation :move_friendly_id_error_to_name def move_friendly_id_error_to_name errors.add :name, *errors.delete(:friendly_id) if errors[:friendly_id].present? end end =end module Reserved # When included, this module adds configuration options to the model class's # friendly_id_config. def self.included(model_class) model_class.class_eval do friendly_id_config.class.send :include, Reserved::Configuration validates_exclusion_of :friendly_id, :in => ->(_) { friendly_id_config.reserved_words || [] } end end # This module adds the `:reserved_words` configuration option to # {FriendlyId::Configuration FriendlyId::Configuration}. module Configuration attr_accessor :reserved_words attr_accessor :treat_reserved_as_conflict end end end friendly_id-5.3.0/lib/friendly_id/.gitattributes0000644000004100000410000000002613611022220021753 0ustar www-datawww-dataversion.rb merge=ours friendly_id-5.3.0/lib/friendly_id/configuration.rb0000644000004100000410000000663213611022220022265 0ustar www-datawww-datamodule FriendlyId # The configuration parameters passed to {Base#friendly_id} will be stored in # this object. class Configuration attr_writer :base # The default configuration options. attr_reader :defaults # The modules in use attr_reader :modules # The model class that this configuration belongs to. # @return ActiveRecord::Base attr_accessor :model_class # The module to use for finders attr_accessor :finder_methods # The value used for the slugged association's dependent option attr_accessor :dependent # Route generation preferences attr_accessor :routes def initialize(model_class, values = nil) @model_class = model_class @defaults = {} @modules = [] @finder_methods = FriendlyId::FinderMethods self.routes = :friendly set values end # Lets you specify the addon modules to use with FriendlyId. # # This method is invoked by {FriendlyId::Base#friendly_id friendly_id} when # passing the `:use` option, or when using {FriendlyId::Base#friendly_id # friendly_id} with a block. # # @example # class Book < ActiveRecord::Base # extend FriendlyId # friendly_id :name, :use => :slugged # end # # @param [#to_s,Module] modules Arguments should be Modules, or symbols or # strings that correspond with the name of an addon to use with FriendlyId. # By default FriendlyId provides `:slugged`, `:history`, `:simple_i18n`, # and `:scoped`. def use(*modules) modules.to_a.flatten.compact.map do |object| mod = get_module(object) mod.setup(@model_class) if mod.respond_to?(:setup) @model_class.send(:include, mod) unless uses? object end end # Returns whether the given module is in use. def uses?(mod) @model_class < get_module(mod) end # The column that FriendlyId will use to find the record when querying by # friendly id. # # This method is generally only used internally by FriendlyId. # @return String def query_field base.to_s end # The base column or method used by FriendlyId as the basis of a friendly id # or slug. # # For models that don't use {FriendlyId::Slugged}, this is the column that # is used to store the friendly id. For models using {FriendlyId::Slugged}, # the base is a column or method whose value is used as the basis of the # slug. # # For example, if you have a model representing blog posts and that uses # slugs, you likely will want to use the "title" attribute as the base, and # FriendlyId will take care of transforming the human-readable title into # something suitable for use in a URL. # # If you pass an argument, it will be used as the base. Otherwise the current # value is returned. # # @param value A symbol referencing a column or method in the model. This # value is usually set by passing it as the first argument to # {FriendlyId::Base#friendly_id friendly_id}. def base(*value) if value.empty? @base else self.base = value.first end end private def get_module(object) Module === object ? object : FriendlyId.const_get(object.to_s.titleize.camelize.gsub(/\s+/, '')) end def set(values) values and values.each {|name, value| self.send "#{name}=", value} end end end friendly_id-5.3.0/lib/friendly_id/object_utils.rb0000644000004100000410000000454313611022220022103 0ustar www-datawww-datamodule FriendlyId # Instances of these classes will never be considered a friendly id. # @see FriendlyId::ObjectUtils#friendly_id UNFRIENDLY_CLASSES = [ Array, FalseClass, Hash, NilClass, Numeric, Symbol, TrueClass ] # Utility methods for determining whether any object is a friendly id. # # Monkey-patching Object is a somewhat extreme measure not to be taken lightly # by libraries, but in this case I decided to do it because to me, it feels # cleaner than adding a module method to {FriendlyId}. I've given the methods # names that unambigously refer to the library of their origin, which should # be sufficient to avoid conflicts with other libraries. module ObjectUtils # True if the id is definitely friendly, false if definitely unfriendly, # else nil. # # An object is considired "definitely unfriendly" if its class is or # inherits from ActiveRecord::Base, Array, Hash, NilClass, Numeric, or # Symbol. # # An object is considered "definitely friendly" if it responds to +to_i+, # and its value when cast to an integer and then back to a string is # different from its value when merely cast to a string: # # 123.friendly_id? #=> false # :id.friendly_id? #=> false # {:name => 'joe'}.friendly_id? #=> false # ['name = ?', 'joe'].friendly_id? #=> false # nil.friendly_id? #=> false # "123".friendly_id? #=> nil # "abc123".friendly_id? #=> true def friendly_id? true if respond_to?(:to_i) && to_i.to_s != to_s end # True if the id is definitely unfriendly, false if definitely friendly, # else nil. def unfriendly_id? val = friendly_id? ; !val unless val.nil? end end module UnfriendlyUtils def friendly_id? false end def unfriendly_id? true end end def self.mark_as_unfriendly(klass) klass.send(:include, FriendlyId::UnfriendlyUtils) end end Object.send :include, FriendlyId::ObjectUtils # Considered unfriendly if object is an instance of an unfriendly class or # one of its descendants. FriendlyId::UNFRIENDLY_CLASSES.each { |klass| FriendlyId.mark_as_unfriendly(klass) } ActiveSupport.on_load(:active_record) do FriendlyId.mark_as_unfriendly(ActiveRecord::Base) end friendly_id-5.3.0/lib/friendly_id/migration.rb0000644000004100000410000000145713611022220021407 0ustar www-datawww-dataMIGRATION_CLASS = if ActiveRecord::VERSION::MAJOR >= 5 ActiveRecord::Migration["#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"] else ActiveRecord::Migration end class CreateFriendlyIdSlugs < MIGRATION_CLASS def change create_table :friendly_id_slugs do |t| t.string :slug, :null => false t.integer :sluggable_id, :null => false t.string :sluggable_type, :limit => 50 t.string :scope t.datetime :created_at end add_index :friendly_id_slugs, [:sluggable_type, :sluggable_id] add_index :friendly_id_slugs, [:slug, :sluggable_type], length: { slug: 140, sluggable_type: 50 } add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], length: { slug: 70, sluggable_type: 50, scope: 70 }, unique: true end end friendly_id-5.3.0/lib/friendly_id/slugged.rb0000644000004100000410000003412613611022220021047 0ustar www-datawww-data# encoding: utf-8 require "friendly_id/slug_generator" require "friendly_id/candidates" module FriendlyId =begin ## Slugged Models FriendlyId can use a separate column to store slugs for models which require some text processing. For example, blog applications typically use a post title to provide the basis of a search engine friendly URL. Such identifiers typically lack uppercase characters, use ASCII to approximate UTF-8 characters, and strip out other characters which may make them aesthetically unappealing or error-prone when used in a URL. class Post < ActiveRecord::Base extend FriendlyId friendly_id :title, :use => :slugged end @post = Post.create(:title => "This is the first post!") @post.friendly_id # returns "this-is-the-first-post" redirect_to @post # the URL will be /posts/this-is-the-first-post In general, use slugs by default unless you know for sure you don't need them. To activate the slugging functionality, use the {FriendlyId::Slugged} module. FriendlyId will generate slugs from a method or column that you specify, and store them in a field in your model. By default, this field must be named `:slug`, though you may change this using the {FriendlyId::Slugged::Configuration#slug_column slug_column} configuration option. You should add an index to this column, and in most cases, make it unique. You may also wish to constrain it to NOT NULL, but this depends on your app's behavior and requirements. ### Example Setup # your model class Post < ActiveRecord::Base extend FriendlyId friendly_id :title, :use => :slugged validates_presence_of :title, :slug, :body end # a migration class CreatePosts < ActiveRecord::Migration def self.up create_table :posts do |t| t.string :title, :null => false t.string :slug, :null => false t.text :body end add_index :posts, :slug, :unique => true end def self.down drop_table :posts end end ### Working With Slugs #### Formatting By default, FriendlyId uses Active Support's [paramaterize](http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize) method to create slugs. This method will intelligently replace spaces with dashes, and Unicode Latin characters with ASCII approximations: movie = Movie.create! :title => "Der Preis fürs Überleben" movie.slug #=> "der-preis-furs-uberleben" #### Column or Method? FriendlyId always uses a method as the basis of the slug text - not a column. At first glance, this may sound confusing, but remember that Active Record provides methods for each column in a model's associated table, and that's what FriendlyId uses. Here's an example of a class that uses a custom method to generate the slug: class Person < ActiveRecord::Base extend FriendlyId friendly_id :name_and_location, use: :slugged def name_and_location "#{name} from #{location}" end end bob = Person.create! :name => "Bob Smith", :location => "New York City" bob.friendly_id #=> "bob-smith-from-new-york-city" FriendlyId refers to this internally as the "base" method. #### Uniqueness When you try to insert a record that would generate a duplicate friendly id, FriendlyId will append a UUID to the generated slug to ensure uniqueness: car = Car.create :title => "Peugeot 206" car2 = Car.create :title => "Peugeot 206" car.friendly_id #=> "peugeot-206" car2.friendly_id #=> "peugeot-206-f9f3789a-daec-4156-af1d-fab81aa16ee5" Previous versions of FriendlyId appended a numeric sequence to make slugs unique, but this was removed to simplify using FriendlyId in concurrent code. #### Candidates Since UUIDs are ugly, FriendlyId provides a "slug candidates" functionality to let you specify alternate slugs to use in the event the one you want to use is already taken. For example: class Restaurant < ActiveRecord::Base extend FriendlyId friendly_id :slug_candidates, use: :slugged # Try building a slug based on the following fields in # increasing order of specificity. def slug_candidates [ :name, [:name, :city], [:name, :street, :city], [:name, :street_number, :street, :city] ] end end r1 = Restaurant.create! name: 'Plaza Diner', city: 'New Paltz' r2 = Restaurant.create! name: 'Plaza Diner', city: 'Kingston' r1.friendly_id #=> 'plaza-diner' r2.friendly_id #=> 'plaza-diner-kingston' To use candidates, make your FriendlyId base method return an array. The method need not be named `slug_candidates`; it can be anything you want. The array may contain any combination of symbols, strings, procs or lambdas and will be evaluated lazily and in order. If you include symbols, FriendlyId will invoke a method on your model class with the same name. Strings will be interpreted literally. Procs and lambdas will be called and their return values used as the basis of the friendly id. If none of the candidates can generate a unique slug, then FriendlyId will append a UUID to the first candidate as a last resort. #### Sequence Separator By default, FriendlyId uses a dash to separate the slug from a sequence. You can change this with the {FriendlyId::Slugged::Configuration#sequence_separator sequence_separator} configuration option. #### Providing Your Own Slug Processing Method You can override {FriendlyId::Slugged#normalize_friendly_id} in your model for total control over the slug format. It will be invoked for any generated slug, whether for a single slug or for slug candidates. #### Deciding When to Generate New Slugs As of FriendlyId 5.0, slugs are only generated when the `slug` field is nil. If you want a slug to be regenerated,set the slug field to nil: restaurant.friendly_id # joes-diner restaurant.name = "The Plaza Diner" restaurant.save! restaurant.friendly_id # joes-diner restaurant.slug = nil restaurant.save! restaurant.friendly_id # the-plaza-diner You can also override the {FriendlyId::Slugged#should_generate_new_friendly_id?} method, which lets you control exactly when new friendly ids are set: class Post < ActiveRecord::Base extend FriendlyId friendly_id :title, :use => :slugged def should_generate_new_friendly_id? title_changed? end end If you want to extend the default behavior but add your own conditions, don't forget to invoke `super` from your implementation: class Category < ActiveRecord::Base extend FriendlyId friendly_id :name, :use => :slugged def should_generate_new_friendly_id? name_changed? || super end end #### Locale-specific Transliterations Active Support's `parameterize` uses [transliterate](http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-transliterate), which in turn can use I18n's transliteration rules to consider the current locale when replacing Latin characters: # config/locales/de.yml de: i18n: transliterate: rule: ü: "ue" ö: "oe" etc... movie = Movie.create! :title => "Der Preis fürs Überleben" movie.slug #=> "der-preis-fuers-ueberleben" This functionality was in fact taken from earlier versions of FriendlyId. #### Gotchas: Common Problems FriendlyId uses a before_validation callback to generate and set the slug. This means that if you create two model instances before saving them, it's possible they will generate the same slug, and the second save will fail. This can happen in two fairly normal cases: the first, when a model using nested attributes creates more than one record for a model that uses friendly_id. The second, in concurrent code, either in threads or multiple processes. To solve the nested attributes issue, I recommend simply avoiding them when creating more than one nested record for a model that uses FriendlyId. See [this Github issue](https://github.com/norman/friendly_id/issues/185) for discussion. =end module Slugged # Sets up behavior and configuration options for FriendlyId's slugging # feature. def self.included(model_class) model_class.friendly_id_config.instance_eval do self.class.send :include, Configuration self.slug_generator_class ||= SlugGenerator defaults[:slug_column] ||= 'slug' defaults[:sequence_separator] ||= '-' end model_class.before_validation :set_slug model_class.after_validation :unset_slug_if_invalid end # Process the given value to make it suitable for use as a slug. # # This method is not intended to be invoked directly; FriendlyId uses it # internally to process strings into slugs. # # However, if FriendlyId's default slug generation doesn't suit your needs, # you can override this method in your model class to control exactly how # slugs are generated. # # ### Example # # class Person < ActiveRecord::Base # extend FriendlyId # friendly_id :name_and_location # # def name_and_location # "#{name} from #{location}" # end # # # Use default slug, but upper case and with underscores # def normalize_friendly_id(string) # super.upcase.gsub("-", "_") # end # end # # bob = Person.create! :name => "Bob Smith", :location => "New York City" # bob.friendly_id #=> "BOB_SMITH_FROM_NEW_YORK_CITY" # # ### More Resources # # You might want to look into Babosa[https://github.com/norman/babosa], # which is the slugging library used by FriendlyId prior to version 4, which # offers some specialized functionality missing from Active Support. # # @param [#to_s] value The value used as the basis of the slug. # @return The candidate slug text, without a sequence. def normalize_friendly_id(value) value = value.to_s.parameterize value = value[0...friendly_id_config.slug_limit] if friendly_id_config.slug_limit value end # Whether to generate a new slug. # # You can override this method in your model if, for example, you only want # slugs to be generated once, and then never updated. def should_generate_new_friendly_id? send(friendly_id_config.slug_column).nil? && !send(friendly_id_config.base).nil? end # Public: Resolve conflicts. # # This method adds UUID to first candidate and truncates (if `slug_limit` is set). # # Examples: # # resolve_friendly_id_conflict(['12345']) # # => '12345-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' # # FriendlyId.defaults { |config| config.slug_limit = 40 } # resolve_friendly_id_conflict(['12345']) # # => '123-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' # # candidates - the Array with candidates. # # Returns the String with new slug. def resolve_friendly_id_conflict(candidates) uuid = SecureRandom.uuid [ apply_slug_limit(candidates.first, uuid), uuid ].compact.join(friendly_id_config.sequence_separator) end # Private: Apply slug limit to candidate. # # candidate - the String with candidate. # uuid - the String with UUID. # # Return the String with truncated candidate. def apply_slug_limit(candidate, uuid) return candidate unless candidate && friendly_id_config.slug_limit candidate[0...candidate_limit(uuid)] end private :apply_slug_limit # Private: Get max length of candidate. # # uuid - the String with UUID. # # Returns the Integer with max length. def candidate_limit(uuid) [ friendly_id_config.slug_limit - uuid.size - friendly_id_config.sequence_separator.size, 0 ].max end private :candidate_limit # Sets the slug. def set_slug(normalized_slug = nil) if should_generate_new_friendly_id? candidates = FriendlyId::Candidates.new(self, normalized_slug || send(friendly_id_config.base)) slug = slug_generator.generate(candidates) || resolve_friendly_id_conflict(candidates) send "#{friendly_id_config.slug_column}=", slug end end private :set_slug def scope_for_slug_generator scope = self.class.base_class.unscoped scope = scope.friendly unless scope.respond_to?(:exists_by_friendly_id?) primary_key_name = self.class.primary_key scope.where(self.class.base_class.arel_table[primary_key_name].not_eq(send(primary_key_name))) end private :scope_for_slug_generator def slug_generator friendly_id_config.slug_generator_class.new(scope_for_slug_generator, friendly_id_config) end private :slug_generator def unset_slug_if_invalid if errors.present? && attribute_changed?(friendly_id_config.query_field.to_s) diff = changes[friendly_id_config.query_field] send "#{friendly_id_config.slug_column}=", diff.first end end private :unset_slug_if_invalid # This module adds the `:slug_column`, and `:slug_limit`, and `:sequence_separator`, # and `:slug_generator_class` configuration options to # {FriendlyId::Configuration FriendlyId::Configuration}. module Configuration attr_writer :slug_column, :slug_limit, :sequence_separator attr_accessor :slug_generator_class # Makes FriendlyId use the slug column for querying. # @return String The slug column. def query_field slug_column end # The string used to separate a slug base from a numeric sequence. # # You can change the default separator by setting the # {FriendlyId::Slugged::Configuration#sequence_separator # sequence_separator} configuration option. # @return String The sequence separator string. Defaults to "`-`". def sequence_separator @sequence_separator ||= defaults[:sequence_separator] end # The column that will be used to store the generated slug. def slug_column @slug_column ||= defaults[:slug_column] end # The limit that will be used for slug. def slug_limit @slug_limit ||= defaults[:slug_limit] end end end end friendly_id-5.3.0/lib/friendly_id/initializer.rb0000644000004100000410000000766013611022220021743 0ustar www-datawww-data# FriendlyId Global Configuration # # Use this to set up shared configuration options for your entire application. # Any of the configuration options shown here can also be applied to single # models by passing arguments to the `friendly_id` class method or defining # methods in your model. # # To learn more, check out the guide: # # http://norman.github.io/friendly_id/file.Guide.html FriendlyId.defaults do |config| # ## Reserved Words # # Some words could conflict with Rails's routes when used as slugs, or are # undesirable to allow as slugs. Edit this list as needed for your app. config.use :reserved config.reserved_words = %w(new edit index session login logout users admin stylesheets assets javascripts images) # This adds an option to treat reserved words as conflicts rather than exceptions. # When there is no good candidate, a UUID will be appended, matching the existing # conflict behavior. # config.treat_reserved_as_conflict = true # ## Friendly Finders # # Uncomment this to use friendly finders in all models. By default, if # you wish to find a record by its friendly id, you must do: # # MyModel.friendly.find('foo') # # If you uncomment this, you can do: # # MyModel.find('foo') # # This is significantly more convenient but may not be appropriate for # all applications, so you must explicity opt-in to this behavior. You can # always also configure it on a per-model basis if you prefer. # # Something else to consider is that using the :finders addon boosts # performance because it will avoid Rails-internal code that makes runtime # calls to `Module.extend`. # # config.use :finders # # ## Slugs # # Most applications will use the :slugged module everywhere. If you wish # to do so, uncomment the following line. # # config.use :slugged # # By default, FriendlyId's :slugged addon expects the slug column to be named # 'slug', but you can change it if you wish. # # config.slug_column = 'slug' # # By default, slug has no size limit, but you can change it if you wish. # # config.slug_limit = 255 # # When FriendlyId can not generate a unique ID from your base method, it appends # a UUID, separated by a single dash. You can configure the character used as the # separator. If you're upgrading from FriendlyId 4, you may wish to replace this # with two dashes. # # config.sequence_separator = '-' # # Note that you must use the :slugged addon **prior** to the line which # configures the sequence separator, or else FriendlyId will raise an undefined # method error. # # ## Tips and Tricks # # ### Controlling when slugs are generated # # As of FriendlyId 5.0, new slugs are generated only when the slug field is # nil, but if you're using a column as your base method can change this # behavior by overriding the `should_generate_new_friendly_id?` method that # FriendlyId adds to your model. The change below makes FriendlyId 5.0 behave # more like 4.0. # Note: Use(include) Slugged module in the config if using the anonymous module. # If you have `friendly_id :name, use: slugged` in the model, Slugged module # is included after the anonymous module defined in the initializer, so it # overrides the `should_generate_new_friendly_id?` method from the anonymous module. # # config.use :slugged # config.use Module.new { # def should_generate_new_friendly_id? # slug.blank? || _changed? # end # } # # FriendlyId uses Rails's `parameterize` method to generate slugs, but for # languages that don't use the Roman alphabet, that's not usually sufficient. # Here we use the Babosa library to transliterate Russian Cyrillic slugs to # ASCII. If you use this, don't forget to add "babosa" to your Gemfile. # # config.use Module.new { # def normalize_friendly_id(text) # text.to_slug.normalize! :transliterations => [:russian, :latin] # end # } end friendly_id-5.3.0/lib/friendly_id/simple_i18n.rb0000644000004100000410000000541513611022220021544 0ustar www-datawww-datarequire "i18n" module FriendlyId =begin ## Translating Slugs Using Simple I18n The {FriendlyId::SimpleI18n SimpleI18n} module adds very basic i18n support to FriendlyId. In order to use this module, your model must have a slug column for each locale. By default FriendlyId looks for columns named, for example, "slug_en", "slug_es", etc. The first part of the name can be configured by passing the `:slug_column` option if you choose. Note that the column for the default locale must also include the locale in its name. This module is most suitable to applications that need to support few locales. If you need to support two or more locales, you may wish to use the friendly_id_globalize gem instead. ### Example migration def self.up create_table :posts do |t| t.string :title t.string :slug_en t.string :slug_es t.text :body end add_index :posts, :slug_en add_index :posts, :slug_es end ### Finds Finds will take into consideration the current locale: I18n.locale = :es Post.friendly.find("la-guerra-de-las-galaxias") I18n.locale = :en Post.friendly.find("star-wars") To find a slug by an explicit locale, perform the find inside a block passed to I18n's `with_locale` method: I18n.with_locale(:es) do Post.friendly.find("la-guerra-de-las-galaxias") end ### Creating Records When new records are created, the slug is generated for the current locale only. ### Translating Slugs To translate an existing record's friendly_id, use {FriendlyId::SimpleI18n::Model#set_friendly_id}. This will ensure that the slug you add is properly escaped, transliterated and sequenced: post = Post.create :name => "Star Wars" post.set_friendly_id("La guerra de las galaxias", :es) If you don't pass in a locale argument, FriendlyId::SimpleI18n will just use the current locale: I18n.with_locale(:es) do post.set_friendly_id("La guerra de las galaxias") end =end module SimpleI18n # FriendlyId::Config.use will invoke this method when present, to allow # loading dependent modules prior to overriding them when necessary. def self.setup(model_class) model_class.friendly_id_config.use :slugged end def self.included(model_class) model_class.class_eval do friendly_id_config.class.send :include, Configuration include Model end end module Model def set_friendly_id(text, locale = nil) I18n.with_locale(locale || I18n.locale) do set_slug(normalize_friendly_id(text)) end end def slug=(value) super write_attribute friendly_id_config.slug_column, value end end module Configuration def slug_column "#{super}_#{I18n.locale}" end end end end friendly_id-5.3.0/lib/friendly_id/sequentially_slugged.rb0000644000004100000410000000561013611022220023642 0ustar www-datawww-datamodule FriendlyId module SequentiallySlugged def self.setup(model_class) model_class.friendly_id_config.use :slugged end def resolve_friendly_id_conflict(candidate_slugs) candidate = candidate_slugs.first return if candidate.nil? SequentialSlugCalculator.new(scope_for_slug_generator, candidate, friendly_id_config.slug_column, friendly_id_config.sequence_separator, slug_base_class).next_slug end class SequentialSlugCalculator attr_accessor :scope, :slug, :slug_column, :sequence_separator def initialize(scope, slug, slug_column, sequence_separator, base_class) @scope = scope @slug = slug table_name = scope.connection.quote_table_name(base_class.arel_table.name) @slug_column = "#{table_name}.#{scope.connection.quote_column_name(slug_column)}" @sequence_separator = sequence_separator end def next_slug slug + sequence_separator + next_sequence_number.to_s end private def next_sequence_number last_sequence_number ? last_sequence_number + 1 : 2 end def last_sequence_number regexp = /#{slug}#{sequence_separator}(\d+)\z/ # Reject slug_conflicts that doesn't come from the first_candidate # Map all sequence numbers and take the maximum slug_conflicts.reject{ |slug_conflict| !regexp.match(slug_conflict) }.map do |slug_conflict| regexp.match(slug_conflict)[1].to_i end.max end def slug_conflicts scope. where(conflict_query, slug, sequential_slug_matcher). order(Arel.sql(ordering_query)).pluck(Arel.sql(slug_column)) end def conflict_query base = "#{slug_column} = ? OR #{slug_column} LIKE ?" # Awful hack for SQLite3, which does not pick up '\' as the escape character # without this. base << " ESCAPE '\\'" if scope.connection.adapter_name =~ /sqlite/i base end def sequential_slug_matcher # Underscores (matching a single character) and percent signs (matching # any number of characters) need to be escaped. While this looks like # an excessive number of backslashes, it is correct. "#{slug}#{sequence_separator}".gsub(/[_%]/, '\\\\\&') + '%' end # Return the unnumbered (shortest) slug first, followed by the numbered ones # in ascending order. def ordering_query length_command = "LENGTH" length_command = "LEN" if scope.connection.adapter_name =~ /sqlserver/i "#{length_command}(#{slug_column}) ASC, #{slug_column} ASC" end end private def slug_base_class if friendly_id_config.uses?(:history) Slug else self.class.base_class end end end end friendly_id-5.3.0/lib/friendly_id/slug_generator.rb0000644000004100000410000000114113611022220022424 0ustar www-datawww-datamodule FriendlyId # The default slug generator offers functionality to check slug candidates for # availability. class SlugGenerator def initialize(scope, config) @scope = scope @config = config end def available?(slug) if @config.uses?(::FriendlyId::Reserved) && @config.reserved_words.present? && @config.treat_reserved_as_conflict return false if @config.reserved_words.include?(slug) end !@scope.exists_by_friendly_id?(slug) end def generate(candidates) candidates.each {|c| return c if available?(c)} nil end end end friendly_id-5.3.0/UPGRADING.md0000644000004100000410000001112513611022220015666 0ustar www-datawww-data## Articles * [Migrating an ad-hoc URL slug system to FriendlyId](http://olivierlacan.com/posts/migrating-an-ad-hoc-url-slug-system-to-friendly-id/) * [Pretty URLs with FriendlyId](http://railscasts.com/episodes/314-pretty-urls-with-friendlyid) ## Docs The most current docs from the master branch can always be found [here](http://norman.github.io/friendly_id). Docs for older versions are also available: * [5.0](http://norman.github.io/friendly_id/5.0/) * [4.0](http://norman.github.io/friendly_id/4.0/) * [3.3](http://norman.github.io/friendly_id/3.3/) * [2.3](http://norman.github.io/friendly_id/2.3/) ## What Changed in Version 5.1 5.1 is a bugfix release, but bumps the minor version because some applications may be dependent on the previously buggy behavior. The changes include: * Blank strings can no longer be used as slugs. * When the first slug candidate is rejected because it is reserved, additional candidates will now be considered before marking the record as invalid. * The `:finders` module is now compatible with Rails 4.2. ## What Changed in Version 5.0 As of version 5.0, FriendlyId uses [semantic versioning](http://semver.org/). Therefore, as you might infer from the version number, 5.0 introduces changes incompatible with 4.0. The most important changes are: * Finders are no longer overridden by default. If you want to do friendly finds, you must do `Model.friendly.find` rather than `Model.find`. You can however restore FriendlyId 4-style finders by using the `:finders` addon: ```ruby friendly_id :foo, use: :slugged # you must do MyClass.friendly.find('bar') # or... friendly_id :foo, use: [:slugged, :finders] # you can now do MyClass.find('bar') ``` * A new "candidates" functionality which makes it easy to set up a list of alternate slugs that can be used to uniquely distinguish records, rather than appending a sequence. For example: ```ruby class Restaurant < ActiveRecord::Base extend FriendlyId friendly_id :slug_candidates, use: :slugged # Try building a slug based on the following fields in # increasing order of specificity. def slug_candidates [ :name, [:name, :city], [:name, :street, :city], [:name, :street_number, :street, :city] ] end end ``` * Now that candidates have been added, FriendlyId no longer uses a numeric sequence to differentiate conflicting slug, but rather a UUID (e.g. something like `2bc08962-b3dd-4f29-b2e6-244710c86106`). This makes the codebase simpler and more reliable when running concurrently, at the expense of uglier ids being generated when there are conflicts. * The default sequence separator has been changed from two dashes to one dash. * Slugs are no longer regenerated when a record is saved. If you want to regenerate a slug, you must explicitly set the slug column to nil: ```ruby restaurant.friendly_id # joes-diner restaurant.name = "The Plaza Diner" restaurant.save! restaurant.friendly_id # joes-diner restaurant.slug = nil restaurant.save! restaurant.friendly_id # the-plaza-diner ``` You can restore some of the old behavior by overriding the `should_generate_new_friendly_id?` method. * The `friendly_id` Rails generator now generates an initializer showing you how to do some common global configuration. * The Globalize plugin has moved to a [separate gem](https://github.com/norman/friendly_id-globalize) (currently in alpha). * The `:reserved` module no longer includes any default reserved words. Previously it blocked "edit" and "new" everywhere. The default word list has been moved to `config/initializers/friendly_id.rb` and now includes many more words. * The `:history` and `:scoped` addons can now be used together. * Since it now requires Rails 4, FriendlyId also now requires Ruby 1.9.3 or higher. ## Upgrading from FriendlyId 4.0 Run `rails generate friendly_id --skip-migration` and edit the initializer generated in `config/initializers/friendly_id.rb`. This file contains notes describing how to restore (or not) some of the defaults from FriendlyId 4.0. If you want to use the `:history` and `:scoped` addons together, you must add a `:scope` column to your friendly_id_slugs table and replace the unique index on `:slug` and `:sluggable_type` with a unique index on those two columns, plus the new `:scope` column. A migration like this should be sufficient: ```ruby add_column :friendly_id_slugs, :scope, :string remove_index :friendly_id_slugs, [:slug, :sluggable_type] add_index :friendly_id_slugs, [:slug, :sluggable_type] add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], unique: true ``` friendly_id-5.3.0/CONTRIBUTING.md0000644000004100000410000000063413611022220016260 0ustar www-datawww-data# FriendlyId Please ask questions on [Stack Overflow](http://stackoverflow.com/questions/tagged/friendly-id) using the "friendly_id" or "friendly-id" tag. Prior to asking, search and see if your question has already been answered. Please only post issues in Github issues for actual bugs. I am asking people to do this because the same questions keep getting asked over and over and over again in the issues. friendly_id-5.3.0/.yardopts0000644000004100000410000000016613611022220015675 0ustar www-datawww-data-e guide.rb --files=Changelog.md,Guide.md --private --protected --exclude lib/friendly_id/migration --markup=markdown friendly_id-5.3.0/Gemfile0000644000004100000410000000062113611022220015316 0ustar www-datawww-datasource 'https://rubygems.org' gemspec group :development, :test do platforms :ruby do gem 'byebug' gem 'pry' end platforms :jruby do gem 'activerecord-jdbcsqlite3-adapter', '>= 1.3.0.beta2' gem 'kramdown' end platforms :ruby, :rbx do gem 'sqlite3' gem 'redcarpet' end platforms :rbx do gem 'rubysl', '~> 2.0' gem 'rubinius-developer_tools' end end friendly_id-5.3.0/MIT-LICENSE0000644000004100000410000000211013611022220015452 0ustar www-datawww-dataCopyright (c) 2008-2016 Norman Clarke, Adrian Mugnolo and Emilio Tagua. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. friendly_id-5.3.0/friendly_id.gemspec0000644000004100000410000000242213611022220017661 0ustar www-datawww-data# encoding: utf-8 require File.expand_path("../lib/friendly_id/version", __FILE__) Gem::Specification.new do |s| s.name = "friendly_id" s.version = FriendlyId::VERSION s.authors = ["Norman Clarke", "Philip Arndt"] s.email = ["norman@njclarke.com", "p@arndt.io"] s.homepage = "https://github.com/norman/friendly_id" s.summary = "A comprehensive slugging and pretty-URL plugin." s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test}/*`.split("\n") s.require_paths = ["lib"] s.license = 'MIT' s.required_ruby_version = '>= 1.9.3' s.add_dependency 'activerecord', '>= 4.0.0' s.add_development_dependency 'coveralls' s.add_development_dependency 'railties', '>= 4.0' s.add_development_dependency 'minitest', '~> 5.3' s.add_development_dependency 'mocha', '~> 1.1' s.add_development_dependency 'yard' s.add_development_dependency 'i18n' s.add_development_dependency 'ffaker' s.add_development_dependency 'simplecov' s.description = <<-EOM FriendlyId is the "Swiss Army bulldozer" of slugging and permalink plugins for Active Record. It lets you create pretty URLs and work with human-friendly strings as if they were numeric ids. EOM end friendly_id-5.3.0/Changelog.md0000644000004100000410000002637213611022220016247 0ustar www-datawww-data# FriendlyId Changelog We would like to think our many [contributors](https://github.com/norman/friendly_id/graphs/contributors) for suggestions, ideas and improvements to FriendlyId. ## 5.3.0 (2019-09-25) * Record history when scope changes but slug does not ([#916](https://github.com/norman/friendly_id/pull/916)) * Add support for Rails 6 ([#897](https://github.com/norman/friendly_id/pull/897)) ## 5.2.5 (2018-12-30) * Pass all possible parameters to ActiveRecord::RecordNotFound.new when raising the exception ([#890](https://github.com/norman/friendly_id/pull/890)) * Use composite index for queries by sluggable ([#882](https://github.com/norman/friendly_id/pull/882)) * Scoped: generate new slug if scope changed ([#878](https://github.com/norman/friendly_id/pull/878)) * Fix History + SequentiallySlugged issues ([#877](https://github.com/norman/friendly_id/pull/877)) * Support scoped with STI ([#745](https://github.com/norman/friendly_id/pull/745)) * Fix exists? to behave the same as find for numeric slugs ([#875](https://github.com/norman/friendly_id/pull/875)) * Remove dirty tracking code from to_param ([#867](https://github.com/norman/friendly_id/pull/867)) ## 5.2.4 (2018-04-24) * Fix compatibility with Rails versions 4.0 -> 5.2. ([#863](https://github.com/norman/friendly_id/pull/863)). * Refactor `History::FinderMethods` to use base implementation. ([#853](https://github.com/norman/friendly_id/pull/853)). * Defer loading of ActiveRecord to avoid config issues. ([#852](https://github.com/norman/friendly_id/pull/852)). * Ensure compatibility with paranoid deletion libraries. ([#838](https://github.com/norman/friendly_id/pull/838)). * Add treat_reserved_as_conflict option to initializer ([#847](https://github.com/norman/friendly_id/pull/847)). ## 5.2.3 (2017-09-22) * Added option to treat reserved words as conflicts ([#831](https://github.com/norman/friendly_id/pull/831)). ## 5.2.2 (2017-09-13) * Prevent warning on db:migrate in Rails 5.1 ([#826](https://github.com/norman/friendly_id/pull/826)). * Allow to set size limit for slug ([#809](https://github.com/norman/friendly_id/pull/809)). * Update specs and drop support for ruby 2.0.0 ([#824](https://github.com/norman/friendly_id/pull/824)). ## 5.2.1 (2017-04-09) * Change ActiveRecord::Base to ApplicationRecord ([#782](https://github.com/norman/friendly_id/pull/782)). * Refactor `Candidates#each` method. ([#773](https://github.com/norman/friendly_id/pull/773)). * Assign to configured slug column, not 'slug' when validation fails. ([#779](https://github.com/norman/friendly_id/pull/779)). * Fix sequential slugs when using History. ([#774](https://github.com/norman/friendly_id/pull/774)). ## 5.2.0 (2016-12-01) * Add sequential slug module for FriendlyId 4.x-style sequential slugs. ([#644](https://github.com/norman/friendly_id/pull/644)). * Make Candidates#each iterable without block ([#651](https://github.com/norman/friendly_id/pull/651)). * Ensure slug history prefers the record that most recently used the slug ([#663](https://github.com/norman/friendly_id/pull/663)). * Don't calculate all changes just to check if the param field has changed ([#667](https://github.com/norman/friendly_id/pull/667)). * Don't set or change slug when unrelated validation failures block the record from being saved ([#642](https://github.com/norman/friendly_id/issues/642)). * Fix order dependence bug between history and finders modules ([#718](https://github.com/norman/friendly_id/pull/718)) * Added ability to conditionally turn off `:dependent => :destroy` on FriendlyId::Slugs([#724](https://github.com/norman/friendly_id/pull/724)) * Add support for Rails 5. ([#728](https://github.com/norman/friendly_id/pull/728)) * Allow per-model conditional disabling of friendly path generation using a :routes option to friendly_id ([#735](https://github.com/norman/friendly_id/pull/735)) ## 5.1.0 (2015-01-15) * FriendlyId will no longer allow blank strings as slugs ([#571](https://github.com/norman/friendly_id/pull/571)). * FriendlyId will now try to use the first non-reserved candidate as its slug and will only mark the record invalid if all candidates ([#536](https://github.com/norman/friendly_id/issues/536)). * Fix order dependence bug between history and scoped modules ([#588](https://github.com/norman/friendly_id/pull/588)). * Fix "friendly" finds on Rails 4.2 ([#607](https://github.com/norman/friendly_id/issues/607)). ## 5.0.4 (2014-05-29) * Bug fix for call to removed `primary` method on Edge Rails. ([#557](https://github.com/norman/friendly_id/pull/557)). * Bug fix for unwanted slug regeneration when the slug source was changed, but not the actual generated slug ([#563](https://github.com/norman/friendly_id/pull/562)). * Big fix to look for UUIDs only at the end of slugs ([#548](https://github.com/norman/friendly_id/pull/548)). * Various documentation and test setup improvements. ## 5.0.3 (2013-02-14) * Bug fix for calls to #dup with unslugged models ([#518](https://github.com/norman/friendly_id/pull/518)). * Bug fixes for STI ([#516](https://github.com/norman/friendly_id/pull/516)). * Bug fix for slug regeneration (both scoped and unscoped) ([#513](https://github.com/norman/friendly_id/pull/513)). * Bug fix for finds with models that use the :history module ([#509](https://github.com/norman/friendly_id/pull/509)). ## 5.0.2 (2013-12-10) * Query performance improvements ([#497](https://github.com/norman/friendly_id/pull/497)). * Documentation improvements (thanks [John Bachir](https://github.com/jjb)). * Minor refactoring of internals (thanks [Gagan Ahwad](https://github.com/gaganawhad)). * Set slug to `nil` on call to `dup` to ensure slug is generated ([#483](https://github.com/norman/friendly_id/pull/483)). ## 5.0.1 (2013-10-27) * Fix compatibility with Rails 4.0.1.rc3 (thanks [Herman verschooten](https://github.com/Hermanverschooten)). ## 5.0.0 (2013-10-16) * Fix to let scoped records reuse their slugs (thanks [Donny Kurnia](https://github.com/donnykurnia)). ## 5.0.0.rc.3 (2013-10-04) * Support friendly finds on associations in Rails 4.0.1 and up. They will currently work on Rails 4.0 associations only if `:inverse_of` is not used. In Rails 4-0-stable, associations have been modified to use a special relation class, giving FriendlyId a consistent extension point. Since the behavior in 4.0.0 is considered defective and fixed in 4-0-stable, FriendlyId 5.0 will not support friendly finds on inverse relelations in 4.0.0. For a reliable workaround, use the `friendly` scope for friendly finds on associations; this works on all Rails 4.0.x versions and will continue to be supported. * Documentation fixes. ## 5.0.0.rc2 (2013-09-29) * When the :finders addon has been included, use it in FriendlyId's internal finds to boost performance. * Use instance methods rather than class methods in migrations. * On find, fall back to super when the primary key is a character type. Thanks to [Jamie Davidson](https://github.com/jhdavids8). * Fix reversion to previously used slug from history table when `should_generate_new_friendly_id?` is overridden. * Fix sequencing of numeric slugs ## 5.0.0.rc1 (2013-08-28) * Removed some outdated tests. * Improved documentation. * Removed Guide from repository and added tasks to maintain docs up to date on Github pages at http://norman.github.io/friendly_id. ## 5.0.0.beta4 (2013-08-21) * Add an initializer to the generator; move the default reserved words there. * Allow assignment from {FriendlyId::Configuration#base}. * Fix bug whereby records could not reuse their own slugs. ## 5.0.0.beta3 (2013-08-20) * Update gemspec to ensure FriendlyId 5.0 is only used with AR 4.0.x. ## 5.0.0.beta2 (2013-08-16) * Add "finders" module to easily restore FriendlyId 4.0 finder behavior. ## 5.0.0.beta1 (2013-08-10) * Support for Rails 4. * Made the :scoped and :history modules compatible with each other (Andre Duffeck). * Removed class-level finders in favor of `friendly` scope (Norman Clarke). * Implemented "candidates" support (Norman Clarke). * Slug "sequences" are now GUIDs rather than numbers (Norman Clarke). * `find` no longer falls back to super unless id is fully numeric string (Norman Clarke). * Default sequence separator is now '-' rather than '--'. * Support for Globalize has been removed until Globalize supports Rails 4. * Removed support for Ruby < 1.9.3 and Rails < 4.0. ## 4.0.10.1 (2013-08-20) * Update dependencies in gemspec to avoid using with Active Record 4. * Fixed links in docs. ## 4.0.10 (2013-08-10) * Fixed table prefixes/suffixes being ignored (Jesse Farless). * Fixed sequence generation for slugs containing numbers (Adam Carroll). ## 4.0.9 (2012-10-31) * Fixed support for Rails 3.2.9.rc1 ## 4.0.8 (2012-08-01) * Name internal anonymous class to fix marshall dump/load error (Jess Brown, Philip Arndt and Norman Clarke). * Avoid using deprecated `update_attribute` (Philip Arndt). * Added set_friendly_id method to Globalize module (Norman Clarke). * autoload FriendlyId::Slug; previously this class was not accessible from migrations unless required explicitly, which could cause some queries to unexpectedly fail (Norman Clarke). * Fix Mocha load order (Mark Turner). * Minor doc updates (Rob Yurkowski). * Other miscellaneous refactorings and doc updates. ## 4.0.7 (2012-06-06) * to_param just calls super when no friendly id is present, to keep the model's default behavior. (Andrew White) * FriendlyId can now properly sequence slugs that end in numbers even when a single dash is used as the separator (Tomás Arribas). ## 4.0.6 (2012-05-21) * Fix nil return value from to_param when save fails because of validation errors (Tomás Arribas) * Fix incorrect usage of i18n API (Vinicius Ferriani) * Improve error handling in reserved module (Adrián Mugnolo and Github user "nolamesa") ## 4.0.5 (2012-04-28) * Favor `includes` over `joins` in globalize to avoid read-only results (Jakub Wojtysiak) * Fix globalize compatibility with results from dynamic finders (Chris Salzberg) ## 4.0.4 (2012-03-26) * Fix globalize plugin to avoid issues with asset precompilation (Philip Arndt) ## 4.0.3 (2012-03-14) * Fix escape for '%' and '_' on SQLite (Norman Clarke and Sergey Petrunin) * Allow FriendlyId to be extended or included (Norman Clarke) * Allow Configuration#use to accept a Module (Norman Clarke) * Fix bugs with History module + STI (Norman Clarke and Sergey Petrunin) ## 4.0.2 (2012-03-12) * Improved conflict handling and performance in History module (Erik Ogan and Thomas Shafer) * Fixed bug that impeded using underscores as a sequence separator (Erik Ogan and Thomas Shafer) * Minor documentation improvements (Norman Clarke) ## 4.0.1 (2012-02-29) * Added support for Globalize 3 (Enrico Pilotto and Philip Arndt) * Allow the scoped module to use multiple scopes (Ben Caldwell) * Fixes for conflicting slugs in history module (Erik Ogan, Thomas Shafer, Evan Arnold) * Fix for conflicting slugs when using STI (Danny van der Heiden, Diederick Lawson) * Maintainence improvements (Norman Clarke, Philip Arndt, Thomas Darde, Lee Hambley) ## 4.0.0 (2011-12-27) This is a complete rewrite of FriendlyId, and introduces a smaller, faster and less ambitious codebase. The primary change is the relegation of external slugs to an optional addon, and the adoption of what were formerly "cached slugs" as the primary way of handling slugging. ## Older releases Please see the 3.x branch.