slugger 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,6 +1,6 @@
1
1
  = Slugger
2
2
 
3
- Slugger is yet another slug generator. It makes pretty urls for Active Record models. It jumps through some hoops to make sure every slug is unique.
3
+ Slugger is yet another slug generator. It makes pretty urls for Active Record models. Why would the ruby (on rails) community need another slug generating plugin you ask? It doesn't, but I needed one that would jump through some hoops to make sure slugs are unique without setting errors.
4
4
 
5
5
  == Installation
6
6
 
@@ -8,33 +8,42 @@ It's a gem. Either run `gem install slugger` at the command line, or add `gem 's
8
8
 
9
9
  == Usage
10
10
 
11
- has_slug :source_column, [options]
11
+ has_slug :source_column, [options_hash]
12
12
 
13
- The default source column is title.
13
+ The default source column is title and slug column is, well slug. The following two settings are the same:
14
+
15
+ has_slug
16
+ has_slug :title, :slug_column => 'slug'
14
17
 
15
18
  :source_column can also be an array, for example, if you wanted to create slugs for an Author model on first and last name:
16
19
 
17
- has_slug [:first_name, :last_name]
20
+ class Author << ActiveRecord::Base
21
+ has_slug [:first_name, :last_name]
22
+ end
23
+
24
+ To be sure that the slug will never have a validation error:
18
25
 
19
- == Options
26
+ class Post << ActiveRecord::Base
27
+ has_slug :title, :on_conflict => :concat_random_chars
28
+ end
20
29
 
21
- Say you have an Episode model that belongs to TVSeries. Every episode will have a pilot, but you don't want the unique validation to build silly looking urls:
30
+ Say you have an Episode model that belongs to TVSeries. Every episode will have a pilot, but you don't want the unique validation to build silly looking urls: (this doesn't actually work), patches welcome
22
31
 
23
32
  class Episode << ActiveRecord::Base
24
33
  belongs_to :series
25
34
 
26
35
  has_slug :title, :scope => :series_id
27
36
  end
28
-
29
37
 
30
38
  == Example
31
39
 
32
- create_table "collection" do |t|
40
+ create_table "collections" do |t|
33
41
  t.string "title"
34
42
  t.string "slug"
35
43
  end
44
+ add_index(:collections, :slug)
36
45
 
37
- class Project < ActiveRecord::Base
46
+ class Collection < ActiveRecord::Base
38
47
  has_slug
39
48
  end
40
49
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.1
data/lib/slugger.rb CHANGED
@@ -7,35 +7,39 @@ module Slugger
7
7
  base.extend(ClassMethods)
8
8
  end
9
9
  module ClassMethods
10
- def has_slug(title_column=nil,sluger_options={})
11
- class_inheritable_accessor :sluger_options
12
- sluger_options[:title_column] ||= title_column || 'title'
13
- # sluger_options[:title_column] ||= 'title'
14
- sluger_options[:slug_column] ||= 'slug'
15
- sluger_options[:as_param] ||= true
16
- sluger_options[:substitution_char] ||= '-'
17
- self.sluger_options = sluger_options
10
+ def has_slug(title_column=nil,options={})
11
+ class_inheritable_accessor :slugger_options
12
+ default_options = {
13
+ :title_column => 'title',
14
+ :slug_column => 'slug',
15
+ :as_param => true,
16
+ :substitution_char => '-',
17
+ :downcase => true,
18
+ :on_conflict => :error
19
+ }
20
+ # :on_conflict can be :error or :concat_random_chars
21
+ self.slugger_options = default_options.merge(options)
22
+ self.slugger_options[:title_column] = title_column unless title_column.nil?
18
23
 
19
- # if columns_hash[sluger_options[:title_column].to_s].nil?
20
- #
24
+ # if columns_hash[slugger_options[:title_column].to_s].nil?
21
25
  # raise ArgumentError, "#{self.name} is missing source column"
22
26
  # end
23
- raise ArgumentError, "#{self.name} is missing required slug column" if columns_hash[sluger_options[:slug_column]].nil?
27
+ raise ArgumentError, "#{self.name} is missing required #{slugger_options[:slug_column]} column" if columns_hash[slugger_options[:slug_column].to_s].nil?
24
28
 
25
- before_validation :create_slug, :on => :create
29
+ before_validation :permalize, :on => :create
26
30
 
27
- validates sluger_options[:slug_column].to_sym, :presence => true
28
- if sluger_options[:scope]
29
- validates sluger_options[:slug_column].to_sym, :uniqueness => {:scope => sluger_options[:scope]}
31
+ validates slugger_options[:slug_column].to_sym, :presence => true
32
+ if slugger_options[:scope]
33
+ validates slugger_options[:slug_column].to_sym, :uniqueness => {:scope => slugger_options[:scope]}
30
34
  else
