nateabbott-friendly-id 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+
3
+ module FriendlyId #:nodoc:
4
+ module Version #:nodoc:
5
+ MAJOR = 2
6
+ MINOR = 2
7
+ TINY = 1
8
+ STRING = [MAJOR, MINOR, TINY].join('.')
9
+ end
10
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ namespace :friendly_id do
4
+ desc "Make slugs for a model."
5
+ task :make_slugs => :environment do
6
+ raise 'USAGE: rake friendly_id:make_slugs MODEL=MyModelName' if ENV["MODEL"].nil?
7
+ if !sluggable_class.friendly_id_options[:use_slug]
8
+ raise "Class \"#{sluggable_class.to_s}\" doesn't appear to be using slugs"
9
+ end
10
+ while records = sluggable_class.find(:all, :include => :slugs, :conditions => "slugs.id IS NULL", :limit => 1000) do
11
+ break if records.size == 0
12
+ records.each do |r|
13
+ r.send(:set_slug)
14
+ r.save!
15
+ puts "#{sluggable_class.to_s}(#{r.id}) friendly_id set to \"#{r.slug.name}\""
16
+ end
17
+ end
18
+ end
19
+
20
+ desc "Regenereate slugs for a model."
21
+ task :redo_slugs => :environment do
22
+ raise 'USAGE: rake friendly_id:redo_slugs MODEL=MyModelName' if ENV["MODEL"].nil?
23
+ if !sluggable_class.friendly_id_options[:use_slug]
24
+ raise "Class \"#{sluggable_class.to_s}\" doesn't appear to be using slugs"
25
+ end
26
+ Slug.destroy_all(["sluggable_type = ?", sluggable_class.to_s])
27
+ Rake::Task["friendly_id:make_slugs"].invoke
28
+ end
29
+
30
+ desc "Kill obsolete slugs older than 45 days."
31
+ task :remove_old_slugs => :environment do
32
+ if ENV["DAYS"].nil?
33
+ @days = 45
34
+ else
35
+ @days = ENV["DAYS"].to_i
36
+ end
37
+ slugs = Slug.find(:all, :conditions => ["created_at < ?", DateTime.now - @days.days])
38
+ slugs.each do |s|
39
+ s.destroy if !s.is_most_recent?
40
+ end
41
+ end
42
+ end
43
+
44
+ def sluggable_class
45
+ if (ENV["MODEL"].split('::').size > 1)
46
+ ENV["MODEL"].split('::').inject(Kernel) {|scope, const_name| scope.const_get(const_name)}
47
+ else
48
+ Object.const_get(ENV["MODEL"])
49
+ end
50
+ end
@@ -0,0 +1 @@
1
+ load 'tasks/friendly_id.rake'
@@ -0,0 +1,109 @@
1
+ # encoding: utf-8!
2
+ require "mocha"
3
+
4
+ require File.dirname(__FILE__) + '/test_helper'
5
+
6
+ class CachedSlugModelTest < Test::Unit::TestCase
7
+
8
+ context "A slugged model with a cached_slugs column" do
9
+
10
+ setup do
11
+ City.delete_all
12
+ Slug.delete_all
13
+ @paris = City.new(:name => "Paris")
14
+ @paris.save!
15
+ end
16
+
17
+ should "have a slug" do
18
+ assert_not_nil @paris.slug
19
+ end
20
+
21
+ should "have a cached slug" do
22
+ assert_not_nil @paris.my_slug
23
+ end
24
+
25
+ should "have a to_param method that returns the cached slug" do
26
+ assert_equal "paris", @paris.to_param
27
+ end
28
+
29
+ should "protect the cached slug value" do
30
+ @paris.update_attributes(:my_slug => "Madrid")
31
+ @paris.reload
32
+ assert_equal "paris", @paris.my_slug
33
+ end
34
+
35
+ should "cache the incremented sequence for duplicate slug names" do
36
+ @paris2 = City.create!(:name => "Paris")
37
+ assert_equal 2, @paris2.slug.sequence
38
+ assert_equal "paris--2", @paris2.my_slug
39
+ end
40
+
41
+ should "not update the cached slug column if it has not changed" do
42
+ @paris.population = 10_000_000
43
+ @paris.expects(:my_slug=).never
44
+ @paris.save
45
+ end
46
+
47
+
48
+ context "found by its friendly id" do
49
+
50
+ setup do
51
+ @paris = City.find(@paris.friendly_id)
52
+ end
53
+
54
+ should "not indicate that it has a better id" do
55
+ assert !@paris.has_better_id?
56
+ end
57
+
58
+ end
59
+
60
+ context "found by its numeric id" do
61
+
62
+ setup do
63
+ @paris = City.find(@paris.id)
64
+ end
65
+
66
+ should "indicate that it has a better id" do
67
+ assert @paris.has_better_id?
68
+ end
69
+
70
+ end
71
+
72
+
73
+ context "with a new slug" do
74
+
75
+ setup do
76
+ @paris.name = "Paris, France"
77
+ @paris.save!
78
+ @paris.reload
79
+ end
80
+
81
+ should "have its cached slug updated" do
82
+ assert_equal "paris-france", @paris.my_slug
83
+ end
84
+
85
+ should "have its cached slug synchronized with its friendly_id" do
86
+ assert_equal @paris.my_slug, @paris.friendly_id
87
+ end
88
+
89
+ end
90
+
91
+
92
+ context "with a cached_slug column" do
93
+
94
+ setup do
95
+ District.delete_all
96
+ @district = District.new(:name => "Latin Quarter")
97
+ @district.save!
98
+ end
99
+
100
+ should "have its cached_slug filled automatically" do
101
+ assert_equal @district.cached_slug, "latin-quarter"
102
+ end
103
+
104
+ end
105
+
106
+ end
107
+
108
+ end
109
+
data/test/contest.rb ADDED
@@ -0,0 +1,94 @@
1
+ # License
2
+ # -------
3
+ #
4
+ # Contest is copyright (c) 2009 Damian Janowski and Michel Martens for
5
+ # Citrusbyte
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person
8
+ # obtaining a copy of this software and associated documentation
9
+ # files (the "Software"), to deal in the Software without
10
+ # restriction, including without limitation the rights to use,
11
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ # copies of the Software, and to permit persons to whom the
13
+ # Software is furnished to do so, subject to the following
14
+ # conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be
17
+ # included in all copies or substantial portions of the Software.
18
+ #
19
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
21
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26
+ # OTHER DEALINGS IN THE SOFTWARE.
27
+ require "test/unit"
28
+
29
+ # Test::Unit loads a default test if the suite is empty, whose purpose is to
30
+ # fail. Since having empty contexts is a common practice, we decided to
31
+ # overwrite TestSuite#empty? in order to allow them. Having a failure when no
32
+ # tests have been defined seems counter-intuitive.
33
+ class Test::Unit::TestSuite
34
+ def empty?
35
+ false
36
+ end
37
+ end
38
+
39
+ # Contest adds +teardown+, +test+ and +context+ as class methods, and the
40
+ # instance methods +setup+ and +teardown+ now iterate on the corresponding
41
+ # blocks. Note that all setup and teardown blocks must be defined with the
42
+ # block syntax. Adding setup or teardown instance methods defeats the purpose
43
+ # of this library.
44
+ class Test::Unit::TestCase
45
+ def self.setup(&block)
46
+ define_method :setup do
47
+ super(&block)
48
+ instance_eval(&block)
49
+ end
50
+ end
51
+
52
+ def self.teardown(&block)
53
+ define_method :teardown do
54
+ instance_eval(&block)
55
+ super(&block)
56
+ end
57
+ end
58
+
59
+ def self.context(name, &block)
60
+ subclass = Class.new(self)
61
+ remove_tests(subclass)
62
+ subclass.class_eval(&block)
63
+ const_set(context_name(name), subclass)
64
+ end
65
+
66
+ def self.test(name, &block)
67
+ define_method(test_name(name), &block)
68
+ end
69
+
70
+ class << self
71
+ alias_method :should, :test
72
+ alias_method :describe, :context
73
+ end
74
+
75
+ private
76
+
77
+ def self.context_name(name)
78
+ "Test#{sanitize_name(name).gsub(/(^| )(\w)/) { $2.upcase }}".to_sym
79
+ end
80
+
81
+ def self.test_name(name)
82
+ "test_#{sanitize_name(name).gsub(/\s+/,'_')}".to_sym
83
+ end
84
+
85
+ def self.sanitize_name(name)
86
+ name.gsub(/\W+/, ' ').strip
87
+ end
88
+
89
+ def self.remove_tests(subclass)
90
+ subclass.public_instance_methods.grep(/^test_/).each do |meth|
91
+ subclass.send(:undef_method, meth.to_sym)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ require File.dirname(__FILE__) + '/test_helper'
4
+
5
+ class CustomSlugNormalizerTest < Test::Unit::TestCase
6
+
7
+ context "A slugged model using a custom slug generator" do
8
+
9
+ setup do
10
+ Thing.friendly_id_options = FriendlyId::DEFAULT_FRIENDLY_ID_OPTIONS.merge(:column => :name, :use_slug => true)
11
+ Thing.delete_all
12
+ Slug.delete_all
13
+ end
14
+
15
+ should "invoke the block code" do
16
+ @thing = Thing.create!(:name => "test")
17
+ assert_equal "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", @thing.friendly_id
18
+ end
19
+
20
+ should "respect the max_length option" do
21
+ Thing.friendly_id_options = Thing.friendly_id_options.merge(:max_length => 10)
22
+ @thing = Thing.create!(:name => "test")
23
+ assert_equal "a94a8fe5cc", @thing.friendly_id
24
+ end
25
+
26
+ should "respect the reserved option" do
27
+ Thing.friendly_id_options = Thing.friendly_id_options.merge(:reserved => ["a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"])
28
+ assert_raises FriendlyId::SlugGenerationError do
29
+ Thing.create!(:name => "test")
30
+ end
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,2 @@
1
+ class Book < ActiveRecord::Base
2
+ end
@@ -0,0 +1,4 @@
1
+ class City < ActiveRecord::Base
2
+ attr_accessible :name
3
+ has_friendly_id :name, :use_slug => true, :cache_column => 'my_slug'
4
+ end
@@ -0,0 +1,4 @@
1
+ class Country < ActiveRecord::Base
2
+ has_many :people
3
+ has_friendly_id :name, :use_slug => true
4
+ end
@@ -0,0 +1,3 @@
1
+ class District < ActiveRecord::Base
2
+ has_friendly_id :name
3
+ end
@@ -0,0 +1,3 @@
1
+ class Event < ActiveRecord::Base
2
+ has_friendly_id :event_date, :use_slug => true
3
+ end
@@ -0,0 +1,3 @@
1
+ class Novel < Book
2
+ has_friendly_id :title, :use_slug => true
3
+ end
@@ -0,0 +1,6 @@
1
+ class Person < ActiveRecord::Base
2
+
3
+ belongs_to :country
4
+ has_friendly_id :name, :use_slug => true, :scope => :country
5
+
6
+ end
@@ -0,0 +1,6 @@
1
+ class Post < ActiveRecord::Base
2
+ has_friendly_id :title, :use_slug => true
3
+
4
+ named_scope :published, :conditions => { :published => true }
5
+
6
+ end
@@ -0,0 +1,6 @@
1
+ require 'digest/sha1'
2
+ class Thing < ActiveRecord::Base
3
+ has_friendly_id :name, :use_slug => true do |text|
4
+ Digest::SHA1::hexdigest(text)
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ class User < ActiveRecord::Base
2
+ has_friendly_id :login
3
+ end
@@ -0,0 +1,98 @@
1
+ # encoding: utf-8
2
+
3
+ require File.dirname(__FILE__) + '/test_helper'
4
+
5
+ class NonSluggedTest < Test::Unit::TestCase
6
+
7
+ context "A non-slugged model with default FriendlyId options" do
8
+
9
+ setup do
10
+ User.delete_all
11
+ @user = User.create!(:login => "joe", :email => "joe@example.org")
12
+ end
13
+
14
+ should "have friendly_id options" do
15
+ assert_not_nil User.friendly_id_options
16
+ end
17
+
18
+ should "not have a slug" do
19
+ assert !@user.respond_to?(:slug)
20
+ end
21
+
22
+ should "be findable by its friendly_id" do
23
+ assert User.find(@user.friendly_id)
24
+ end
25
+
26
+ should "be findable by its regular id" do
27
+ assert User.find(@user.id)
28
+ end
29
+
30
+ should "respect finder conditions" do
31
+ assert_raises ActiveRecord::RecordNotFound do
32
+ User.find(@user.friendly_id, :conditions => "1 = 2")
33
+ end
34
+ end
35
+
36
+ should "indicate if it was found by its friendly id" do
37
+ @user = User.find(@user.friendly_id)
38
+ assert @user.found_using_friendly_id?
39
+ end
40
+
41
+ should "indicate if it was found by its numeric id" do
42
+ @user = User.find(@user.id)
43
+ assert @user.found_using_numeric_id?
44
+ end
45
+
46
+ should "indicate if it has a better id" do
47
+ @user = User.find(@user.id)
48
+ assert @user.has_better_id?
49
+ end
50
+
51
+ should "not validate if the friendly_id text is reserved" do
52
+ @user = User.new(:login => "new", :email => "test@example.org")
53
+ assert !@user.valid?
54
+ end
55
+
56
+ should "have always string for a friendly_id" do
57
+ assert_equal String, @user.to_param.class
58
+ end
59
+
60
+ should "return its id if the friendly_id is null" do
61
+ @user.login = nil
62
+ assert_equal @user.id.to_s, @user.to_param
63
+ end
64
+
65
+
66
+ context "when using an array as the find argument" do
67
+
68
+ setup do
69
+ @user2 = User.create(:login => "jane", :email => "jane@example.org")
70
+ end
71
+
72
+ should "return results" do
73
+ assert_equal 2, User.find([@user.friendly_id, @user2.friendly_id]).size
74
+ end
75
+
76
+ should "not allow mixed friendly and non-friendly ids for the same record" do
77
+ assert_raises ActiveRecord::RecordNotFound do
78
+ User.find([@user.id, @user.friendly_id]).size
79
+ end
80
+ end
81
+
82
+ should "raise an error when all records are not found" do
83
+ assert_raises ActiveRecord::RecordNotFound do
84
+ User.find(['bad', 'bad2'])
85
+ end
86
+ end
87
+
88
+ should "indicate if the results were found using a friendly_id" do
89
+ @users = User.find([@user.id, @user2.friendly_id], :order => "login ASC")
90
+ assert @users[0].found_using_friendly_id?
91
+ assert @users[1].found_using_numeric_id?
92
+ end
93
+
94
+ end
95
+
96
+ end
97
+
98
+ end