djanowski-permalink_fu 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +71 -0
- data/Rakefile +26 -0
- data/init.rb +1 -0
- data/lib/permalink_fu.rb +143 -0
- metadata +63 -0
data/README.markdown
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# PermalinkFu
|
2
|
+
|
3
|
+
A simple plugin for creating URL-friendly permalinks (slugs) from attributes.
|
4
|
+
|
5
|
+
Uses [`ActiveSupport::Multibyte::Handlers::UTF8Handler`](http://api.rubyonrails.org/classes/ActiveSupport/Multibyte/Handlers/UTF8Handler.html) (part of Rails since 1.2) rather than `iconv` (which is [inconsistent between platforms](http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/243426)) for normalization/decomposition.
|
6
|
+
|
7
|
+
|
8
|
+
## Usage
|
9
|
+
|
10
|
+
class Article < ActiveRecord::Base
|
11
|
+
has_permalink :title
|
12
|
+
end
|
13
|
+
|
14
|
+
This will escape the title in a before_validation callback, turning e.g. "Föö!! Bàr" into "foo-bar".
|
15
|
+
|
16
|
+
The permalink is by default stored in the `permalink` attribute.
|
17
|
+
|
18
|
+
has_permalink :title, :as => :slug
|
19
|
+
|
20
|
+
will store it in `slug` instead.
|
21
|
+
|
22
|
+
has_permalink [:category, :title]
|
23
|
+
|
24
|
+
will store a permalink form of `"#{category}-#{title}"`.
|
25
|
+
|
26
|
+
Permalinks are guaranteed unique: "foo-bar-2", "foo-bar-3" etc are used if there are conflicts. You can set the scope of the uniqueness like
|
27
|
+
|
28
|
+
has_permalink :title, :scope => :blog_id
|
29
|
+
|
30
|
+
This means that two articles with the same `blog_id` can not have the same permalink, but two articles with different `blog_id`s can.
|
31
|
+
|
32
|
+
Two finders are provided:
|
33
|
+
|
34
|
+
Article.find_by_permalink(params[:id])
|
35
|
+
Article.find_by_permalink!(params[:id])
|
36
|
+
|
37
|
+
These methods keep their name no matter what attribute is used to store the permalink.
|
38
|
+
|
39
|
+
The `find_by_permalink` method returns `nil` if there is no match; the `find_by_permalink!` method will raise `ActiveRecord::RecordNotFound`.
|
40
|
+
|
41
|
+
By default, this fork will override `ActiveRecord::Base#to_param` so that your controllers can still find your models without further work:
|
42
|
+
|
43
|
+
article.to_param
|
44
|
+
# => "1-foo-bar"
|
45
|
+
|
46
|
+
You can change this behavior by using the `param` option:
|
47
|
+
|
48
|
+
has_permalink :title, :param => :permalink
|
49
|
+
|
50
|
+
has_permalink :title, :param => false
|
51
|
+
|
52
|
+
This means that the permalink will be used instead of the primary key (id) in generated URLs. Remember to change your controller code from e.g. `find` to `find_by_permalink!`.
|
53
|
+
|
54
|
+
You can add conditions to `has_permalink` like so:
|
55
|
+
|
56
|
+
class Article < ActiveRecord::Base
|
57
|
+
has_permalink :title, :if => Proc.new { |article| article.needs_permalink? }
|
58
|
+
end
|
59
|
+
|
60
|
+
Use the `:if` or `:unless` options to specify a Proc, method, or string to be called or evaluated. The permalink will only be generated if the option evaluates to true.
|
61
|
+
|
62
|
+
You can use `PermalinkFu.escape` to escape a string manually.
|
63
|
+
|
64
|
+
|
65
|
+
## Credits
|
66
|
+
|
67
|
+
Originally extracted from [Mephisto](http://mephistoblog.com) by [technoweenie](http://github.com/technoweenie/permalink_fu/).
|
68
|
+
|
69
|
+
Conditions added by [Pat Nakajima](http://github.com/nakajima/permalink_fu/).
|
70
|
+
|
71
|
+
[Henrik Nyh](http://github.com/technoweenie/permalink_fu/) replaced `iconv` with `ActiveSupport::Multibyte`.
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'spec/rake/spectask'
|
5
|
+
|
6
|
+
gem_spec_file = 'permalink_fu.gemspec'
|
7
|
+
|
8
|
+
gem_spec = eval(File.read(gem_spec_file)) rescue nil
|
9
|
+
|
10
|
+
desc 'Default: run all specs.'
|
11
|
+
task :default => :spec
|
12
|
+
|
13
|
+
desc "Run all specs in spec directory (excluding plugin specs)"
|
14
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
15
|
+
t.spec_opts = ['--options', File.join(File.dirname(__FILE__), 'spec', 'spec.opts')]
|
16
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "Generate the gemspec file."
|
20
|
+
task :gemspec do
|
21
|
+
require 'erb'
|
22
|
+
|
23
|
+
File.open(gem_spec_file, 'w') do |f|
|
24
|
+
f.write ERB.new(File.read("#{gem_spec_file}.erb")).result(binding)
|
25
|
+
end
|
26
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ActiveRecord::Base.send :include, PermalinkFu
|
data/lib/permalink_fu.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
begin
|
2
|
+
require "active_support"
|
3
|
+
rescue LoadError
|
4
|
+
require "rubygems"
|
5
|
+
require "active_support"
|
6
|
+
end
|
7
|
+
|
8
|
+
module PermalinkFu
|
9
|
+
|
10
|
+
def self.escape(str)
|
11
|
+
s = ActiveSupport::Multibyte::Handlers::UTF8Handler.normalize(str.to_s, :kd)
|
12
|
+
s.gsub!(/[^\w -]+/n, '') # strip unwanted characters
|
13
|
+
s.strip! # ohh la la
|
14
|
+
s.downcase!
|
15
|
+
s.gsub!(/[ -]+/, '-') # separate by single dashes
|
16
|
+
s
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.included(base)
|
20
|
+
base.extend ClassMethods
|
21
|
+
class << base
|
22
|
+
attr_accessor :permalink_options
|
23
|
+
attr_accessor :permalink_attributes
|
24
|
+
attr_accessor :permalink_field
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
# Specifies the given field(s) as a permalink, meaning it is passed through PermalinkFu.escape and set to the permalink_field. This
|
30
|
+
# is done
|
31
|
+
#
|
32
|
+
# class Foo < ActiveRecord::Base
|
33
|
+
# # stores permalink form of #title to the #permalink attribute
|
34
|
+
# has_permalink :title
|
35
|
+
#
|
36
|
+
# # stores a permalink form of "#{category}-#{title}" to the #permalink attribute
|
37
|
+
#
|
38
|
+
# has_permalink [:category, :title]
|
39
|
+
#
|
40
|
+
# # stores permalink form of #title to the #category_permalink attribute
|
41
|
+
# has_permalink [:category, :title], :as => :category_permalink
|
42
|
+
#
|
43
|
+
# # add a scope
|
44
|
+
# has_permalink :title, :scope => :blog_id
|
45
|
+
#
|
46
|
+
# # add a scope and specify the permalink field name
|
47
|
+
# has_permalink :title, :as => :slug, :scope => :blog_id
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
def has_permalink(attr_names = [], options = {})
|
51
|
+
self.permalink_attributes = Array(attr_names)
|
52
|
+
self.permalink_field = (options.delete(:as) || 'permalink').to_s
|
53
|
+
self.permalink_options = options
|
54
|
+
before_validation :create_unique_permalink
|
55
|
+
evaluate_attribute_method permalink_field, "def #{self.permalink_field}=(new_value);write_attribute(:#{self.permalink_field}, PermalinkFu.escape(new_value));end", "#{self.permalink_field}="
|
56
|
+
extend PermalinkFinders
|
57
|
+
|
58
|
+
case options[:param]
|
59
|
+
when false
|
60
|
+
# nothing
|
61
|
+
when :permalink
|
62
|
+
include ToParam
|
63
|
+
else
|
64
|
+
include ToParamWithID
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
module ToParam
|
70
|
+
def to_param
|
71
|
+
send(self.class.permalink_field)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
module ToParamWithID
|
76
|
+
def to_param
|
77
|
+
"#{id}-#{send(self.class.permalink_field)}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
module PermalinkFinders
|
82
|
+
def find_by_permalink(value)
|
83
|
+
find(:first, :conditions => { permalink_field => value })
|
84
|
+
end
|
85
|
+
|
86
|
+
def find_by_permalink!(value)
|
87
|
+
find_by_permalink(value) ||
|
88
|
+
raise(ActiveRecord::RecordNotFound, "Couldn't find #{name} with permalink #{value.inspect}")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
protected
|
93
|
+
def create_unique_permalink
|
94
|
+
return unless should_create_permalink?
|
95
|
+
if send(self.class.permalink_field).to_s.empty?
|
96
|
+
send("#{self.class.permalink_field}=", create_permalink_for(self.class.permalink_attributes))
|
97
|
+
end
|
98
|
+
limit = self.class.columns_hash[self.class.permalink_field].limit
|
99
|
+
base = send("#{self.class.permalink_field}=", send(self.class.permalink_field)[0..limit - 1])
|
100
|
+
counter = 1
|
101
|
+
# oh how i wish i could use a hash for conditions
|
102
|
+
conditions = ["#{self.class.permalink_field} = ?", base]
|
103
|
+
unless new_record?
|
104
|
+
conditions.first << " and id != ?"
|
105
|
+
conditions << id
|
106
|
+
end
|
107
|
+
if self.class.permalink_options[:scope]
|
108
|
+
conditions.first << " and #{self.class.permalink_options[:scope]} = ?"
|
109
|
+
conditions << send(self.class.permalink_options[:scope])
|
110
|
+
end
|
111
|
+
while self.class.exists?(conditions)
|
112
|
+
suffix = "-#{counter += 1}"
|
113
|
+
conditions[1] = "#{base[0..limit-suffix.size-1]}#{suffix}"
|
114
|
+
send("#{self.class.permalink_field}=", conditions[1])
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def create_permalink_for(attr_names)
|
119
|
+
attr_names.collect { |attr_name| send(attr_name).to_s } * " "
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
def should_create_permalink?
|
124
|
+
if self.class.permalink_options[:if]
|
125
|
+
evaluate_method(self.class.permalink_options[:if])
|
126
|
+
elsif self.class.permalink_options[:unless]
|
127
|
+
!evaluate_method(self.class.permalink_options[:unless])
|
128
|
+
else
|
129
|
+
true
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def evaluate_method(method)
|
134
|
+
case method
|
135
|
+
when Symbol
|
136
|
+
send(method)
|
137
|
+
when String
|
138
|
+
eval(method, instance_eval { binding })
|
139
|
+
when Proc, Method
|
140
|
+
method.call(self)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: djanowski-permalink_fu
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Damian Janowski
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-10-16 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: ""
|
17
|
+
email: damian.janowski@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.textile
|
24
|
+
files:
|
25
|
+
- lib/permalink_fu.rb
|
26
|
+
- init.rb
|
27
|
+
- README.markdown
|
28
|
+
- MIT-LICENSE
|
29
|
+
- Rakefile
|
30
|
+
- README.textile
|
31
|
+
has_rdoc: false
|
32
|
+
homepage:
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options:
|
35
|
+
- --line-numbers
|
36
|
+
- --inline-source
|
37
|
+
- --title
|
38
|
+
- permalink_fu
|
39
|
+
- --main
|
40
|
+
- README.textile
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
version:
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
requirements: []
|
56
|
+
|
57
|
+
rubyforge_project:
|
58
|
+
rubygems_version: 1.2.0
|
59
|
+
signing_key:
|
60
|
+
specification_version: 2
|
61
|
+
summary: ActiveRecord plugin for automatically converting fields to permalinks.
|
62
|
+
test_files: []
|
63
|
+
|