djanowski-permalink_fu 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/README.markdown +71 -0
  2. data/Rakefile +26 -0
  3. data/init.rb +1 -0
  4. data/lib/permalink_fu.rb +143 -0
  5. 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
@@ -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
+