nwp-friendly_id 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. data/History.txt +133 -0
  2. data/MIT-LICENSE +19 -0
  3. data/Manifest.txt +39 -0
  4. data/README.rdoc +343 -0
  5. data/Rakefile +49 -0
  6. data/config/website.yml +2 -0
  7. data/friendly_id.gemspec +45 -0
  8. data/generators/friendly_id/friendly_id_generator.rb +12 -0
  9. data/generators/friendly_id/templates/create_slugs.rb +18 -0
  10. data/generators/friendly_id_20_upgrade/friendly_id_20_upgrade_generator.rb +11 -0
  11. data/generators/friendly_id_20_upgrade/templates/upgrade_friendly_id_to_20.rb +19 -0
  12. data/init.rb +3 -0
  13. data/lib/friendly_id.rb +101 -0
  14. data/lib/friendly_id/helpers.rb +15 -0
  15. data/lib/friendly_id/non_sluggable_class_methods.rb +42 -0
  16. data/lib/friendly_id/non_sluggable_instance_methods.rb +43 -0
  17. data/lib/friendly_id/slug.rb +102 -0
  18. data/lib/friendly_id/sluggable_class_methods.rb +116 -0
  19. data/lib/friendly_id/sluggable_instance_methods.rb +115 -0
  20. data/lib/friendly_id/version.rb +10 -0
  21. data/lib/tasks/friendly_id.rake +50 -0
  22. data/lib/tasks/friendly_id.rb +1 -0
  23. data/test/contest.rb +94 -0
  24. data/test/custom_slug_normalizer_test.rb +35 -0
  25. data/test/models/book.rb +2 -0
  26. data/test/models/country.rb +4 -0
  27. data/test/models/event.rb +3 -0
  28. data/test/models/novel.rb +3 -0
  29. data/test/models/person.rb +6 -0
  30. data/test/models/post.rb +6 -0
  31. data/test/models/thing.rb +6 -0
  32. data/test/models/user.rb +3 -0
  33. data/test/non_slugged_test.rb +98 -0
  34. data/test/schema.rb +55 -0
  35. data/test/scoped_model_test.rb +53 -0
  36. data/test/slug_test.rb +106 -0
  37. data/test/slugged_model_test.rb +284 -0
  38. data/test/sti_test.rb +48 -0
  39. data/test/test_helper.rb +30 -0
  40. metadata +154 -0
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ require 'newgem'
2
+ require 'lib/friendly_id/version'
3
+
4
+ $hoe = Hoe.new("friendly_id", FriendlyId::Version::STRING) do |p|
5
+ p.rubyforge_name = "friendly-id"
6
+ p.author = ['Norman Clarke', 'Adrian Mugnolo', 'Emilio Tagua']
7
+ p.email = ['norman@rubysouth.com', 'adrian@rubysouth.com', 'miloops@gmail.com']
8
+ p.summary = "A comprehensive slugging and pretty-URL plugin for ActiveRecord."
9
+ p.description = 'A comprehensive slugging and pretty-URL plugin for ActiveRecord.'
10
+ p.url = 'http://friendly-id.rubyforge.org/'
11
+ p.test_globs = ['test/**/*_test.rb']
12
+ p.extra_deps << ['activerecord', '>= 2.0.0']
13
+ p.extra_deps << ['activesupport', '>= 2.0.0']
14
+ p.extra_dev_deps << ['newgem', ">= #{::Newgem::VERSION}"]
15
+ p.extra_dev_deps << ['sqlite3-ruby']
16
+ p.remote_rdoc_dir = ""
17
+ end
18
+
19
+ require 'newgem/tasks'
20
+
21
+ desc "Run RCov"
22
+ task :rcov do
23
+ run_coverage Dir["test/**/*_test.rb"]
24
+ end
25
+
26
+ def run_coverage(files)
27
+ rm_f "coverage"
28
+ rm_f "coverage.data"
29
+ if files.length == 0
30
+ puts "No files were specified for testing"
31
+ return
32
+ end
33
+ files = files.join(" ")
34
+ # if RUBY_PLATFORM =~ /darwin/
35
+ # exclude = '--exclude "gems/"'
36
+ # else
37
+ # exclude = '--exclude "rubygems"'
38
+ # end
39
+ rcov = ENV["RCOV"] ? ENV["RCOV"] : "rcov"
40
+ sh "#{rcov} -Ilib:test --sort coverage --text-report #{files}"
41
+ end
42
+
43
+ desc 'Publish RDoc to RubyForge.'
44
+ task :publish_docs => [:clean, :docs] do
45
+ host = "compay@rubyforge.org"
46
+ remote_dir = "/var/www/gforge-projects/friendly-id"
47
+ local_dir = 'doc'
48
+ sh %{rsync -av --delete #{local_dir}/ #{host}:#{remote_dir}}
49
+ end
@@ -0,0 +1,2 @@
1
+ host: compay@rubyforge.org
2
+ remote_dir: /var/www/gforge-projects/friendly-id
@@ -0,0 +1,45 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{friendly_id}
5
+ s.version = "2.1.3"
6
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
7
+ s.authors = ["Norman Clarke", "Adrian Mugnolo", "Emilio Tagua"]
8
+ s.date = %q{2009-08-15}
9
+ s.description = %q{A comprehensive slugging and pretty-URL plugin for ActiveRecord.}
10
+ s.email = ["norman@rubysouth.com", "adrian@rubysouth.com", "miloops@gmail.com"]
11
+ s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.rdoc"]
12
+ s.files = ["History.txt", "MIT-LICENSE", "Manifest.txt", "README.rdoc", "Rakefile", "config/website.yml", "friendly_id.gemspec", "generators/friendly_id/friendly_id_generator.rb", "generators/friendly_id/templates/create_slugs.rb", "generators/friendly_id_20_upgrade/friendly_id_20_upgrade_generator.rb", "generators/friendly_id_20_upgrade/templates/upgrade_friendly_id_to_20.rb", "init.rb", "lib/friendly_id.rb", "lib/friendly_id/helpers.rb", "lib/friendly_id/non_sluggable_class_methods.rb", "lib/friendly_id/non_sluggable_instance_methods.rb", "lib/friendly_id/slug.rb", "lib/friendly_id/sluggable_class_methods.rb", "lib/friendly_id/sluggable_instance_methods.rb", "lib/friendly_id/version.rb", "lib/tasks/friendly_id.rake", "lib/tasks/friendly_id.rb", "test/contest.rb", "test/custom_slug_normalizer_test.rb", "test/models/book.rb", "test/models/country.rb", "test/models/event.rb", "test/models/novel.rb", "test/models/person.rb", "test/models/post.rb", "test/models/thing.rb", "test/models/user.rb", "test/non_slugged_test.rb", "test/schema.rb", "test/scoped_model_test.rb", "test/slug_test.rb", "test/slugged_model_test.rb", "test/sti_test.rb", "test/test_helper.rb"]
13
+ s.homepage = %q{http://friendly-id.rubyforge.org/}
14
+ s.rdoc_options = ["--main", "README.rdoc"]
15
+ s.require_paths = ["lib"]
16
+ s.rubyforge_project = %q{friendly-id}
17
+ s.rubygems_version = %q{1.3.3}
18
+ s.summary = %q{A comprehensive slugging and pretty-URL plugin for ActiveRecord.}
19
+ s.test_files = ["test/custom_slug_normalizer_test.rb", "test/non_slugged_test.rb", "test/scoped_model_test.rb", "test/slug_test.rb", "test/slugged_model_test.rb", "test/sti_test.rb"]
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
+ s.add_runtime_dependency(%q<activerecord>, [">= 2.0.0"])
27
+ s.add_runtime_dependency(%q<activesupport>, [">= 2.0.0"])
28
+ s.add_development_dependency(%q<newgem>, [">= 1.4.1"])
29
+ s.add_development_dependency(%q<sqlite3-ruby>, [">= 0"])
30
+ s.add_development_dependency(%q<hoe>, [">= 1.8.0"])
31
+ else
32
+ s.add_dependency(%q<activerecord>, [">= 2.0.0"])
33
+ s.add_dependency(%q<activesupport>, [">= 2.0.0"])
34
+ s.add_dependency(%q<newgem>, [">= 1.4.1"])
35
+ s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
36
+ s.add_dependency(%q<hoe>, [">= 1.8.0"])
37
+ end
38
+ else
39
+ s.add_dependency(%q<activerecord>, [">= 2.0.0"])
40
+ s.add_dependency(%q<activesupport>, [">= 2.0.0"])
41
+ s.add_dependency(%q<newgem>, [">= 1.4.1"])
42
+ s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
43
+ s.add_dependency(%q<hoe>, [">= 1.8.0"])
44
+ end
45
+ end
@@ -0,0 +1,12 @@
1
+ class FriendlyIdGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ unless options[:skip_migration]
5
+ m.migration_template(
6
+ 'create_slugs.rb', 'db/migrate', :migration_file_name => 'create_slugs'
7
+ )
8
+ m.file "/../../../lib/tasks/friendly_id.rake", "lib/tasks/friendly_id.rake"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ class CreateSlugs < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :slugs do |t|
4
+ t.string :name
5
+ t.integer :sluggable_id
6
+ t.integer :sequence, :null => false, :default => 1
7
+ t.string :sluggable_type, :limit => 40
8
+ t.string :scope, :limit => 40
9
+ t.datetime :created_at
10
+ end
11
+ add_index :slugs, [:name, :sluggable_type, :scope, :sequence], :unique => true
12
+ add_index :slugs, :sluggable_id
13
+ end
14
+
15
+ def self.down
16
+ drop_table :slugs
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ class FriendlyId20UpgradeGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ unless options[:skip_migration]
5
+ m.migration_template(
6
+ 'upgrade_friendly_id_to_20.rb', 'db/migrate', :migration_file_name => 'upgrade_friendly_id_to_20'
7
+ )
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ class UpgradeFriendlyIdTo20 < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ remove_column :slugs, :updated_at
5
+ remove_index :slugs, :column => [:name, :sluggable_type]
6
+ add_column :slugs, :sequence, :integer, :null => false, :default => 1
7
+ add_column :slugs, :scope, :string, :limit => 40
8
+ add_index :slugs, [:name, :sluggable_type, :scope, :sequence], :unique => true, :name => "index_slugs_on_n_s_s_and_s"
9
+ end
10
+
11
+ def self.down
12
+ remove_index :slugs, :name => "index_slugs_on_n_s_s_and_s"
13
+ remove_column :slugs, :scope
14
+ remove_column :slugs, :sequence
15
+ add_column :slugs, :updated_at, :datetime
16
+ add_index :slugs, [:name, :sluggable_type], :unique => true
17
+ end
18
+
19
+ end
data/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ # encoding: utf-8
2
+
3
+ require 'friendly_id'
@@ -0,0 +1,101 @@
1
+ # encoding: utf-8
2
+
3
+ require 'friendly_id/helpers'
4
+ require 'friendly_id/slug'
5
+
6
+ # FriendlyId is a comprehensize Rails plugin/gem for slugging and permalinks.
7
+ module FriendlyId
8
+
9
+ # Default options for has_friendly_id.
10
+ DEFAULT_FRIENDLY_ID_OPTIONS = {
11
+ :max_length => 255,
12
+ :method => nil,
13
+ :reserved => ["new", "index"],
14
+ :reserved_message => 'can not be "%s"',
15
+ :scope => nil,
16
+ :strip_diacritics => false,
17
+ :strip_non_ascii => false,
18
+ :use_slug => false }.freeze
19
+
20
+ # Valid keys for has_friendly_id options.
21
+ VALID_FRIENDLY_ID_KEYS = [
22
+ :max_length,
23
+ :reserved,
24
+ :reserved_message,
25
+ :scope,
26
+ :strip_diacritics,
27
+ :strip_non_ascii,
28
+ :use_slug ].freeze
29
+
30
+ # This error is raised when it's not possible to generate a unique slug.
31
+ class SlugGenerationError < StandardError ; end
32
+
33
+ module ClassMethods
34
+
35
+ # Set up an ActiveRecord model to use a friendly_id.
36
+ #
37
+ # The column argument can be one of your model's columns, or a method
38
+ # you use to generate the slug.
39
+ #
40
+ # Options:
41
+ # * <tt>:use_slug</tt> - Defaults to false. Use slugs when you want to use a non-unique text field for friendly ids.
42
+ # * <tt>:max_length</tt> - Defaults to 255. The maximum allowed length for a slug.
43
+ # * <tt>:strip_diacritics</tt> - Defaults to false. If true, it will remove accents, umlauts, etc. from western characters.
44
+ # * <tt>:strip_non_ascii</tt> - Defaults to false. If true, it will all non-ascii ([^a-z0-9]) characters.
45
+ # * <tt>:reserved</tt> - Array of words that are reserved and can't be used as friendly_id's. For sluggable models, if such a word is used, it will be treated the same as if that slug was already taken (numeric extension will be appended). Defaults to ["new", "index"].
46
+ # * <tt>:reserved_message</tt> - The validation message that will be shown when a reserved word is used as a frindly_id. Defaults to '"%s" is reserved'.
47
+ #
48
+ # You can also optionally pass a block if you want to use your own custom
49
+ # slugnormalization routines rather than the default ones that come with
50
+ # friendly_id:
51
+ #
52
+ # require 'stringex'
53
+ # class Post < ActiveRecord::Base
54
+ # has_friendly_id :title, :use_slug => true do |text|
55
+ # # Use stringex to generate the friendly_id rather than the baked-in methods
56
+ # text.to_url
57
+ # end
58
+ # end
59
+ def has_friendly_id(column, options = {}, &block)
60
+ options.assert_valid_keys VALID_FRIENDLY_ID_KEYS
61
+ options = DEFAULT_FRIENDLY_ID_OPTIONS.merge(options).merge(:column => column)
62
+ write_inheritable_attribute :friendly_id_options, options
63
+ class_inheritable_accessor :friendly_id_options
64
+ class_inheritable_reader :slug_normalizer_block
65
+
66
+ if options[:use_slug]
67
+ has_many :slugs, :order => 'id DESC', :as => :sluggable, :dependent => :destroy
68
+ require 'friendly_id/sluggable_class_methods'
69
+ require 'friendly_id/sluggable_instance_methods'
70
+ extend SluggableClassMethods
71
+ include SluggableInstanceMethods
72
+ before_save :set_slug
73
+ if block_given?
74
+ write_inheritable_attribute :slug_normalizer_block, block
75
+ end
76
+ else
77
+ require 'friendly_id/non_sluggable_class_methods'
78
+ require 'friendly_id/non_sluggable_instance_methods'
79
+ extend NonSluggableClassMethods
80
+ include NonSluggableInstanceMethods
81
+ validate :validate_friendly_id
82
+ end
83
+ end
84
+
85
+ end
86
+
87
+ class << self
88
+
89
+ # Load FriendlyId if the gem is included in a Rails app.
90
+ def enable
91
+ return if ActiveRecord::Base.methods.include? 'has_friendly_id'
92
+ ActiveRecord::Base.class_eval { extend FriendlyId::ClassMethods }
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+
99
+ if defined?(ActiveRecord)
100
+ FriendlyId::enable
101
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ module FriendlyId
4
+
5
+ module Helpers
6
+ # Calculate expected result size for find_some_with_friendly (taken from
7
+ # active_record/base.rb)
8
+ def expected_size(ids_and_names, options) #:nodoc:#
9
+ size = options[:offset] ? ids_and_names.size - options[:offset] : ids_and_names.size
10
+ size = options[:limit] if options[:limit] && size > options[:limit]
11
+ size
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ module FriendlyId::NonSluggableClassMethods
4
+
5
+ include FriendlyId::Helpers
6
+
7
+ def self.extended(base) #:nodoc:#
8
+ class << base
9
+ alias_method_chain :find_one, :friendly
10
+ alias_method_chain :find_some, :friendly
11
+ end
12
+ end
13
+
14
+ protected
15
+
16
+ def find_one_with_friendly(id, options) #:nodoc:#
17
+ if id.is_a?(String) && result = send("find_by_#{ friendly_id_options[:column] }", id, options)
18
+ result.send(:found_using_friendly_id=, true)
19
+ else
20
+ result = find_one_without_friendly id, options
21
+ end
22
+ result
23
+ end
24
+
25
+ def find_some_with_friendly(ids_and_names, options) #:nodoc:#
26
+
27
+ results = with_scope :find => options do
28
+ find :all, :conditions => ["#{quoted_table_name}.#{primary_key} IN (?) OR #{friendly_id_options[:column].to_s} IN (?)",
29
+ ids_and_names, ids_and_names]
30
+ end
31
+
32
+ expected = expected_size(ids_and_names, options)
33
+ if results.size != expected
34
+ raise ActiveRecord::RecordNotFound, "Couldn't find all #{ name.pluralize } with IDs (#{ ids_and_names * ', ' }) AND #{ sanitize_sql options[:conditions] } (found #{ results.size } results, but was looking for #{ expected })"
35
+ end
36
+
37
+ results.each {|r| r.send(:found_using_friendly_id=, true) if ids_and_names.include?(r.friendly_id)}
38
+
39
+ results
40
+
41
+ end
42
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ module FriendlyId::NonSluggableInstanceMethods
4
+
5
+ attr :found_using_friendly_id
6
+
7
+ # Was the record found using one of its friendly ids?
8
+ def found_using_friendly_id?
9
+ @found_using_friendly_id
10
+ end
11
+
12
+ # Was the record found using its numeric id?
13
+ def found_using_numeric_id?
14
+ !@found_using_friendly_id
15
+ end
16
+ alias has_better_id? found_using_numeric_id?
17
+
18
+ # Returns the friendly_id.
19
+ def friendly_id
20
+ send friendly_id_options[:column]
21
+ end
22
+ alias best_id friendly_id
23
+
24
+ # Returns the friendly id, or if none is available, the numeric id.
25
+ def to_param
26
+ (friendly_id || id).to_s
27
+ end
28
+
29
+ private
30
+
31
+ def validate_friendly_id
32
+ if self.class.friendly_id_options[:reserved].include? friendly_id
33
+ self.errors.add(self.class.friendly_id_options[:column],
34
+ self.class.friendly_id_options[:reserved_message] % friendly_id)
35
+ return false
36
+ end
37
+ end
38
+
39
+ def found_using_friendly_id=(value) #:nodoc#
40
+ @found_using_friendly_id = value
41
+ end
42
+
43
+ end
@@ -0,0 +1,102 @@
1
+ #encoding: utf-8
2
+
3
+ # A Slug is a unique, human-friendly identifier for an ActiveRecord.
4
+ class Slug < ActiveRecord::Base
5
+
6
+ belongs_to :sluggable, :polymorphic => true
7
+ before_save :check_for_blank_name, :set_sequence
8
+
9
+
10
+ ASCII_APPROXIMATIONS = {
11
+ 198 => "AE",
12
+ 208 => "D",
13
+ 216 => "O",
14
+ 222 => "Th",
15
+ 223 => "ss",
16
+ 230 => "ae",
17
+ 240 => "d",
18
+ 248 => "o",
19
+ 254 => "th"
20
+ }.freeze
21
+
22
+ class << self
23
+
24
+ # Sanitizes and dasherizes string to make it safe for URL's.
25
+ #
26
+ # Example:
27
+ #
28
+ # slug.normalize('This... is an example!') # => "this-is-an-example"
29
+ #
30
+ # Note that the Unicode handling in ActiveSupport may fail to process some
31
+ # characters from Polish, Icelandic and other languages. If your
32
+ # application uses these languages, check {out this
33
+ # article}[http://link-coming-soon.com] for information on how to get
34
+ # better urls in your application.
35
+ def normalize(slug_text)
36
+ return "" if slug_text.nil? || slug_text == ""
37
+ ActiveSupport::Multibyte.proxy_class.new(slug_text.to_s).normalize(:kc).
38
+ gsub(/[\W]/u, ' ').
39
+ strip.
40
+ gsub(/\s+/u, '-').
41
+ gsub(/-\z/u, '').
42
+ downcase.
43
+ to_s
44
+ end
45
+
46
+ def parse(friendly_id)
47
+ name, sequence = friendly_id.split('--')
48
+ sequence ||= "1"
49
+ return name, sequence
50
+ end
51
+
52
+ # Remove diacritics (accents, umlauts, etc.) from the string. Borrowed
53
+ # from "The Ruby Way."
54
+ def strip_diacritics(string)
55
+ ActiveSupport::Multibyte.proxy_class.new(string).normalize(:kd).unpack('U*').inject([]) { |a, u|
56
+ if ASCII_APPROXIMATIONS[u]
57
+ a += ASCII_APPROXIMATIONS[u].unpack('U*')
58
+ elsif (u < 0x300 || u > 0x036F)
59
+ a << u
60
+ end
61
+ a
62
+ }.pack('U*')
63
+ end
64
+
65
+
66
+
67
+ # Remove non-ascii characters from the string.
68
+ def strip_non_ascii(string)
69
+ strip_diacritics(string).gsub(/[^a-z0-9]+/i, ' ')
70
+ end
71
+
72
+ private
73
+
74
+ end
75
+
76
+ # Whether or not this slug is the most recent of its owner's slugs.
77
+ def is_most_recent?
78
+ sluggable.slug == self
79
+ end
80
+
81
+ def to_friendly_id
82
+ sequence > 1 ? "#{name}--#{sequence}" : name
83
+ end
84
+
85
+ protected
86
+
87
+ # Raise a FriendlyId::SlugGenerationError if the slug name is blank.
88
+ def check_for_blank_name #:nodoc:#
89
+ if name.blank?
90
+ raise FriendlyId::SlugGenerationError.new("The slug text is blank.")
91
+ end
92
+ end
93
+
94
+ def set_sequence
95
+ return unless new_record?
96
+ last = Slug.find(:first, :conditions => { :name => name, :scope => scope,
97
+ :sluggable_type => sluggable_type}, :order => "sequence DESC",
98
+ :select => 'sequence')
99
+ self.sequence = last.sequence + 1 if last
100
+ end
101
+
102
+ end