cmassimo-friendly_id 3.0.4.2

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 (56) hide show
  1. data/Changelog.md +277 -0
  2. data/Contributors.md +39 -0
  3. data/Guide.md +561 -0
  4. data/LICENSE +19 -0
  5. data/README.md +83 -0
  6. data/Rakefile +66 -0
  7. data/extras/README.txt +3 -0
  8. data/extras/bench.rb +36 -0
  9. data/extras/extras.rb +38 -0
  10. data/extras/prof.rb +14 -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 +73 -0
  16. data/lib/friendly_id/active_record.rb +52 -0
  17. data/lib/friendly_id/active_record_adapter/configuration.rb +67 -0
  18. data/lib/friendly_id/active_record_adapter/finders.rb +156 -0
  19. data/lib/friendly_id/active_record_adapter/simple_model.rb +123 -0
  20. data/lib/friendly_id/active_record_adapter/slug.rb +66 -0
  21. data/lib/friendly_id/active_record_adapter/slugged_model.rb +238 -0
  22. data/lib/friendly_id/active_record_adapter/tasks.rb +69 -0
  23. data/lib/friendly_id/configuration.rb +113 -0
  24. data/lib/friendly_id/finders.rb +109 -0
  25. data/lib/friendly_id/railtie.rb +20 -0
  26. data/lib/friendly_id/sequel.rb +5 -0
  27. data/lib/friendly_id/slug_string.rb +391 -0
  28. data/lib/friendly_id/slugged.rb +102 -0
  29. data/lib/friendly_id/status.rb +35 -0
  30. data/lib/friendly_id/test.rb +291 -0
  31. data/lib/friendly_id/version.rb +9 -0
  32. data/lib/generators/friendly_id_generator.rb +25 -0
  33. data/lib/tasks/friendly_id.rake +19 -0
  34. data/rails/init.rb +2 -0
  35. data/test/active_record_adapter/ar_test_helper.rb +119 -0
  36. data/test/active_record_adapter/basic_slugged_model_test.rb +14 -0
  37. data/test/active_record_adapter/cached_slug_test.rb +61 -0
  38. data/test/active_record_adapter/core.rb +98 -0
  39. data/test/active_record_adapter/custom_normalizer_test.rb +20 -0
  40. data/test/active_record_adapter/custom_table_name_test.rb +22 -0
  41. data/test/active_record_adapter/scoped_model_test.rb +118 -0
  42. data/test/active_record_adapter/simple_test.rb +76 -0
  43. data/test/active_record_adapter/slug_test.rb +34 -0
  44. data/test/active_record_adapter/slugged.rb +30 -0
  45. data/test/active_record_adapter/slugged_status_test.rb +25 -0
  46. data/test/active_record_adapter/sti_test.rb +22 -0
  47. data/test/active_record_adapter/support/database.jdbcsqlite3.yml +2 -0
  48. data/test/active_record_adapter/support/database.mysql.yml +4 -0
  49. data/test/active_record_adapter/support/database.postgres.yml +6 -0
  50. data/test/active_record_adapter/support/database.sqlite3.yml +2 -0
  51. data/test/active_record_adapter/support/models.rb +87 -0
  52. data/test/active_record_adapter/tasks_test.rb +82 -0
  53. data/test/friendly_id_test.rb +55 -0
  54. data/test/slug_string_test.rb +88 -0
  55. data/test/test_helper.rb +15 -0
  56. metadata +168 -0
