rdavila_friendly_id 2.2.6

Sign up to get free protection for your applications and to get access to all the features.
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