friendly_id_globalize3 3.2.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.
Files changed (58) hide show
  1. data/Changelog.md +354 -0
  2. data/Contributors.md +43 -0
  3. data/Guide.md +686 -0
  4. data/MIT-LICENSE +19 -0
  5. data/README.md +99 -0
  6. data/Rakefile +75 -0
  7. data/extras/README.txt +3 -0
  8. data/extras/bench.rb +40 -0
  9. data/extras/extras.rb +38 -0
  10. data/extras/prof.rb +19 -0
  11. data/extras/template-gem.rb +26 -0
  12. data/extras/template-plugin.rb +28 -0
  13. data/generators/friendly_id/friendly_id_generator.rb +30 -0
  14. data/generators/friendly_id/templates/create_slugs.rb +18 -0
  15. data/lib/friendly_id.rb +93 -0
  16. data/lib/friendly_id/active_record.rb +74 -0
  17. data/lib/friendly_id/active_record_adapter/configuration.rb +68 -0
  18. data/lib/friendly_id/active_record_adapter/finders.rb +148 -0
  19. data/lib/friendly_id/active_record_adapter/relation.rb +165 -0
  20. data/lib/friendly_id/active_record_adapter/simple_model.rb +63 -0
  21. data/lib/friendly_id/active_record_adapter/slug.rb +77 -0
  22. data/lib/friendly_id/active_record_adapter/slugged_model.rb +122 -0
  23. data/lib/friendly_id/active_record_adapter/tasks.rb +72 -0
  24. data/lib/friendly_id/configuration.rb +178 -0
  25. data/lib/friendly_id/datamapper.rb +5 -0
  26. data/lib/friendly_id/railtie.rb +22 -0
  27. data/lib/friendly_id/sequel.rb +5 -0
  28. data/lib/friendly_id/slug_string.rb +25 -0
  29. data/lib/friendly_id/slugged.rb +105 -0
  30. data/lib/friendly_id/status.rb +35 -0
  31. data/lib/friendly_id/test.rb +350 -0
  32. data/lib/friendly_id/version.rb +9 -0
  33. data/lib/generators/friendly_id_generator.rb +25 -0
  34. data/lib/tasks/friendly_id.rake +19 -0
  35. data/rails/init.rb +2 -0
  36. data/test/active_record_adapter/ar_test_helper.rb +150 -0
  37. data/test/active_record_adapter/basic_slugged_model_test.rb +14 -0
  38. data/test/active_record_adapter/cached_slug_test.rb +76 -0
  39. data/test/active_record_adapter/core.rb +138 -0
  40. data/test/active_record_adapter/custom_normalizer_test.rb +20 -0
  41. data/test/active_record_adapter/custom_table_name_test.rb +22 -0
  42. data/test/active_record_adapter/default_scope_test.rb +30 -0
  43. data/test/active_record_adapter/optimistic_locking_test.rb +18 -0
  44. data/test/active_record_adapter/scoped_model_test.rb +119 -0
  45. data/test/active_record_adapter/simple_test.rb +76 -0
  46. data/test/active_record_adapter/slug_test.rb +34 -0
  47. data/test/active_record_adapter/slugged.rb +33 -0
  48. data/test/active_record_adapter/slugged_status_test.rb +28 -0
  49. data/test/active_record_adapter/sti_test.rb +22 -0
  50. data/test/active_record_adapter/support/database.jdbcsqlite3.yml +2 -0
  51. data/test/active_record_adapter/support/database.mysql.yml +4 -0
  52. data/test/active_record_adapter/support/database.postgres.yml +6 -0
  53. data/test/active_record_adapter/support/database.sqlite3.yml +2 -0
  54. data/test/active_record_adapter/support/models.rb +104 -0
  55. data/test/active_record_adapter/tasks_test.rb +82 -0
  56. data/test/friendly_id_test.rb +96 -0
  57. data/test/test_helper.rb +13 -0
  58. metadata +193 -0
