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.
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
+