djanowski-permalink_fu 0.1.0
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/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
|
+
|