@@ -0,0 +1,9 @@
1
+ module FriendlyId
2
+ module Version
3
+ MAJOR = 3
4
+ MINOR = 2
5
+ TINY = 0
6
+ BUILD = nil
7
+ STRING = [MAJOR, MINOR, TINY, BUILD].compact.join('.')
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ class FriendlyIdGenerator < Rails::Generators::Base
5
+
6
+ include Rails::Generators::Migration
7
+
8
+ MIGRATIONS_FILE = File.join(File.dirname(__FILE__), "..", "..", "generators", "friendly_id", "templates", "create_slugs.rb")
9
+
10
+ class_option :"skip-migration", :type => :boolean, :desc => "Don't generate a migration for the slugs table"
11
+
12
+ def copy_files(*args)
13
+ migration_template MIGRATIONS_FILE, "db/migrate/create_slugs.rb" unless options["skip-migration"]
14
+ end
15
+
16
+ # Taken from ActiveRecord's migration generator
17
+ def self.next_migration_number(dirname) #:nodoc:
18
+ if ActiveRecord::Base.timestamped_migrations
19
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
20
+ else
21
+ "%.3d" % (current_migration_number(dirname) + 1)
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,19 @@
1
+ namespace :friendly_id do
2
+ desc "Make slugs for a model."
3
+ task :make_slugs => :environment do
4
+ FriendlyId::TaskRunner.new.make_slugs do |record|
5
+ puts "#{record.class}(#{record.id}): friendly_id set to '#{record.slug.name}'" if record.slug
6
+ end
7
+ end
8
+
9
+ desc "Regenereate slugs for a model."
10
+ task :redo_slugs => :environment do
11
+ FriendlyId::TaskRunner.new.delete_slugs
12
+ Rake::Task["friendly_id:make_slugs"].invoke
13
+ end
14
+
15
+ desc "Destroy obsolete slugs older than DAYS=45 days."
16
+ task :remove_old_slugs => :environment do
17
+ FriendlyId::TaskRunner.new.delete_old_slugs
18
+ end
19
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "friendly_id"
2
+ require "friendly_id/active_record"
@@ -0,0 +1,150 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+ require "logger"
3
+ require "active_record"
4
+ begin
5
+ require "active_support/log_subscriber"
6
+ rescue LoadError
7
+ end
8
+
9
+ # If you want to see the ActiveRecord log, invoke the tests using `rake test LOG=true`
10
+ ActiveRecord::Base.logger = Logger.new($stdout) if ENV["LOG"]
11
+
12
+ require "friendly_id/active_record"
13
+ require File.expand_path("../../../generators/friendly_id/templates/create_slugs", __FILE__)
14
+ require File.expand_path("../support/models", __FILE__)
15
+ require File.expand_path('../core', __FILE__)
16
+ require File.expand_path('../slugged', __FILE__)
17
+
18
+ driver = (ENV["DB"] or "sqlite3").downcase
19
+ db_yaml = File.expand_path("../support/database.#{driver}.yml", __FILE__)
20
+ ActiveRecord::Base.establish_connection(YAML::load(File.open(db_yaml)))
21
+
22
+ class ActiveRecord::Base
23
+ def log_protected_attribute_removal(*args) end
24
+ end
25
+
26
+ ActiveRecord::Base.connection.tables.each do |table|
27
+ ActiveRecord::Base.connection.drop_table(table)
28
+ end
29
+ ActiveRecord::Migration.verbose = false
30
+ CreateSlugs.up
31
+ CreateSupportModels.up
32
+
33
+ # A model that uses the automagically configured "cached_slug" column
34
+ class District < ActiveRecord::Base
35
+ has_friendly_id :name, :use_slug => true
36
+ before_save :say_hello
37
+
38
+ def say_hello
39
+ @said_hello = true
40
+ end
41
+ end
42
+
43
+ # A model with optimistic locking enabled
44
+ class Region < ActiveRecord::Base
45
+ has_friendly_id :name, :use_slug => true
46
+ after_create do |obj|
47
+ other_instance = Region.find obj.id
48
+ other_instance.update_attributes :note => name + "!"
49
+ end
50
+ end
51
+
52
+ # A model that specifies a custom cached slug column
53
+ class City < ActiveRecord::Base
54
+ has_friendly_id :name, :use_slug => true, :cache_column => "my_slug"
55
+ end
56
+
57
+ # A model with a custom slug text normalizer
58
+ class Person < ActiveRecord::Base
59
+ has_friendly_id :name, :use_slug => true
60
+
61
+ def normalize_friendly_id(string)
62
+ string.upcase
63
+ end
64
+
65
+ end
66
+
67
+ # A model that doesn't use FriendlyId
68
+ class Unfriendly < ActiveRecord::Base
69
+ end
70
+
71
+ # A slugged model that uses a scope
72
+ class Resident < ActiveRecord::Base
73
+ belongs_to :country
74
+ has_friendly_id :name, :use_slug => true, :scope => :country
75
+ end
76
+
77
+ # Like resident, but has a cached slug
78
+ class Tourist < ActiveRecord::Base
79
+ belongs_to :country
80
+ has_friendly_id :name, :use_slug => true, :scope => :country
81
+ end
82
+
83
+ # A slugged model used as a scope
84
+ class Country < ActiveRecord::Base
85
+ has_many :people
86
+ has_many :residents
87
+ has_friendly_id :name, :use_slug => true
88
+ end
89
+
90
+ # A model that doesn't use slugs
91
+ class User < ActiveRecord::Base
92
+ has_friendly_id :name
93
+ has_many :houses
94
+ end
95
+
96
+ # Another model that doesn"t use slugs
97
+ class Author < ActiveRecord::Base
98
+ has_friendly_id :name
99
+ end
100
+
101
+
102
+ # A model that uses a non-slugged model for its scope
103
+ class House < ActiveRecord::Base
104
+ belongs_to :user
105
+ has_friendly_id :name, :use_slug => true, :scope => :user
106
+ end
107
+
108
+ # A model that uses default slug settings and has a named scope
109
+ class Post < ActiveRecord::Base
110
+ has_friendly_id :name, :use_slug => true
111
+ def self.named_scope(*args) scope(*args) end if FriendlyId.on_ar3?
112
+ named_scope :published, :conditions => { :published => true }
113
+ end
114
+
115
+ # Model that uses a custom table name
116
+ class Place < ActiveRecord::Base
117
+ self.table_name = "legacy_table"
118
+ has_friendly_id :name, :use_slug => true
119
+ end
120
+
121
+ # A model that uses a datetime field for its friendly_id
122
+ class Event < ActiveRecord::Base
123
+ has_friendly_id :event_date, :use_slug => true
124
+ end
125
+
126
+ # A base model for single table inheritence
127
+ class Book < ActiveRecord::Base ; end
128
+
129
+ # A model that uses STI
130
+ class Novel < ::Book
131
+ has_friendly_id :name, :use_slug => true
132
+ end
133
+
134
+ # A model with no table
135
+ class Question < ActiveRecord::Base
136
+ has_friendly_id :name, :use_slug => true
137
+ end
138
+
139
+ # A model to test polymorphic associations
140
+ class Site < ActiveRecord::Base
141
+ belongs_to :owner, :polymorphic => true
142
+ has_friendly_id :name, :use_slug => true
143
+ end
144
+
145
+ # A model used as a polymorphic owner
146
+ class Company < ActiveRecord::Base
147
+ has_many :sites, :as => :owner
148
+ end
149
+
150
+ puts "Using: #{RUBY_VERSION}, #{driver}, AR#{ENV["AR"] or 3}"
@@ -0,0 +1,14 @@
1
+ require File.expand_path('../ar_test_helper', __FILE__)
2
+
3
+ module FriendlyId
4
+ module Test
5
+ module ActiveRecordAdapter
6
+ class BasicSluggedModelTest < ::Test::Unit::TestCase
7
+ include FriendlyId::Test::Generic
8
+ include FriendlyId::Test::Slugged
9
+ include FriendlyId::Test::ActiveRecordAdapter::Slugged
10
+ include FriendlyId::Test::ActiveRecordAdapter::Core
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,76 @@
1
+ require File.expand_path("../ar_test_helper", __FILE__)
2
+
3
+ module FriendlyId
4
+ module Test
5
+ module ActiveRecordAdapter
6
+
7
+ class CachedSlugTest < ::Test::Unit::TestCase
8
+
9
+ include FriendlyId::Test::Generic
10
+ include FriendlyId::Test::Slugged
11
+ include FriendlyId::Test::ActiveRecordAdapter::Slugged
12
+ include FriendlyId::Test::ActiveRecordAdapter::Core
13
+
14
+ def klass
15
+ District
16
+ end
17
+
18
+ def other_class
19
+ Post
20
+ end
21
+
22
+ def cached_slug
23
+ instance.send(cache_column)
24
+ end
25
+
26
+ def cache_column
27
+ klass.friendly_id_config.cache_column
28
+ end
29
+
30
+ test "should have a cached_slug" do
31
+ assert_equal cached_slug, instance.slug.to_friendly_id
32
+ end
33
+
34
+ test "should protect the cached slug value" do
35
+ old_value = cached_slug
36
+ instance.update_attributes(cache_column => "Madrid")
37
+ instance.reload
38
+ assert_equal old_value, cached_slug
39
+ end
40
+
41
+ test "should update the cached slug when updating the slug" do
42
+ instance.update_attributes(:name => "new name")
43
+ assert_equal instance.slug.to_friendly_id, cached_slug
44
+ end
45
+
46
+ test "should not update the cached slug column if it has not changed" do
47
+ instance.note = "a note"
48
+ instance.expects("#{cache_column}=".to_sym).never
49
+ instance.save!
50
+ end
51
+
52
+ test "should cache the incremented sequence for duplicate slug names" do
53
+ instance_2 = klass.create!(:name => instance.name)
54
+ assert_match(/2\z/, instance_2.send(cache_column))
55
+ end
56
+
57
+ test "#friendly_id should check the cached value by default" do
58
+ instance.expects(:slug).never
59
+ instance.friendly_id
60
+ end
61
+
62
+ test "#friendly_id should skip the cache if invoked with true" do
63
+ instance.expects(:slug)
64
+ instance.friendly_id(true)
65
+ end
66
+
67
+ test "should not fire callbacks when updating slug cache" do
68
+ instance.expects(:say_hello).once
69
+ instance.update_attributes(:name => "new name")
70
+ assert_equal instance.slug.to_friendly_id, cached_slug
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+
@@ -0,0 +1,138 @@
1
+ require File.expand_path("../ar_test_helper", __FILE__)
2
+
3
+ module FriendlyId
4
+
5
+ module Test
6
+
7
+ module ActiveRecordAdapter
8
+
9
+ module Core
10
+
11
+ def teardown
12
+ klass.delete_all
13
+ other_class.delete_all
14
+ Slug.delete_all
15
+ end
16
+
17
+ def find_method
18
+ :find
19
+ end
20
+
21
+ def create_method
22
+ :create!
23
+ end
24
+
25
+ def delete_all_method
26
+ :delete_all
27
+ end
28
+
29
+ def save_method
30
+ :save!
31
+ end
32
+
33
+ def unfriendly_class
34
+ Unfriendly
35
+ end
36
+
37
+ def validation_exceptions
38
+ [ActiveRecord::RecordInvalid, FriendlyId::ReservedError, FriendlyId::BlankError]
39
+ end
40
+
41
+ test "should return their friendly_id for #to_param" do
42
+ assert_match(instance.friendly_id, instance.to_param)
43
+ end
44
+
45
+ test "instances should be findable by their own instance" do
46
+ assert_equal instance, klass.find(instance)
47
+ end
48
+
49
+ test "instances should be findable by an array of friendly_ids" do
50
+ second = klass.create!(:name => "second_instance")
51
+ third = klass.create!(:name => "third_instance")
52
+ assert_equal 2, klass.find([instance.friendly_id, second.friendly_id]).size
53
+ end
54
+
55
+ test "failing finds with array of unfriendly_id should raise errors normally" do
56
+ assert_raise ActiveRecord::RecordNotFound do
57
+ klass.find([0, -1])
58
+ end
59
+ end
60
+
61
+ test "instances should be findable by an array of numeric ids" do
62
+ second = klass.create!(:name => "second_instance")
63
+ third = klass.create!(:name => "third_instance")
64
+ assert_equal 2, klass.find([instance.id.to_i, second.id.to_i]).size
65
+ end
66
+
67
+ test "instances should be findable by an array of numeric ids as strings" do
68
+ second = klass.create!(:name => "second_instance")
69
+ third = klass.create!(:name => "third_instance")
70
+ assert_equal 2, klass.find([instance.id.to_s, second.id.to_s]).size
71
+ end
72
+
73
+ test "instances should be findable by an array of instances" do
74
+ second = klass.create!(:name => "second_instance")
75
+ third = klass.create!(:name => "third_instance")
76
+ assert_equal 2, klass.find([instance, second]).size
77
+ end
78
+
79
+ test "instances should be findable by an array of mixed types" do
80
+ second = klass.create!(:name => "second_instance")
81
+ third = klass.create!(:name => "third_instance")
82
+ assert_equal 2, klass.find([instance.friendly_id, second]).size
83
+ end
84
+
85
+ test "should not raise error when finding with empty array" do
86
+ assert_nothing_raised do
87
+ klass.find []
88
+ end
89
+ end
90
+
91
+ test "models should raise an error when not all records are found" do
92
+ assert_raises(ActiveRecord::RecordNotFound) do
93
+ klass.find([instance.friendly_id, 'bad-friendly-id'])
94
+ end
95
+ end
96
+
97
+ test "models should respect finder conditions" do
98
+ assert_raise ActiveRecord::RecordNotFound do
99
+ klass.find(instance.friendly_id, :conditions => "1 = 2")
100
+ end
101
+ end
102
+
103
+ # This emulates a fairly common issue where id's generated by fixtures are very high.
104
+ test "should continue to admit very large ids" do
105
+ klass.connection.execute("INSERT INTO #{klass.table_name} (id, name) VALUES (2047483647, 'An instance')")
106
+ assert_nothing_raised do
107
+ klass.base_class.find(2047483647)
108
+ end
109
+ end
110
+
111
+ test "should not change failure behavior for models not using friendly_id" do
112
+ assert_raise ActiveRecord::RecordNotFound do
113
+ unfriendly_class.find(-1)
114
+ end
115
+ end
116
+
117
+ test "failing finds with unfriendly_id should raise errors normally" do
118
+ assert_raise ActiveRecord::RecordNotFound do
119
+ klass.send(find_method, 0)
120
+ end
121
+ end
122
+
123
+ test "instances found by a single id should not be read-only" do
124
+ i = klass.find(instance.friendly_id)
125
+ assert !i.readonly?, "expected instance not to be readonly"
126
+ end
127
+
128
+ test "instances found by an array of ids should not be read-only" do
129
+ second = klass.create!(:name => "second_instance")
130
+ third = klass.create!(:name => "third_instance")
131
+ klass.find([instance.friendly_id, second.friendly_id]).each do |record|
132
+ assert !record.readonly?, "expected instance not to be readonly"
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,20 @@
1
+ require File.expand_path("../ar_test_helper", __FILE__)
2
+
3
+ module FriendlyId
4
+ module Test
5
+ module ActiveRecordAdapter
6
+
7
+ class CustomNormalizerTest < ::Test::Unit::TestCase
8
+
9
+ include FriendlyId::Test::ActiveRecordAdapter::Core
10
+ include FriendlyId::Test::ActiveRecordAdapter::Slugged
11
+ include FriendlyId::Test::CustomNormalizer
12
+
13
+ def klass
14
+ Person
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+ end