31
- validates sluger_options[:slug_column].to_sym, :uniqueness => true
35
+ validates slugger_options[:slug_column].to_sym, :uniqueness => true
32
36
  end
33
37
 
34
- send :define_method, :column_to_slug, lambda { self.send(sluger_options[:title_column]) }
38
+ send :define_method, :column_to_slug, lambda { self.send(slugger_options[:title_column]) }
35
39
 
36
40
  class << self
37
41
  def find(*args)
38
- if self.sluger_options[:as_param] && args.first.is_a?(String)
42
+ if self.slugger_options[:as_param] && args.first.is_a?(String)
39
43
  find_by_slug(args)
40
44
  else
41
45
  super(*args)
@@ -49,31 +53,48 @@ module Slugger
49
53
  module InstanceMethods
50
54
 
51
55
  def to_param
52
- sluger_options[:as_param] ? self.slug : self.id
56
+ slugger_options[:as_param] ? self.slug : self.id
53
57
  end
54
58
 
55
59
  protected
56
60
 
57
61
  def permalize
58
- return if !self.send("#{self.sluggable_conf[:slug_column]}").blank?
59
- s = Iconv.iconv('ascii//ignore//translit', 'utf-8', self.send("#{self.sluggable_conf[:title_column]}")).to_s
62
+ return unless self.send("#{self.slugger_options[:slug_column]}").blank?
63
+ if slugger_options[:title_column].is_a?(Array)
64
+ s = ""
65
+ self.slugger_options[:title_column].each do |m|
66
+ s = "#{s} #{self.send(m)}"
67
+ end
68
+ s = Iconv.iconv('ascii//ignore//translit', 'utf-8', s).to_s
69
+ else
70
+ s = Iconv.iconv('ascii//ignore//translit', 'utf-8', self.send("#{self.slugger_options[:title_column]}")).to_s
71
+ end
60
72
  s.gsub!(/\'/, '') # remove '
61
73
  s.gsub!(/\W+/, ' ') # all non-word chars to spaces
62
74
  s.strip! # ohh la la
63
- s.downcase! #
64
- s.gsub!(/\ +/, '-') # spaces to dashes, preferred separator char everywhere
65
- self.send("#{self.sluggable_conf[:slug_column]}=", s)
75
+ s.downcase! if slugger_options[:downcase]
76
+ s.gsub!(/\ +/, slugger_options[:substitution_char].to_s) # spaces to dashes, preferred separator char everywhere
77
+ self.send("#{self.slugger_options[:slug_column]}=", s)
78
+ slug_conflict_resolution
66
79
  end
67
- def strip_title
68
- self.send("#{self.sluggable_conf[:title_column]}").strip!
80
+ def slug_conflict_resolution(append=nil)
81
+ existing = self.class.send(:where, "#{slugger_options[:slug_column]}".to_sym => self.send("#{self.slugger_options[:slug_column]}")).first
82
+ if existing
83
+ self.send("slug_conflict_resolution_#{self.slugger_options[:on_conflict]}", append)
84
+ end
69
85
  end
70
-
71
- def create_slug
72
- self.slug ||= clean("#{column_to_slug}")
86
+ def slug_conflict_resolution_concat_random_chars(append)
87
+ chars = ("a".."z").to_a + ("1".."9").to_a
88
+ random_chars = Array.new(3, '').collect{chars[rand(chars.size)]}.join
89
+ self.send("#{self.slugger_options[:slug_column]}=", "#{self.slugger_options[:slug_column]}#{self.slugger_options[:substitution_char]}#{random_chars}")
90
+ slug_conflict_resolution
91
+ end
92
+ def slug_conflict_resolution_error(append)
93
+ # no op, validatino sets error
73
94
  end
74
-
75
- def clean(string)
76
- string.downcase.gsub(/[^\w\s\d\_\-]/,'').gsub(/\s\s+/,' ').gsub(/[^\w\d]/, sluger_options[:substitution_char])
95
+
96
+ def strip_title
97
+ self.send("#{self.slugger_options[:title_column]}").strip!
77
98
  end
78
99
  end
79
100
  end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ # class Edge < ActiveRecord::Base
4
+ # has_slug 'name', :slug_column => :slug_name,
5
+ # :as_param => false,
6
+ # :substitution_char => "_",
7
+ # :downcase => false,
8
+ # :on_conflict => :concat_random_chars
9
+ # end
10
+
11
+ describe Edge do
12
+ it "should set slug on create" do
13
+ e = Edge.create(:name => "hello")
14
+ e.slug_name.should == "hello"
15
+ end
16
+ it "should honor downcase false" do
17
+ e = Edge.create(:name => "YELLING")
18
+ e.slug_name.should == "YELLING"
19
+ end
20
+ it "should honor changing substitution_char" do
21
+ e = Edge.create(:name => "with spaces")
22
+ e.slug_name.should == "with_spaces"
23
+ end
24
+ it "should concat random chars when conflict" do
25
+ e = Edge.create(:name => "dupable")
26
+ e.slug_name.should == "dupable"
27
+ f = Edge.create(:name => e.slug_name)
28
+ f.should be_valid
29
+ f.slug_name.should_not == e.slug_name
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Post do
4
+ it "should set slug on create" do
5
+ p = Post.create(:title => "hello world")
6
+ p.slug.should == "hello-world"
7
+ end
8
+ it "should not override given slug" do
9
+ p = Post.create(:title => "hello world", :slug => "custom-slug")
10
+ p.slug.should == "custom-slug"
11
+ end
12
+ it "should remove apostrophes" do
13
+ p = Post.create(:title => "apostrop'hes", :slug => "apostrophes")
14
+ p.slug.should == "apostrophes"
15
+ end
16
+ it "should not be valid on duplicate" do
17
+ p = Post.create(:title => "hello")
18
+ p.slug.should == "hello"
19
+ q = Post.create(:title => "hello")
20
+ q.should_not be_valid
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ # class User < ActiveRecord::Base
4
+ # has_slug [:first_name, :last_name]
5
+ # end
6
+
7
+ describe User do
8
+ it "should set slug on create when given an array" do
9
+ u = User.create(:first_name => "Tyler", :last_name => "Durden")
10
+ u.slug.should == "tyler-durden"
11
+ end
12
+ end
data/spec/schema.rb CHANGED
@@ -20,6 +20,11 @@ class CreateSchema < ActiveRecord::Migration
20
20
  t.string :slug
21
21
  t.timestamps
22
22
  end
23
+
24
+ create_table :edges, :force => true do |t|
25
+ t.string :name
26
+ t.string :slug_name
27
+ end
23
28
  end
24
29
  end
25
30
 
@@ -33,12 +38,12 @@ end
33
38
 
34
39
  class User < ActiveRecord::Base
35
40
  has_slug [:first_name, :last_name]
41
+ end
36
42
 
37
- def name
38
- [first_name, last_name].compact.join(' ')
39
- end
40
-
41
- def name=(names)
42
- self[:first_name], self[:last_name] = names.split(' ', 2)
43
- end
43
+ class Edge < ActiveRecord::Base
44
+ has_slug 'name', :slug_column => :slug_name,
45
+ :as_param => false,
46
+ :substitution_char => "_",
47
+ :downcase => false,
48
+ :on_conflict => :concat_random_chars
44
49
  end
data/spec/spec_helper.rb CHANGED
@@ -5,7 +5,6 @@ require 'schema'
5
5
 
6
6
  Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
7
7
 
8
-
9
8
  RSpec.configure do |config|
10
9
  # == Mock Framework
11
10
  #
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slugger
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 25
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 0
10
- version: 0.1.0
9
+ - 1
10
+ version: 0.1.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Seth Faxon
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-08-23 00:00:00 Z
18
+ date: 2011-08-24 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: activerecord
@@ -85,7 +85,9 @@ files:
85
85
  - lib/slugger.rb
86
86
  - lib/slugger/version.rb
87
87
  - slugger.gemspec
88
- - spec/lib/slugger_spec.rb
88
+ - spec/lib/edge_spec.rb
89
+ - spec/lib/post_spec.rb
90
+ - spec/lib/user_spec.rb
89
91
  - spec/schema.rb
90
92
  - spec/spec_helper.rb
91
93
  - tasks/spec.rake
@@ -123,6 +125,8 @@ signing_key:
123
125
  specification_version: 3
124
126
  summary: Slugger is yet another slug generator.
125
127
  test_files:
126
- - spec/lib/slugger_spec.rb
128
+ - spec/lib/edge_spec.rb
129
+ - spec/lib/post_spec.rb
130
+ - spec/lib/user_spec.rb
127
131
  - spec/schema.rb
128
132
  - spec/spec_helper.rb
@@ -1,8 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Post do
4
- it "should set slug on create" do
5
- p = Post.create(:title => "hello world")
6
- p.slug.should == "hello-world"
7
- end
8
- end