rdavila_friendly_id 2.2.6

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.
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/gempackagetask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/clean'
6
+
7
+ CLEAN << "pkg" << "docs" << "coverage"
8
+
9
+ task :default => :test
10
+
11
+ Rake::TestTask.new(:test) { |t| t.pattern = 'test/**/*_test.rb' }
12
+ Rake::GemPackageTask.new(eval(File.read("friendly_id.gemspec"))) { |pkg| }
13
+ Rake::RDocTask.new do |r|
14
+ r.rdoc_dir = "docs"
15
+ r.main = "README.rdoc"
16
+ r.rdoc_files.include "README.rdoc", "History.txt", "lib/**/*.rb"
17
+ end
18
+
19
+ begin
20
+ require "yard"
21
+ YARD::Rake::YardocTask.new do |t|
22
+ t.options = ["--output-dir=docs"]
23
+ end
24
+ rescue LoadError
25
+ end
26
+
27
+ begin
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |r|
30
+ r.test_files = FileList['test/*_test.rb']
31
+ r.verbose = true
32
+ r.rcov_opts << "--exclude gems/*"
33
+ end
34
+ rescue LoadError
35
+ end
data/extras/README.txt ADDED
@@ -0,0 +1,3 @@
1
+ These templates are here to generate FriendlyId-enabled Rails apps for
2
+ testing. They are for developers, they are not intended for generating
3
+ "real" applications.
@@ -0,0 +1,26 @@
1
+ run "rm public/index.html"
2
+ gem "friendly_id"
3
+ gem "haml"
4
+ gem "will_paginate"
5
+ run "haml --rails ."
6
+ generate "friendly_id"
7
+ generate :haml_scaffold, "post title:string"
8
+ route "map.root :controller => 'posts', :action => 'index'"
9
+ rake "db:migrate"
10
+ rake "db:fixtures:load"
11
+ file 'app/models/post.rb',
12
+ %q{class Post < ActiveRecord::Base
13
+ has_friendly_id :title, :use_slug => true
14
+ end}
15
+ file 'test/fixtures/slugs.yml',
16
+ %q{
17
+ one:
18
+ name: mystring
19
+ sequence: 1
20
+ sluggable: one (Post)
21
+
22
+ two:
23
+ name: mystring
24
+ sequence: 2
25
+ sluggable: two (Post)
26
+ }
@@ -0,0 +1,28 @@
1
+ run "rm public/index.html"
2
+ inside 'vendor/plugins' do
3
+ run "git clone ../../../../ friendly_id"
4
+ end
5
+ gem "haml"
6
+ gem "will_paginate"
7
+ run "haml --rails ."
8
+ generate "friendly_id"
9
+ generate :haml_scaffold, "post title:string"
10
+ route "map.root :controller => 'posts', :action => 'index'"
11
+ rake "db:migrate"
12
+ rake "db:fixtures:load"
13
+ file 'app/models/post.rb',
14
+ %q{class Post < ActiveRecord::Base
15
+ has_friendly_id :title, :use_slug => true
16
+ end}
17
+ file 'test/fixtures/slugs.yml',
18
+ %q{
19
+ one:
20
+ name: mystring
21
+ sequence: 1
22
+ sluggable: one (Post)
23
+
24
+ two:
25
+ name: mystring
26
+ sequence: 2
27
+ sluggable: two (Post)
28
+ }
@@ -0,0 +1,28 @@
1
+ class FriendlyIdGenerator < Rails::Generator::Base
2
+
3
+ def manifest
4
+ record do |m|
5
+ unless options[:skip_migration]
6
+ m.migration_template('create_slugs.rb', 'db/migrate', :migration_file_name => 'create_slugs')
7
+ end
8
+ unless options[:skip_tasks]
9
+ m.directory "lib/tasks"
10
+ m.file "/../../../lib/tasks/friendly_id.rake", "lib/tasks/friendly_id.rake"
11
+ end
12
+ end
13
+ end
14
+
15
+ protected
16
+
17
+ def add_options!(opt)
18
+ opt.separator ''
19
+ opt.separator 'Options:'
20
+ opt.on("--skip-migration", "Don't generate a migration for the slugs table") do |value|
21
+ options[:skip_migration] = value
22
+ end
23
+ opt.on("--skip-tasks", "Don't add friendly_id Rake tasks to lib/tasks") do |value|
24
+ options[:skip_tasks] = value
25
+ end
26
+ end
27
+
28
+ 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], :name => "index_slugs_on_n_s_s_and_s", :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,12 @@
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
+ m.file "/../../../lib/tasks/friendly_id.rake", "lib/tasks/friendly_id.rake"
9
+ end
10
+ end
11
+ end
12
+ 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 @@
1
+ require "friendly_id"
@@ -0,0 +1,83 @@
1
+ require "friendly_id/helpers"
2
+ require "friendly_id/slug"
3
+ require "friendly_id/sluggable_class_methods"
4
+ require "friendly_id/sluggable_instance_methods"
5
+ require "friendly_id/non_sluggable_class_methods"
6
+ require "friendly_id/non_sluggable_instance_methods"
7
+
8
+ begin
9
+ require 'ar-extensions'
10
+ require 'ar-extensions/adapters/mysql'
11
+ require 'ar-extensions/import/mysql'
12
+ rescue LoadError
13
+ puts "Please install ar-extensions if you want to use friendly_id:make_slugs_faster or friendly_id:redo_slugs_faster tasks."
14
+ end
15
+
16
+ # FriendlyId is a comprehensive Ruby library for slugging and permalinks with
17
+ # ActiveRecord.
18
+ module FriendlyId
19
+
20
+ # Default options for has_friendly_id.
21
+ DEFAULT_OPTIONS = {
22
+ :max_length => 255,
23
+ :reserved => ["new", "index"],
24
+ :reserved_message => 'can not be "%s"'
25
+ }.freeze
26
+
27
+ # The names of all valid configuration options.
28
+ VALID_OPTIONS = (DEFAULT_OPTIONS.keys + [
29
+ :cache_column,
30
+ :scope,
31
+ :strip_diacritics,
32
+ :strip_non_ascii,
33
+ :use_slug
34
+ ]).freeze
35
+
36
+ # This error is raised when it's not possible to generate a unique slug.
37
+ class SlugGenerationError < StandardError ; end
38
+
39
+ # Set up an ActiveRecord model to use a friendly_id.
40
+ #
41
+ # The column argument can be one of your model's columns, or a method
42
+ # you use to generate the slug.
43
+ #
44
+ # Options:
45
+ # * <tt>:use_slug</tt> - Defaults to nil. Use slugs when you want to use a non-unique text field for friendly ids.
46
+ # * <tt>:max_length</tt> - Defaults to 255. The maximum allowed length for a slug.
47
+ # * <tt>:cache_column</tt> - Defaults to nil. Use this column as a cache for generating to_param (experimental) Note that if you use this option, any calls to +attr_accessible+ must be made BEFORE any calls to has_friendly_id in your class.
48
+ # * <tt>:strip_diacritics</tt> - Defaults to nil. If true, it will remove accents, umlauts, etc. from western characters.
49
+ # * <tt>:strip_non_ascii</tt> - Defaults to nil. If true, it will remove all non-ASCII characters.
50
+ # * <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 raise a FriendlyId::SlugGenerationError. Defaults to ["new", "index"].
51
+ # * <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'.
52
+ #
53
+ # You can also optionally pass a block if you want to use your own custom
54
+ # slug normalization routines rather than the default ones that come with
55
+ # friendly_id:
56
+ #
57
+ # require "stringex"
58
+ # class Post < ActiveRecord::Base
59
+ # has_friendly_id :title, :use_slug => true do |text|
60
+ # # Use stringex to generate the friendly_id rather than the baked-in methods
61
+ # text.to_url
62
+ # end
63
+ # end
64
+ def has_friendly_id(method, options = {}, &block)
65
+ options.assert_valid_keys VALID_OPTIONS
66
+ options = DEFAULT_OPTIONS.merge(options).merge(:method => method)
67
+ write_inheritable_attribute :friendly_id_options, options
68
+ class_inheritable_accessor :friendly_id_options
69
+ class_inheritable_reader :slug_normalizer_block
70
+ write_inheritable_attribute(:slug_normalizer_block, block) if block_given?
71
+ if friendly_id_options[:use_slug]
72
+ extend SluggableClassMethods
73
+ include SluggableInstanceMethods
74
+ else
75
+ extend NonSluggableClassMethods
76
+ include NonSluggableInstanceMethods
77
+ end
78
+ end
79
+ end
80
+
81
+ class ActiveRecord::Base #:nodoc:#
82
+ extend FriendlyId #:nodoc:#
83
+ end
@@ -0,0 +1,12 @@
1
+ module FriendlyId
2
+
3
+ module Helpers
4
+ # Calculate expected result size for find_some_with_friendly (taken from
5
+ # active_record/base.rb)
6
+ def expected_size(ids_and_names, options) #:nodoc:#
7
+ size = options[:offset] ? ids_and_names.size - options[:offset] : ids_and_names.size
8
+ size = options[:limit] if options[:limit] && size > options[:limit]
9
+ size
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,34 @@
1
+ module FriendlyId::NonSluggableClassMethods
2
+
3
+ include FriendlyId::Helpers
4
+
5
+ protected
6
+
7
+ def find_one(id, options) #:nodoc:#
8
+ if id.respond_to?(:to_str) && result = send("find_by_#{ friendly_id_options[:method] }", id.to_str, options)
9
+ result.send(:found_using_friendly_id=, true)
10
+ else
11
+ result = super id, options
12
+ end
13
+ result
14
+ end
15
+
16
+ def find_some(ids_and_names, options) #:nodoc:#
17
+
18
+ names, ids = ids_and_names.partition {|id_or_name| id_or_name.respond_to?(:to_str) && id_or_name.to_str }
19
+ results = with_scope :find => options do
20
+ find :all, :conditions => ["#{quoted_table_name}.#{primary_key} IN (?) OR #{friendly_id_options[:method]} IN (?)",
21
+ ids, names]
22
+ end
23
+
24
+ expected = expected_size(ids_and_names, options)
25
+ if results.size != expected
26
+ 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 })"
27
+ end
28
+
29
+ results.each {|r| r.send(:found_using_friendly_id=, true) if names.include?(r.friendly_id)}
30
+
31
+ results
32
+
33
+ end
34
+ end
@@ -0,0 +1,45 @@
1
+ module FriendlyId::NonSluggableInstanceMethods
2
+
3
+ def self.included(base)
4
+ base.validate :validate_friendly_id
5
+ end
6
+
7
+ attr :found_using_friendly_id
8
+
9
+ # Was the record found using one of its friendly ids?
10
+ def found_using_friendly_id?
11
+ @found_using_friendly_id
12
+ end
13
+
14
+ # Was the record found using its numeric id?
15
+ def found_using_numeric_id?
16
+ !@found_using_friendly_id
17
+ end
18
+ alias has_better_id? found_using_numeric_id?
19
+
20
+ # Returns the friendly_id.
21
+ def friendly_id
22
+ send friendly_id_options[:method]
23
+ end
24
+ alias best_id friendly_id
25
+
26
+ # Returns the friendly id, or if none is available, the numeric id.
27
+ def to_param
28
+ (friendly_id || id).to_s
29
+ end
30
+
31
+ private
32
+
33
+ def validate_friendly_id
34
+ if self.class.friendly_id_options[:reserved].include? friendly_id
35
+ self.errors.add(self.class.friendly_id_options[:method],
36
+ self.class.friendly_id_options[:reserved_message] % friendly_id)
37
+ return false
38
+ end
39
+ end
40
+
41
+ def found_using_friendly_id=(value) #:nodoc#
42
+ @found_using_friendly_id = value
43
+ end
44
+
45
+ end
@@ -0,0 +1,98 @@
1
+ # A Slug is a unique, human-friendly identifier for an ActiveRecord.
2
+ class Slug < ActiveRecord::Base
3
+
4
+ belongs_to :sluggable, :polymorphic => true
5
+ before_save :check_for_blank_name, :set_sequence
6
+
7
+
8
+ ASCII_APPROXIMATIONS = {
9
+ 198 => "AE",
10
+ 208 => "D",
11
+ 216 => "O",
12
+ 222 => "Th",
13
+ 223 => "ss",
14
+ 230 => "ae",
15
+ 240 => "d",
16
+ 248 => "o",
17
+ 254 => "th"
18
+ }.freeze
19
+
20
+ class << self
21
+
22
+ # Sanitizes and dasherizes string to make it safe for URL's.
23
+ #
24
+ # Example:
25
+ #
26
+ # slug.normalize('This... is an example!') # => "this-is-an-example"
27
+ #
28
+ # Note that the Unicode handling in ActiveSupport may fail to process some
29
+ # characters from Polish, Icelandic and other languages.
30
+ def normalize(slug_text)
31
+ return "" if slug_text.nil? || slug_text == ""
32
+ ActiveSupport::Multibyte.proxy_class.new(slug_text.to_s).normalize(:kc).
33
+ gsub(/[\W]/u, ' ').
34
+ strip.
35
+ gsub(/\s+/u, '-').
36
+ gsub(/-\z/u, '').
37
+ downcase.
38
+ to_s
39
+ end
40
+
41
+ def parse(friendly_id)
42
+ name, sequence = friendly_id.split('--')
43
+ sequence ||= "1"
44
+ return name, sequence
45
+ end
46
+
47
+ # Remove diacritics (accents, umlauts, etc.) from the string. Borrowed
48
+ # from "The Ruby Way."
49
+ def strip_diacritics(string)
50
+ a = ActiveSupport::Multibyte.proxy_class.new(string || "").normalize(:kd)
51
+ a.unpack('U*').inject([]) { |a, u|
52
+ if ASCII_APPROXIMATIONS[u]
53
+ a += ASCII_APPROXIMATIONS[u].unpack('U*')
54
+ elsif (u < 0x300 || u > 0x036F)
55
+ a << u
56
+ end
57
+ a
58
+ }.pack('U*')
59
+ end
60
+
61
+
62
+
63
+ # Remove non-ascii characters from the string.
64
+ def strip_non_ascii(string)
65
+ strip_diacritics(string).gsub(/[^a-z0-9]+/i, ' ')
66
+ end
67
+
68
+ private
69
+
70
+ end
71
+
72
+ # Whether or not this slug is the most recent of its owner's slugs.
73
+ def is_most_recent?
74
+ sluggable.slug == self
75
+ end
76
+
77
+ def to_friendly_id
78
+ sequence > 1 ? "#{name}--#{sequence}" : name
79
+ end
80
+
81
+ protected
82
+
83
+ # Raise a FriendlyId::SlugGenerationError if the slug name is blank.
84
+ def check_for_blank_name #:nodoc:#
85
+ if name.blank?
86
+ raise FriendlyId::SlugGenerationError.new("The slug text is blank.")
87
+ end
88
+ end
89
+
90
+ def set_sequence
91
+ return unless new_record?
92
+ last = Slug.find(:first, :conditions => { :name => name, :scope => scope,
93
+ :sluggable_type => sluggable_type}, :order => "sequence DESC",
94
+ :select => 'sequence')
95
+ self.sequence = last.sequence + 1 if last
96
+ end
97
+
98
+ end