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