friendly_id_globalize3 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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