i76-has_slug 0.1.1

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/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Bill Eisenhauer & Andre Lewis
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,123 @@
1
+ = has_slug
2
+
3
+ == Description
4
+
5
+ has_slug is a plugin that add's slugging capabilities to ActiveRecord models.
6
+ It allows you to create SEO friendly URL's that will work the same as the
7
+ numeric defaults Rails provides you.
8
+
9
+ Using has_slug, you can make this:
10
+
11
+ http://example.com/articles/1
12
+
13
+ Look like this:
14
+
15
+ http://example.com/articles/1-first-post
16
+
17
+ Or even better:
18
+
19
+ http://example.com/articles/first-post
20
+
21
+ has_slug is inspired by friendly_id (http://github.com/norman/friendly_id/tree/master),
22
+ but instead of adding a new table for the slugs it uses a single column per model.
23
+
24
+ === Why?
25
+
26
+ * Text-based id's look better
27
+ * They make URL's easier to remember.
28
+ * They give no hint about the number of records in your database.
29
+ * They are better for search engine optimization.
30
+
31
+ === But ...
32
+
33
+ * It can be tricky to ensure they're always unique.
34
+ * They can change, breaking your URL's and your SEO.
35
+ * They can conflict with your application's namespace.
36
+
37
+ has_slug takes care of creating unique slugs by using a counter. The first time
38
+ a slug is generated for "Some Title", it will become "some-title". The second
39
+ time this is done, it will use the slug "some-title_2".
40
+
41
+ has_slug DOES NOT take care of changing slugs! If you need to care of this,
42
+ either use the friendly_id plugin, which DOES take care of this, or use a
43
+ custom solution.
44
+
45
+ has_slug also doesn't take care of the namespace conflicts (a slug named
46
+ 'new' for instance), you could use the following technique if this is
47
+ required: http://henrik.nyh.se/2008/10/validating-slugs-against-existing-routes-in-rails
48
+
49
+ == Usage
50
+
51
+ === Typical usage (with slug column)
52
+
53
+ Blog posts have a distinct, but not necessarily unique title stored in a column
54
+ in the database. If we add a slug column to the posts table, we can use has_slug
55
+ to generate slugs automatically.
56
+
57
+ class Post < ActiveRecord::Base
58
+ has_slug :title
59
+ end
60
+
61
+ We can then use the standard url helpers to generate a SEO friendly URL:
62
+
63
+ @post_1 = Post.create(:title => 'First Post',
64
+ :description => 'Description ...')
65
+
66
+ @post_2 = Post.create(:title => 'First Post',
67
+ :description => 'Description ...')
68
+
69
+ url_for(@post_1)
70
+ # => http://example.com/posts/first-post
71
+
72
+ url_for(@post_2)
73
+ # => http://example.com/posts/first-post_2
74
+
75
+ And use the standard finder to find the corresponding post:
76
+
77
+ @post = Post.find('first-post')
78
+
79
+ You can also use the <code>found_by_slug?</code> method to find out if the
80
+ record was found by the slug. You could use this to redirect to the URL with
81
+ the slug for SEO purposes.
82
+
83
+ redirect_to(@post, :status => 301) unless @post.found_by_slug?
84
+
85
+ == Typical usage (without slug column)
86
+
87
+ We can also use has_slug without adding the slug column. If we do this, the id
88
+ of the record is prepended to the slug.
89
+
90
+ @post = Post.find(1)
91
+
92
+ url_for(@post)
93
+ # => http://example.com/posts/1-first-post
94
+
95
+ == Scoped usage
96
+
97
+ Restaurants belong to a city. They have a unique name, but only in the city they
98
+ are in. If we add a slug column to the restaurants table, we can use has_slug
99
+ to generate scoped slugs automatically.
100
+
101
+ class Restaurant < ActiveRecord::Base
102
+ belongs_to :city
103
+
104
+ has_slug :name, :scope => :city
105
+ end
106
+
107
+ We can then use the standard url helpers to generate a SEO friendly URL:
108
+
109
+ @restaurant_1 = Restaurant.create(:name => 'Da Marco',
110
+ :city => City.find('new-york'))
111
+
112
+ @restaurant_2 = Restaurant.create(:name => 'Da Marco',
113
+ :city => City.find('san-fransisco'))
114
+
115
+ url_for(@restaurant_1.city, @restaurant)
116
+ # => http://example.com/cities/new-york/restaurants/da-marco
117
+
118
+ url_for(@restaurant_2.city, @restaurant)
119
+ # => http://example.com/cities/san-fransisco/restaurants/da-marco
120
+
121
+ == Authors
122
+
123
+ * Tom-Eric Gerritsen (tomeric@i76.nl)
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the has_slug plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for the has_slug plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'has_slug'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'has_slug'
data/lib/has_slug.rb ADDED
@@ -0,0 +1,63 @@
1
+ require 'unicode'
2
+ require 'has_slug/slug'
3
+
4
+ # has_slug is a slugging library for Ruby on Rails
5
+ module HasSlug
6
+
7
+ # Load has_slug if the gem is included
8
+ def self.enable
9
+ return if ActiveRecord::Base.methods.include? 'has_slug'
10
+
11
+ ActiveRecord::Base.class_eval { extend HasSlug::ClassMethods }
12
+ end
13
+
14
+ module ClassMethods
15
+
16
+ # Valid options for the has_slug method
17
+ VALID_HAS_SLUG_OPTIONS = [:scope, :slug_column].freeze
18
+
19
+ # Default options for the has_slug method
20
+ DEFAULT_HAS_SLUG_OPTIONS = { :scope => nil, :slug_column => 'slug' }.freeze
21
+
22
+ # Set up an ActiveRecord model to use a slug.
23
+ #
24
+ # The attribute argument can be one of your model's columns, or a method
25
+ # you use to generate the slug.
26
+ #
27
+ # Options:
28
+ # * <tt>:scope</tt> - The scope of the slug
29
+ # * <tt>:slug_column</tt> - The column that will be used to store the slug in (defaults to slug)
30
+ def has_slug(attribute, options = {})
31
+ options.assert_valid_keys(VALID_HAS_SLUG_OPTIONS)
32
+
33
+ options = DEFAULT_HAS_SLUG_OPTIONS.merge(options).merge(:attribute => attribute)
34
+
35
+ if defined?(has_slug_options)
36
+ raise Exception, "has_slug_options is already defined, you can only call has_slug once"
37
+ end
38
+
39
+ write_inheritable_attribute(:has_slug_options, options)
40
+ class_inheritable_reader(:has_slug_options)
41
+
42
+ if columns.any? { |column| column.name.to_s == options[:slug_column].to_s }
43
+ require 'has_slug/sluggable_class_methods'
44
+ require 'has_slug/sluggable_instance_methods'
45
+
46
+ extend SluggableClassMethods
47
+ include SluggableInstanceMethods
48
+
49
+ before_save :set_slug, :if => :new_slug_needed?
50
+ else
51
+ require 'has_slug/not_sluggable_class_methods'
52
+ require 'has_slug/not_sluggable_instance_methods'
53
+
54
+ extend NotSluggableClassMethods
55
+ include NotSluggableInstanceMethods
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ if defined?(ActiveRecord)
62
+ HasSlug::enable
63
+ end
@@ -0,0 +1,56 @@
1
+ module HasSlug::NotSluggableClassMethods
2
+
3
+ def self.extended(base)
4
+ class << base
5
+ alias_method_chain :find_one, :slug
6
+ alias_method_chain :find_some, :slug
7
+ end
8
+ end
9
+
10
+ def find_one_with_slug(id_or_slug, options = {})
11
+ return find_one_without_slug(id_or_slug, options) if id_or_slug.is_a?(Fixnum)
12
+
13
+ if match = id_or_slug.match(/^([0-9]+)/)
14
+ result = find_one_without_slug(match[1].to_i, options)
15
+ result.found_by_slug! if id_or_slug == result.slug
16
+ else
17
+ result = find_one_without_slug(id_or_slug, options)
18
+ end
19
+
20
+ return result
21
+ end
22
+
23
+ def find_some_with_slug(ids_or_slugs, options = {})
24
+ return find_some_without_slug(ids_or_slugs, options) if ids_or_slugs.all? { |x| x.is_a?(Fixnum) }
25
+
26
+ ids = ids_or_slugs.map do |id_or_slug|
27
+ if match = id_or_slug.to_s.match(/^([0-9]+)/)
28
+ match[1].to_i
29
+ else
30
+ id_or_slug
31
+ end
32
+ end
33
+
34
+ find_options = options.dup
35
+
36
+ find_options[:conditions] ||= [""]
37
+ find_options[:conditions] = [find_options[:conditions] + " AND "] if find_options[:conditions].is_a?(String)
38
+
39
+ find_options[:conditions][0] << "#{quoted_table_name}.#{primary_key} IN (?)"
40
+ find_options[:conditions] << ids
41
+
42
+ found = find_every(find_options)
43
+ expected = ids_or_slugs.map(&:to_s).uniq
44
+
45
+ unless found.size == expected.size
46
+ raise ActiveRecord::RecordNotFound, "Couldn't find all #{ name.pluralize } with IDs (#{ ids_or_slugs * ', ' }) AND #{ sanitize_sql options[:conditions] } (found #{ found.size } results, but was looking for #{ expected.size })"
47
+ end
48
+
49
+ ids_or_slugs.each do |slug|
50
+ slug_record = found.detect { |record| record.slug == slug }
51
+ slug_record.found_by_slug! if slug_record
52
+ end
53
+
54
+ found
55
+ end
56
+ end
@@ -0,0 +1,26 @@
1
+ module HasSlug::NotSluggableInstanceMethods
2
+ attr :found_by_slug
3
+
4
+ def found_by_slug!
5
+ @found_by_slug = true
6
+ end
7
+
8
+ def found_by_slug?
9
+ @found_by_slug
10
+ end
11
+
12
+ def sluggable
13
+ read_attribute(self.class.has_slug_options[:attribute])
14
+ end
15
+
16
+ def slug
17
+ id = self.send(self.class.primary_key)
18
+ slug = self.sluggable.to_slug
19
+
20
+ "#{id}-#{slug}"
21
+ end
22
+
23
+ def to_param
24
+ self.slug
25
+ end
26
+ end
@@ -0,0 +1,35 @@
1
+ class String
2
+ # convert strings to slugs that are lowercase an only contain alphanumeric
3
+ # characters, dashes and sometimes dots
4
+ def to_slug
5
+ # Transliterate
6
+ slug = Unicode.normalize_KD(self).gsub(/[^\x00-\x7F]/n,'')
7
+
8
+ # Convert to lowercase
9
+ slug.downcase!
10
+
11
+ # Change seperators (like spaces) to dashes
12
+ slug.gsub!(/[+_,\.\/|;; ]/, '-')
13
+
14
+ # Dot's should be saved only when they have letter or number on both sides
15
+ # (this preserves file extensions)
16
+ # slug.gsub!(/[^\w\d]\.+/, '-')
17
+ # slug.gsub!(/\.+[^\w\d]/, '-')
18
+
19
+ # Strip dots from begining and end
20
+ slug.gsub!(/^\.+/, '')
21
+ slug.gsub!(/\.+$/, '')
22
+
23
+ # Change multiple succesive dashes to single dashe.
24
+ slug.gsub!(/-+/, '-')
25
+
26
+ # Strip dashes from begining and end
27
+ slug.gsub!(/^-+/, '')
28
+ slug.gsub!(/-+$/, '')
29
+
30
+ # Remove everything that is not letter, number, dash or dot.
31
+ slug.gsub!(/[^\w\d.-]/, '')
32
+
33
+ slug
34
+ end
35
+ end
@@ -0,0 +1,76 @@
1
+ module HasSlug::SluggableClassMethods
2
+
3
+ def self.extended(base) #:nodoc:#
4
+ class << base
5
+ alias_method_chain :find_one, :slug
6
+ alias_method_chain :find_some, :slug
7
+ end
8
+ end
9
+
10
+ # Find a single record that has the same slug as the given record's slug
11
+ def find_one_with_same_slug(object)
12
+ slug_column = has_slug_options[:slug_column]
13
+
14
+ options = if object.new_record? then {}
15
+ else { :conditions => ["#{quoted_table_name}.#{primary_key} != ?", object] }
16
+ end
17
+
18
+ if scope = has_slug_options[:scope]
19
+ scope_attribute = "#{scope}_id" if !columns.any? { |c| c.name == scope } &&
20
+ columns.any? { |c| c.name == "#{scope}_id" }
21
+
22
+ result = send("find_by_#{slug_column}_and_#{scope_attribute}",
23
+ object.slug, object.send(scope), options)
24
+ else
25
+ result = send("find_by_#{slug_column}", object.slug, options)
26
+ end
27
+
28
+ result.found_by_slug! if result
29
+
30
+ result
31
+ end
32
+
33
+ # Find a single record using the record's slug or the record's id
34
+ def find_one_with_slug(id_or_slug, options = {})
35
+ return find_one_without_slug(id_or_slug, options) if id_or_slug.is_a?(Fixnum)
36
+
37
+ slug_column = has_slug_options[:slug_column]
38
+
39
+ if result = send("find_by_#{slug_column}", id_or_slug, options)
40
+ result.found_by_slug!
41
+ else
42
+ result = find_one_without_slug(id_or_slug, options)
43
+ end
44
+
45
+ result
46
+ end
47
+
48
+ # Find multiple records using the records slugs or the records id's
49
+ def find_some_with_slug(ids_or_slugs, options = {})
50
+ return find_some_without_slug(ids_or_slugs, options) if ids_or_slugs.all? { |x| x.is_a?(Fixnum) }
51
+
52
+ find_options = options.dup
53
+
54
+ find_options[:conditions] ||= [""]
55
+ find_options[:conditions] = [find_options[:conditions] + " AND "] if find_options[:conditions].is_a?(String)
56
+ find_options[:conditions][0] << "(#{quoted_table_name}.#{primary_key} IN (?)" <<
57
+ " OR #{quoted_table_name}.#{has_slug_options[:slug_column]} IN (?))"
58
+
59
+ find_options[:conditions] << ids_or_slugs
60
+ find_options[:conditions] << ids_or_slugs.map(&:to_s)
61
+
62
+ found = find_every(find_options)
63
+ expected = ids_or_slugs.map(&:to_s).uniq
64
+
65
+ unless found.size == expected.size
66
+ raise ActiveRecord::RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids_or_slugs * ', '}) AND #{sanitize_sql(options[:conditions])} (found #{found.size} results, but was looking for #{expected.size})"
67
+ end
68
+
69
+ ids_or_slugs.each do |slug|
70
+ slug_record = found.detect { |record| record.send(has_slug_options[:slug_column]) == slug }
71
+ slug_record.found_by_slug! if slug_record
72
+ end
73
+
74
+ found
75
+ end
76
+ end
@@ -0,0 +1,45 @@
1
+ module HasSlug::SluggableInstanceMethods
2
+ attr :found_by_slug
3
+
4
+ def found_by_slug!
5
+ @found_by_slug = true
6
+ end
7
+
8
+ def found_by_slug?
9
+ @found_by_slug
10
+ end
11
+
12
+ def sluggable
13
+ read_attribute(self.class.has_slug_options[:attribute])
14
+ end
15
+
16
+ def slug
17
+ read_attribute(self.class.has_slug_options[:slug_column])
18
+ end
19
+
20
+ def slug=(slug)
21
+ write_attribute(self.class.has_slug_options[:slug_column], slug)
22
+ end
23
+
24
+ def new_slug_needed?
25
+ self.new_record? || self.sluggable.to_slug != self.slug
26
+ end
27
+
28
+ def to_param
29
+ self.slug || self.id
30
+ end
31
+
32
+ private
33
+
34
+ def set_slug
35
+ self.slug = self.sluggable.to_slug
36
+
37
+ while existing = self.class.find_one_with_same_slug(self)
38
+ index ||= 2
39
+
40
+ self.slug = "#{self.sluggable.to_slug}_#{index}"
41
+
42
+ index += 1
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :has_slug do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,3 @@
1
+ Factory.define :city do |city|
2
+ city.name 'New York'
3
+ end
@@ -0,0 +1,3 @@
1
+ Factory.define :kitchen do |kitchen|
2
+ kitchen.name 'Italian'
3
+ end
@@ -0,0 +1,5 @@
1
+ Factory.define :restaurant do |restaurant|
2
+ restaurant.name 'Da Marco'
3
+ restaurant.city { |city| city.association(:city) }
4
+ restaurant.kitchen { |kitchen| kitchen.association(:kitchen) }
5
+ end
@@ -0,0 +1,8 @@
1
+ class City < ActiveRecord::Base
2
+ has_slug :name
3
+
4
+ has_many :restaurants
5
+
6
+ has_many :kitchens,
7
+ :through => :restaurants
8
+ end
@@ -0,0 +1,5 @@
1
+ class Kitchen < ActiveRecord::Base
2
+ has_slug :name
3
+
4
+ belongs_to :restaurant
5
+ end
@@ -0,0 +1,8 @@
1
+ class Restaurant < ActiveRecord::Base
2
+ has_slug :name,
3
+ :scope => :city
4
+
5
+ belongs_to :city
6
+
7
+ belongs_to :kitchen
8
+ end
data/test/schema.rb ADDED
@@ -0,0 +1,17 @@
1
+ ActiveRecord::Schema.define(:version => 1) do
2
+ create_table 'cities', :force => true do |t|
3
+ t.column 'name', :string
4
+ t.column 'slug', :string
5
+ end
6
+
7
+ create_table 'restaurants', :force => true do |t|
8
+ t.column 'name', :string
9
+ t.column 'slug', :string
10
+ t.column 'city_id', :integer
11
+ t.column 'kitchen_id', :integer
12
+ end
13
+
14
+ create_table 'kitchens', :force => true do |t|
15
+ t.column 'name', :string
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'sqlite3'
4
+ require 'activerecord'
5
+ require 'shoulda'
6
+ require 'factory_girl'
7
+
8
+ HAS_SLUG_ROOT = File.dirname(__FILE__) + "/.."
9
+ $:.unshift("#{HAS_SLUG_ROOT}/lib")
10
+
11
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3",
12
+ :dbfile => ":memory:")
13
+ require 'has_slug'
14
+ require "#{HAS_SLUG_ROOT}/test/schema.rb"
15
+
16
+ Dir["#{HAS_SLUG_ROOT}/test/models/*"].each { |f| require f }
17
+ Dir["#{HAS_SLUG_ROOT}/test/factories/*"].each { |f| require f }
18
+
19
+ class Test::Unit::TestCase
20
+ def reset_database!
21
+ City.destroy_all
22
+ Restaurant.destroy_all
23
+ Kitchen.destroy_all
24
+ end
25
+ end
@@ -0,0 +1,133 @@
1
+ require "#{File.dirname(__FILE__)}/../test_helper"
2
+
3
+ class HasSlugTest < Test::Unit::TestCase
4
+ context 'A model' do
5
+ setup do
6
+ reset_database!
7
+ end
8
+
9
+ context 'without a slug column' do
10
+ setup do
11
+ @italian = Factory(:kitchen, :name => 'Italian')
12
+ @french = Factory(:kitchen, :name => 'French')
13
+ end
14
+
15
+ should 'set the slug' do
16
+ assert_not_nil @italian.slug
17
+ end
18
+
19
+ should 'return the slug on call to #to_param' do
20
+ assert_equal @italian.slug, @italian.to_param
21
+ end
22
+
23
+ should 'still find by id' do
24
+ kitchen = Kitchen.find(@italian.id)
25
+
26
+ assert_equal @italian, kitchen
27
+ assert !kitchen.found_by_slug?
28
+ end
29
+
30
+ should 'find one by slug' do
31
+ kitchen = Kitchen.find(@italian.slug)
32
+
33
+ assert_equal @italian, kitchen
34
+ assert kitchen.found_by_slug?
35
+ end
36
+
37
+ should 'still find some by id' do
38
+ kitchens = Kitchen.find([@italian.id, @french.id])
39
+
40
+ assert_equal 2, kitchens.length
41
+ assert !kitchens.any?(&:found_by_slug?)
42
+ end
43
+
44
+ should 'find some by slug' do
45
+ kitchens = Kitchen.find([@italian.slug, @french.slug])
46
+
47
+ assert_equal 2, kitchens.length
48
+ assert kitchens.all?(&:found_by_slug?)
49
+ end
50
+
51
+ should 'find some by id or slug' do
52
+ kitchens = Kitchen.find([@italian.id, @french.slug])
53
+
54
+ assert_equal 2, kitchens.length
55
+ assert !kitchens[0].found_by_slug?
56
+ assert kitchens[1].found_by_slug?
57
+ end
58
+ end
59
+
60
+ context 'with a slug column' do
61
+ setup do
62
+ @new_york = Factory(:city, :name => 'New York')
63
+ @san_fransisco = Factory(:city, :name => 'San Fransisco')
64
+ end
65
+
66
+ context 'and a scope' do
67
+ setup do
68
+ @da_marco = Factory(:restaurant, :name => 'Da Marco',
69
+ :city => @new_york)
70
+ end
71
+
72
+ should 'create the same slug in a different scope' do
73
+ @da_marco_2 = Factory(:restaurant, :name => 'Da Marco',
74
+ :city => @san_fransisco)
75
+
76
+ assert_equal @da_marco_2.slug, @da_marco.slug
77
+ end
78
+ end
79
+
80
+ should 'set the slug' do
81
+ assert_equal 'new-york', @new_york.slug
82
+ assert_equal 'san-fransisco', @san_fransisco.slug
83
+ end
84
+
85
+ should 'return the slug on call to #to_param' do
86
+ assert_equal @new_york.slug, @new_york.to_param
87
+ assert_equal @san_fransisco.slug, @san_fransisco.to_param
88
+ end
89
+
90
+ should 'not create duplicate slugs' do
91
+ @new_york_2 = Factory(:city, :name => 'New-York')
92
+
93
+ assert_not_equal @new_york_2.slug, @new_york.slug
94
+ end
95
+
96
+ should 'still find by id' do
97
+ city = City.find(@new_york.id)
98
+
99
+ assert_equal @new_york, city
100
+ assert !city.found_by_slug?
101
+ end
102
+
103
+ should 'find one by slug' do
104
+ city = City.find(@new_york.slug)
105
+
106
+ assert_equal @new_york, city
107
+ assert city.found_by_slug?
108
+ end
109
+
110
+ should 'still find some by id' do
111
+ cities = City.find([@new_york.id, @san_fransisco.id])
112
+
113
+ assert_equal 2, cities.length
114
+ assert !cities.any?(&:found_by_slug?)
115
+ end
116
+
117
+ should 'find some by slug' do
118
+ cities = City.find([@new_york.slug, @san_fransisco.slug])
119
+
120
+ assert_equal 2, cities.length
121
+ assert cities.all?(&:found_by_slug?)
122
+ end
123
+
124
+ should 'find some by id or slug' do
125
+ cities = City.find([@new_york.id, @san_fransisco.slug])
126
+
127
+ assert_equal 2, cities.length
128
+ assert !cities[0].found_by_slug?
129
+ assert cities[1].found_by_slug?
130
+ end
131
+ end
132
+ end
133
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: i76-has_slug
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Tom-Eric Gerritsen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-12-29 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: unicode
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ version:
24
+ description: has_slug is a plugin that provides slugging capabilities to Ruby on Rails models. Inspired by the friendly_id plugin.
25
+ email: tomeric@i76.nl
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files: []
31
+
32
+ files:
33
+ - init.rb
34
+ - MIT-LICENSE
35
+ - Rakefile
36
+ - README.rdoc
37
+ - lib/has_slug.rb
38
+ - lib/has_slug/slug.rb
39
+ - lib/has_slug/not_sluggable_class_methods.rb
40
+ - lib/has_slug/not_sluggable_instance_methods.rb
41
+ - lib/has_slug/sluggable_class_methods.rb
42
+ - lib/has_slug/sluggable_instance_methods.rb
43
+ - tasks/has_slug_tasks.rake
44
+ has_rdoc: true
45
+ homepage: http://www.i76.nl/
46
+ post_install_message:
47
+ rdoc_options:
48
+ - --main
49
+ - README.rdoc
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.2.0
68
+ signing_key:
69
+ specification_version: 2
70
+ summary: A slugging plugin for Ruby on Rails
71
+ test_files:
72
+ - test/schema.rb
73
+ - test/test_helper.rb
74
+ - test/factories/city_factory.rb
75
+ - test/factories/kitchen_factory.rb
76
+ - test/factories/restaurant_factory.rb
77
+ - test/models/city.rb
78
+ - test/models/kitchen.rb
79
+ - test/models/restaurant.rb
80
+ - test/unit/has_slug_test.rb