@@ -0,0 +1,35 @@
1
+ module FriendlyId
2
+
3
+ # FriendlyId::Status presents information about the status of the
4
+ # id that was used to find the model. This class can be useful for figuring
5
+ # out when to redirect to a new URL.
6
+ class Status
7
+
8
+ # The id or name used as the finder argument
9
+ attr_accessor :name
10
+
11
+ # The found result, if any
12
+ attr_accessor :record
13
+
14
+ def initialize(options={})
15
+ options.each {|key, value| self.send("#{key}=".to_sym, value)}
16
+ end
17
+
18
+ # Did the find operation use a friendly id?
19
+ def friendly?
20
+ !! name
21
+ end
22
+
23
+ # Did the find operation use a numeric id?
24
+ def numeric?
25
+ !friendly?
26
+ end
27
+
28
+ # Did the find operation use the best available id?
29
+ def best?
30
+ record.friendly_id ? friendly? : true
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,291 @@
1
+ Module.send :include, Module.new {
2
+ def test(name, &block)
3
+ define_method("test_#{name.gsub(/[^a-z0-9]/i, "_")}".to_sym, &block)
4
+ end
5
+ alias :should :test
6
+ }
7
+
8
+ module FriendlyId
9
+ module Test
10
+
11
+ # Tests for any model that implements FriendlyId. Any test that tests model
12
+ # features should include this module.
13
+ module Generic
14
+
15
+ def setup
16
+ klass.send delete_all_method
17
+ end
18
+
19
+ def teardown
20
+ klass.send delete_all_method
21
+ end
22
+
23
+ def instance
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def klass
28
+ raise NotImplementedError
29
+ end
30
+
31
+ def other_class
32
+ raise NotImplementedError
33
+ end
34
+
35
+ def find_method
36
+ raise NotImplementedError
37
+ end
38
+
39
+ def create_method
40
+ raise NotImplementedError
41
+ end
42
+
43
+ def update_method
44
+ raise NotImplementedError
45
+ end
46
+
47
+ def validation_exceptions
48
+ return RuntimeError
49
+ end
50
+
51
+ test "models should have a friendly id config" do
52
+ assert_not_nil klass.friendly_id_config
53
+ end
54
+
55
+ test "instances should have a friendly id by default" do
56
+ assert_not_nil instance.friendly_id
57
+ end
58
+
59
+ test "instances should have a friendly id status" do
60
+ assert_not_nil instance.friendly_id_status
61
+ end
62
+
63
+ test "instances should be findable by their friendly id" do
64
+ assert_equal instance, klass.send(find_method, instance.friendly_id)
65
+ end
66
+
67
+ test "instances should be findable by their numeric id as an integer" do
68
+ assert_equal instance, klass.send(find_method, instance.id.to_i)
69
+ end
70
+
71
+ test "instances should be findable by their numeric id as a string" do
72
+ assert_equal instance, klass.send(find_method, instance.id.to_s)
73
+ end
74
+
75
+ test "instances should be findable by a numeric friendly_id" do
76
+ instance = klass.send(create_method, :name => "206")
77
+ assert_equal instance, klass.send(find_method, instance.friendly_id)
78
+ end
79
+
80
+ test "creation should raise an error if the friendly_id text is reserved" do
81
+ assert_raise(*[validation_exceptions].flatten) do
82
+ klass.send(create_method, :name => "new")
83
+ end
84
+ end
85
+
86
+ test "creation should raise an error if the friendly_id text is an empty string" do
87
+ assert_raise(*[validation_exceptions].flatten) do
88
+ klass.send(create_method, :name => "")
89
+ end
90
+ end
91
+
92
+ test "creation should raise an error if the friendly_id text is a blank string" do
93
+ assert_raise(*[validation_exceptions].flatten) do
94
+ klass.send(create_method, :name => " ")
95
+ end
96
+ end
97
+
98
+ test "creation should raise an error if the friendly_id text is nil and allow_nil is false" do
99
+ assert_raise(*[validation_exceptions].flatten) do
100
+ klass.send(create_method, :name => nil)
101
+ end
102
+ end
103
+
104
+ test "creation should succeed if the friendly_id text is nil and allow_nil is true" do
105
+ klass.friendly_id_config.stubs(:allow_nil?).returns(true)
106
+ assert klass.send(create_method, :name => nil)
107
+ end
108
+
109
+ test "should allow the same friendly_id across models" do
110
+ other_instance = other_class.send(create_method, :name => instance.name)
111
+ assert_equal other_instance.friendly_id, instance.friendly_id
112
+ end
113
+
114
+ end
115
+
116
+ module Simple
117
+
118
+ test "should allow friendly_id to be nillable if allow_nil is true" do
119
+ klass.friendly_id_config.stubs(:allow_nil?).returns(true)
120
+ instance = klass.send(create_method, :name => "hello")
121
+ assert instance.friendly_id
122
+ instance.name = nil
123
+ assert instance.send(save_method)
124
+ end
125
+
126
+ end
127
+
128
+ # Tests for any model that implements slugs.
129
+ module Slugged
130
+
131
+ test "should have a slug" do
132
+ assert_not_nil instance.slug
133
+ end
134
+
135
+ test "should not make a new slug unless the friendly_id method value has changed" do
136
+ instance.note = instance.note.to_s << " updated"
137
+ instance.send save_method
138
+ assert_equal 1, instance.slugs.size
139
+ end
140
+
141
+ test "should make a new slug if the friendly_id method value has changed" do
142
+ instance.name = "Changed title"
143
+ instance.send save_method
144
+ assert_equal 2, instance.slugs.size
145
+ end
146
+
147
+ test "should be able to reuse an old friendly_id without incrementing the sequence" do
148
+ old_title = instance.name
149
+ old_friendly_id = instance.friendly_id
150
+ instance.name = "A changed title"
151
+ instance.send save_method
152
+ instance.name = old_title
153
+ instance.send save_method
154
+ assert_equal old_friendly_id, instance.friendly_id
155
+ end
156
+
157
+ test "should increment the slug sequence for duplicate friendly ids" do
158
+ instance2 = klass.send(create_method, :name => instance.name)
159
+ assert_match(/2\z/, instance2.friendly_id)
160
+ end
161
+
162
+ test "should find instance with a sequenced friendly_id" do
163
+ instance2 = klass.send(create_method, :name => instance.name)
164
+ assert_equal instance2, klass.send(find_method, instance2.friendly_id)
165
+ end
166
+
167
+ test "should indicate correct status when found with a sequence" do
168
+ instance2 = klass.send(create_method, :name => instance.name)
169
+ instance2 = klass.send(find_method, instance2.friendly_id)
170
+ assert instance2.friendly_id_status.best?
171
+ end
172
+
173
+ test "should remain findable by previous slugs" do
174
+ old_friendly_id = instance.friendly_id
175
+ instance.name = "#{old_friendly_id} updated"
176
+ instance.send(save_method)
177
+ assert_not_equal old_friendly_id, instance.friendly_id
178
+ assert_equal instance, klass.send(find_method, old_friendly_id)
179
+ end
180
+
181
+ test "should not create a slug when allow_nil is true and friendy_id text is blank" do
182
+ klass.friendly_id_config.stubs(:allow_nil?).returns(true)
183
+ instance = klass.send(create_method, :name => nil)
184
+ assert_nil instance.slug
185
+ end
186
+
187
+ test "should not allow friendly_id to be nillable even if allow_nil is true" do
188
+ klass.friendly_id_config.stubs(:allow_nil?).returns(true)
189
+ instance = klass.send(create_method, :name => "hello")
190
+ assert instance.friendly_id
191
+ instance.name = nil
192
+ assert_raise(*[validation_exceptions].flatten) do
193
+ instance.send(save_method)
194
+ end
195
+ end
196
+
197
+ end
198
+
199
+ # Tests for FriendlyId::Status.
200
+ module Status
201
+
202
+ test "should default to not friendly" do
203
+ assert !status.friendly?
204
+ end
205
+
206
+ test "should default to numeric" do
207
+ assert status.numeric?
208
+ end
209
+
210
+ end
211
+
212
+ # Tests for FriendlyId::Status for a model that uses slugs.
213
+ module SluggedStatus
214
+
215
+ test "should be friendly if slug is set" do
216
+ status.slug = Slug.new
217
+ assert status.friendly?
218
+ end
219
+
220
+ test "should be friendly if name is set" do
221
+ status.name = "name"
222
+ assert status.friendly?
223
+ end
224
+
225
+ test "should be current if current slug is set" do
226
+ status.slug = instance.slug
227
+ assert status.current?
228
+ end
229
+
230
+ test "should not be current if non-current slug is set" do
231
+ status.slug = Slug.new(:sluggable => instance)
232
+ assert !status.current?
233
+ end
234
+
235
+ test "should be best if it is current" do
236
+ status.slug = instance.slug
237
+ assert status.best?
238
+ end
239
+
240
+ test "should be best if it is numeric, but record has no slug" do
241
+ instance.slugs = []
242
+ instance.slug = nil
243
+ assert status.best?
244
+ end
245
+
246
+ [:record, :name].each do |symbol|
247
+ test "should have #{symbol} after find using friendly_id" do
248
+ instance2 = klass.find(instance.friendly_id)
249
+ assert_not_nil instance2.friendly_id_status.send(symbol)
250
+ end
251
+ end
252
+
253
+ def status
254
+ @status ||= instance.friendly_id_status
255
+ end
256
+
257
+ def klass
258
+ raise NotImplementedError
259
+ end
260
+
261
+ def instance
262
+ raise NotImplementedError
263
+ end
264
+
265
+ end
266
+
267
+ # Tests for models to ensure that they properly implement using the
268
+ # +normalize_friendly_id+ method to allow developers to hook into the
269
+ # slug string generation.
270
+ module CustomNormalizer
271
+
272
+ test "should invoke the custom normalizer" do
273
+ assert_equal "JOE SCHMOE", klass.send(create_method, :name => "Joe Schmoe").friendly_id
274
+ end
275
+
276
+ test "should respect the max_length option" do
277
+ klass.friendly_id_config.stubs(:max_length).returns(3)
278
+ assert_equal "JOE", klass.send(create_method, :name => "Joe Schmoe").friendly_id
279
+ end
280
+
281
+ test "should raise an error if the friendly_id text is reserved" do
282
+ klass.friendly_id_config.stubs(:reserved_words).returns(["JOE"])
283
+ assert_raise(*[validation_exceptions].flatten) do
284
+ klass.send(create_method, :name => "Joe")
285
+ end
286
+
287
+ end
288
+
289
+ end
290
+ end
291
+ end
@@ -0,0 +1,9 @@
1
+ module FriendlyId
2
+ module Version
3
+ MAJOR = 3
4
+ MINOR = 0
5
+ TINY = 4
6
+ BUILD = 2
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 "%s(%d): friendly_id set to '%s'" % [record.class.to_s, record.id, 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,119 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ require "active_record"
4
+ require "active_support"
5
+
6
+ require File.expand_path("../../../lib/friendly_id/active_record", __FILE__)
7
+ require File.expand_path("../../../generators/friendly_id/templates/create_slugs", __FILE__)
8
+ require File.expand_path("../support/models", __FILE__)
9
+ require File.expand_path('../core', __FILE__)
10
+ require File.expand_path('../slugged', __FILE__)
11
+
12
+ local_db_settings = File.expand_path("../support/database.yml", __FILE__)
13
+ default_db_settings = File.expand_path("../support/database.sqlite3.yml", __FILE__)
14
+
15
+ db_settings = File.exists?(local_db_settings) ? local_db_settings : default_db_settings
16
+ ActiveRecord::Base.establish_connection(YAML::load(File.open(db_settings)))
17
+
18
+ class ActiveRecord::Base
19
+ def log_protected_attribute_removal(*args) end
20
+ end
21
+
22
+ ActiveRecord::Base.connection.tables.each do |table|
23
+ ActiveRecord::Base.connection.drop_table(table)
24
+ end
25
+ ActiveRecord::Migration.verbose = false
26
+ CreateSlugs.up
27
+ CreateSupportModels.up
28
+
29
+ # A model that uses the automagically configured "cached_slug" column
30
+ class District < ActiveRecord::Base
31
+ has_friendly_id :name, :use_slug => true
32
+ end
33
+
34
+ # A model that specifies a custom cached slug column
35
+ class City < ActiveRecord::Base
36
+ has_friendly_id :name, :use_slug => true, :cache_column => "my_slug"
37
+ end
38
+
39
+ # A model with a custom slug text normalizer
40
+ class Person < ActiveRecord::Base
41
+ has_friendly_id :name, :use_slug => true
42
+
43
+ def normalize_friendly_id(string)
44
+ string.upcase
45
+ end
46
+
47
+ end
48
+
49
+ # A slugged model that uses a scope
50
+ class Resident < ActiveRecord::Base
51
+ belongs_to :country
52
+ has_friendly_id :name, :use_slug => true, :scope => :country
53
+ end
54
+
55
+ # A slugged model used as a scope
56
+ class Country < ActiveRecord::Base
57
+ has_many :people
58
+ has_many :residents
59
+ has_friendly_id :name, :use_slug => true
60
+ end
61
+
62
+ # A model that doesn"t use slugs
63
+ class User < ActiveRecord::Base
64
+ has_friendly_id :name
65
+ has_many :houses
66
+ end
67
+
68
+ # Another model that doesn"t use slugs
69
+ class Author < ActiveRecord::Base
70
+ has_friendly_id :name
71
+ end
72
+
73
+
74
+ # A model that uses a non-slugged model for its scope
75
+ class House < ActiveRecord::Base
76
+ belongs_to :user
77
+ has_friendly_id :name, :use_slug => true, :scope => :user
78
+ end
79
+
80
+ # A model that uses default slug settings and has a named scope
81
+ class Post < ActiveRecord::Base
82
+ has_friendly_id :name, :use_slug => true
83
+ send FriendlyId::ActiveRecordAdapter::Compat.scope_method, :published, :conditions => { :published => true }
84
+ end
85
+
86
+ # Model that uses a custom table name
87
+ class Place < ActiveRecord::Base
88
+ self.table_name = "legacy_table"
89
+ has_friendly_id :name, :use_slug => true
90
+ end
91
+
92
+ # A model that uses a datetime field for its friendly_id
93
+ class Event < ActiveRecord::Base
94
+ has_friendly_id :event_date, :use_slug => true
95
+ end
96
+
97
+ # A base model for single table inheritence
98
+ class Book < ActiveRecord::Base ; end
99
+
100
+ # A model that uses STI
101
+ class Novel < ::Book
102
+ has_friendly_id :name, :use_slug => true
103
+ end
104
+
105
+ # A model with no table
106
+ class Question < ActiveRecord::Base
107
+ has_friendly_id :name, :use_slug => true
108
+ end
109
+
110
+ # A model to test polymorphic associations
111
+ class Site < ActiveRecord::Base
112
+ belongs_to :owner, :polymorphic => true
113
+ has_friendly_id :name, :use_slug => true
114
+ end
115
+
116
+ # A model used as a polymorphic owner
117
+ class Company < ActiveRecord::Base
118
+ has_many :sites, :as => :owner
119
+ end