sluggable 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,64 @@
1
+ = Sluggable
2
+
3
+ == Description
4
+
5
+ This gem allows you to easily create slugs for your ActiveRecord models.
6
+
7
+ == Installation
8
+
9
+ sudo gem install sluggable -s http://gemcutter.org
10
+
11
+ == Usage
12
+
13
+ In `config/environment.rb`, add the following:
14
+
15
+ config.gem 'sluggable', :source => 'http://gemcutter.org'
16
+
17
+ Then, in your ActiveRecord model you just need to include the module
18
+ and provide the column to use when generating the slug. For example,
19
+ if we had a Post model with:
20
+
21
+ class Post < ActiveRecord::Base
22
+ # Columns:
23
+ # title: string
24
+ # body: text
25
+ # slug: string
26
+
27
+ include Sluggable
28
+ slug_column :title
29
+
30
+ before_validation :generate_slug
31
+
32
+ end
33
+
34
+ This sets it up so that the value of the `title` column is used as the
35
+ source when generating the slug. The module also provides a private
36
+ method (`generate_slug`) to generate and store the slug for this record.
37
+ It requires that the table have a column called `slug` and is typically
38
+ called in a lifecycle hook, though it can be called from anywhere you
39
+ need it.
40
+
41
+ == License
42
+
43
+ Copyright (c) 2009 Patrick Reagan (mailto:reaganpr@gmail.com)
44
+
45
+ Permission is hereby granted, free of charge, to any person
46
+ obtaining a copy of this software and associated documentation
47
+ files (the "Software"), to deal in the Software without
48
+ restriction, including without limitation the rights to use,
49
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
50
+ copies of the Software, and to permit persons to whom the
51
+ Software is furnished to do so, subject to the following
52
+ conditions:
53
+
54
+ The above copyright notice and this permission notice shall be
55
+ included in all copies or substantial portions of the Software.
56
+
57
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
58
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
59
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
60
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
61
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
62
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
63
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
64
+ OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/testtask'
4
+
5
+ require 'lib/sluggable/version'
6
+
7
+ spec = Gem::Specification.new do |s|
8
+ s.name = 'sluggable'
9
+ s.version = Sluggable::Version.to_s
10
+ s.has_rdoc = true
11
+ s.extra_rdoc_files = %w(README.rdoc)
12
+ s.rdoc_options = %w(--main README.rdoc)
13
+ s.summary = "This gem provides an easy way to create slugs for your ActiveRecord models"
14
+ s.author = 'Patrick Reagan'
15
+ s.email = 'reaganpr@gmail.com'
16
+ s.homepage = 'http://sneaq.net'
17
+ s.files = %w(README.rdoc Rakefile) + Dir.glob("{lib,test}/**/*")
18
+
19
+ s.add_dependency('activesupport', '>= 2.1.1')
20
+ s.add_dependency('activerecord', '>= 2.1.1')
21
+ end
22
+
23
+ Rake::GemPackageTask.new(spec) do |pkg|
24
+ pkg.gem_spec = spec
25
+ end
26
+
27
+ Rake::TestTask.new do |t|
28
+ t.libs << 'test'
29
+ t.test_files = FileList["test/**/*_test.rb"]
30
+ t.verbose = true
31
+ end
32
+
33
+ begin
34
+ require 'rcov/rcovtask'
35
+
36
+ Rcov::RcovTask.new(:coverage) do |t|
37
+ t.libs = ['test']
38
+ t.test_files = FileList["test/**/*_test.rb"]
39
+ t.verbose = true
40
+ t.rcov_opts = ['--text-report', "-x #{Gem.path}", '-x /Library/Ruby', '-x /usr/lib/ruby']
41
+ end
42
+
43
+ task :default => :coverage
44
+
45
+ rescue LoadError
46
+ warn "\n**** Install rcov (sudo gem install relevance-rcov) to get coverage stats ****\n"
47
+ task :default => :test
48
+ end
49
+
50
+ desc 'Generate the gemspec for the Gem (useful when serving from Github)'
51
+ task :gemspec do
52
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
53
+ File.open(file, 'w') {|f| f << spec.to_ruby }
54
+ puts "Created gemspec: #{file}"
55
+ end
56
+
57
+ task :github => :gemspec
@@ -0,0 +1,7 @@
1
+ class NilClass
2
+
3
+ # The slug for nil is, indeed, an empty string
4
+ def sluggify
5
+ ''
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ class String
2
+
3
+ # Generate the slug version for this string
4
+ def sluggify
5
+ slug = self.downcase.gsub(/[^0-9a-z_ -]/i, '')
6
+ slug = slug.gsub(/\s+/, '-').squeeze('-')
7
+ slug
8
+ end
9
+ end
data/lib/sluggable.rb ADDED
@@ -0,0 +1,51 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'core_ext/nil_class'
4
+ require 'core_ext/string'
5
+
6
+ module Sluggable
7
+ module ClassMethods
8
+
9
+ # Find all the other slugs in the database that don't belong to the
10
+ # record specified by the +:id+ parameter.
11
+ def others_by_slug(id, slug)
12
+ conditions = id.nil? ? {} : {:conditions => ['id != ?', id]}
13
+ find_by_slug(slug, conditions)
14
+ end
15
+
16
+ # Determine the database column to use when generating the slug
17
+ def slug_column(column)
18
+ define_method(:slug_column) do
19
+ column
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ module InstanceMethods
26
+
27
+ def next_available_slug(base_slug) # :nodoc:
28
+ valid_slug = base_slug
29
+
30
+ index = 2
31
+ while self.class.others_by_slug(self.id, valid_slug)
32
+ valid_slug = base_slug + "-#{index}"
33
+ index+= 1
34
+ end
35
+ valid_slug
36
+ end
37
+
38
+ def generate_slug # :nodoc:
39
+ self.slug = next_available_slug(self.send(slug_column).sluggify)
40
+ end
41
+
42
+ private :next_available_slug, :generate_slug
43
+
44
+ end
45
+
46
+ def self.included(other) # :nodoc:
47
+ other.send(:extend, Sluggable::ClassMethods)
48
+ other.send(:include, Sluggable::InstanceMethods)
49
+ end
50
+
51
+ end
@@ -0,0 +1,13 @@
1
+ module Sluggable
2
+ module Version
3
+
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ TINY = 0
7
+
8
+ def self.to_s # :nodoc:
9
+ [MAJOR, MINOR, TINY].join('.')
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ # http://sneaq.net/textmate-wtf
2
+ $:.reject! { |e| e.include? 'TextMate' }
3
+
4
+ require 'rubygems'
5
+ require 'throat_punch'
6
+
7
+ require File.dirname(__FILE__) + '/../lib/sluggable'
@@ -0,0 +1,13 @@
1
+ require File.dirname(__FILE__) + '/../../test_helper'
2
+
3
+ class NilClassTest < Test::Unit::TestCase
4
+
5
+ context "An instance of NilClass" do
6
+
7
+ should "return an empty string as a slug" do
8
+ assert_equal '', nil.sluggify
9
+ end
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,31 @@
1
+ require File.dirname(__FILE__) + '/../../test_helper'
2
+
3
+ class StringTest < Test::Unit::TestCase
4
+
5
+ context "An instance of String" do
6
+ context "when creating a slug" do
7
+
8
+ should "remove non-word characters" do
9
+ assert_equal 'postname', '!#pos$%t+name'.sluggify
10
+ end
11
+
12
+ should "transform a space into a dash" do
13
+ assert_equal 'post-name', 'post name'.sluggify
14
+ end
15
+
16
+ should "transform multiple spaces into a single dash" do
17
+ assert_equal 'post-name', "post name".sluggify
18
+ end
19
+
20
+ should "downcase the original string when creating the slug" do
21
+ assert_equal 'postname', 'PostName'.sluggify
22
+ end
23
+
24
+ should "consolidate multiple dashes in the output" do
25
+ assert_equal 'post-name', 'post- name'.sluggify
26
+ end
27
+
28
+ end
29
+ end
30
+
31
+ end
@@ -0,0 +1,80 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class SluggableImplementation
4
+ include Sluggable
5
+ end
6
+
7
+ class SluggableImplementationTest < Test::Unit::TestCase
8
+
9
+ def stub_others_by_slug(id, *slugs)
10
+ slugs.each do |slug|
11
+ return_val = (slug == slugs.last) ? nil : stub()
12
+ SluggableImplementation.stubs(:others_by_slug).with(@id, slug).returns(return_val)
13
+ end
14
+ end
15
+
16
+ context "The SluggableImplmentation class" do
17
+ setup do
18
+ @object = stub()
19
+ @slug = 'sample-slug'
20
+ end
21
+
22
+ should "be able to find others by slug when provided an ID" do
23
+ id = 1
24
+ SluggableImplementation.expects(:find_by_slug).with(@slug, {:conditions => ['id != ?', id]}).returns(@object)
25
+ assert_equal @object, SluggableImplementation.others_by_slug(id, @slug)
26
+ end
27
+
28
+ should "be able to find others by slug when not provided an ID" do
29
+ SluggableImplementation.expects(:find_by_slug).with(@slug, {}).returns(@object)
30
+ assert_equal @object, SluggableImplementation.others_by_slug(nil, @slug)
31
+ end
32
+
33
+ should "be able to record the column for use when generating the slug" do
34
+ SluggableImplementation.slug_column :title
35
+ @sluggable = SluggableImplementation.new
36
+
37
+ assert_equal :title, @sluggable.slug_column
38
+ end
39
+
40
+ end
41
+
42
+ context "An instance of SluggableImplementation" do
43
+
44
+ setup do
45
+ @id = 1
46
+ @slug = 'title'
47
+
48
+ @sluggable = SluggableImplementation.new
49
+ @sluggable.stubs(:id).returns(@id)
50
+ end
51
+
52
+ should "know the next available slug when the original is taken" do
53
+ stub_others_by_slug(@id, 'title', 'title-2')
54
+ assert_equal "title-2", @sluggable.send(:next_available_slug, 'title')
55
+ end
56
+
57
+ should "incrementally suggest slugs until it finds an available one" do
58
+ stub_others_by_slug(@id, 'title', 'title-2', 'title-3')
59
+ assert_equal 'title-3', @sluggable.send(:next_available_slug, 'title')
60
+ end
61
+
62
+ should "know not to suggest an incremental slug when the existing slug belongs to the current record" do
63
+ SluggableImplementation.stubs(:id).with().returns(@id)
64
+ SluggableImplementation.stubs(:others_by_slug).with(@id, @slug).returns(nil)
65
+
66
+ assert_equal @slug, @sluggable.send(:next_available_slug, @slug)
67
+ end
68
+
69
+ should "be able to assign a valid slug to the slug property" do
70
+ @sluggable.stubs(:slug_column).with().returns(:title)
71
+ @sluggable.stubs(:title).with().returns(stub(:sluggify => @slug))
72
+ @sluggable.stubs(:next_available_slug).with(@slug).returns(@slug)
73
+ @sluggable.expects(:slug=).with(@slug)
74
+
75
+ @sluggable.send(:generate_slug)
76
+ end
77
+
78
+ end
79
+
80
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sluggable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Patrick Reagan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-30 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activesupport
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.1.1
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: activerecord
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.1.1
34
+ version:
35
+ description:
36
+ email: reaganpr@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.rdoc
43
+ files:
44
+ - README.rdoc
45
+ - Rakefile
46
+ - lib/core_ext/nil_class.rb
47
+ - lib/core_ext/string.rb
48
+ - lib/sluggable/version.rb
49
+ - lib/sluggable.rb
50
+ - test/test_helper.rb
51
+ - test/unit/core_ext/nil_class_test.rb
52
+ - test/unit/core_ext/string_test.rb
53
+ - test/unit/sluggable_test.rb
54
+ has_rdoc: true
55
+ homepage: http://sneaq.net
56
+ licenses: []
57
+
58
+ post_install_message:
59
+ rdoc_options:
60
+ - --main
61
+ - README.rdoc
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ version:
76
+ requirements: []
77
+
78
+ rubyforge_project:
79
+ rubygems_version: 1.3.4
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: This gem provides an easy way to create slugs for your ActiveRecord models
83
+ test_files: []
84
+