geothird_friendly_id 4.0.9.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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +12 -0
  4. data/.travis.yml +20 -0
  5. data/.yardopts +4 -0
  6. data/Changelog.md +86 -0
  7. data/Gemfile +15 -0
  8. data/Guide.rdoc +553 -0
  9. data/MIT-LICENSE +19 -0
  10. data/README.md +150 -0
  11. data/Rakefile +108 -0
  12. data/WhatsNew.md +95 -0
  13. data/bench.rb +63 -0
  14. data/friendly_id.gemspec +43 -0
  15. data/gemfiles/Gemfile.rails-3.0.rb +21 -0
  16. data/gemfiles/Gemfile.rails-3.1.rb +22 -0
  17. data/gemfiles/Gemfile.rails-3.2.rb +22 -0
  18. data/geothird_friendly_id.gemspec +43 -0
  19. data/lib/friendly_id/base.rb +291 -0
  20. data/lib/friendly_id/configuration.rb +80 -0
  21. data/lib/friendly_id/finder_methods.rb +35 -0
  22. data/lib/friendly_id/globalize.rb +115 -0
  23. data/lib/friendly_id/history.rb +134 -0
  24. data/lib/friendly_id/migration.rb +18 -0
  25. data/lib/friendly_id/object_utils.rb +50 -0
  26. data/lib/friendly_id/reserved.rb +68 -0
  27. data/lib/friendly_id/scoped.rb +149 -0
  28. data/lib/friendly_id/simple_i18n.rb +95 -0
  29. data/lib/friendly_id/slug.rb +14 -0
  30. data/lib/friendly_id/slug_generator.rb +80 -0
  31. data/lib/friendly_id/slugged.rb +329 -0
  32. data/lib/friendly_id.rb +114 -0
  33. data/lib/generators/friendly_id_generator.rb +17 -0
  34. data/test/base_test.rb +72 -0
  35. data/test/compatibility/ancestry/Gemfile +8 -0
  36. data/test/compatibility/ancestry/ancestry_test.rb +34 -0
  37. data/test/compatibility/threading/Gemfile +8 -0
  38. data/test/compatibility/threading/Gemfile.lock +37 -0
  39. data/test/compatibility/threading/threading.rb +45 -0
  40. data/test/configuration_test.rb +48 -0
  41. data/test/core_test.rb +48 -0
  42. data/test/databases.yml +19 -0
  43. data/test/generator_test.rb +20 -0
  44. data/test/globalize_test.rb +57 -0
  45. data/test/helper.rb +87 -0
  46. data/test/history_test.rb +149 -0
  47. data/test/object_utils_test.rb +28 -0
  48. data/test/reserved_test.rb +40 -0
  49. data/test/schema.rb +79 -0
  50. data/test/scoped_test.rb +83 -0
  51. data/test/shared.rb +156 -0
  52. data/test/simple_i18n_test.rb +133 -0
  53. data/test/slugged_test.rb +280 -0
  54. data/test/sti_test.rb +77 -0
  55. metadata +247 -0
@@ -0,0 +1,329 @@
1
+ # encoding: utf-8
2
+ require "friendly_id/slug_generator"
3
+
4
+ module FriendlyId
5
+ =begin
6
+
7
+ == Slugged Models
8
+
9
+ FriendlyId can use a separate column to store slugs for models which require
10
+ some text processing.
11
+
12
+ For example, blog applications typically use a post title to provide the basis
13
+ of a search engine friendly URL. Such identifiers typically lack uppercase
14
+ characters, use ASCII to approximate UTF-8 character, and strip out other
15
+ characters which may make them aesthetically unappealing or error-prone when
16
+ used in a URL.
17
+
18
+ class Post < ActiveRecord::Base
19
+ extend FriendlyId
20
+ friendly_id :title, :use => :slugged
21
+ end
22
+
23
+ @post = Post.create(:title => "This is the first post!")
24
+ @post.friendly_id # returns "this-is-the-first-post"
25
+ redirect_to @post # the URL will be /posts/this-is-the-first-post
26
+
27
+ In general, use slugs by default unless you know for sure you don't need them.
28
+ To activate the slugging functionality, use the {FriendlyId::Slugged} module.
29
+
30
+ FriendlyId will generate slugs from a method or column that you specify, and
31
+ store them in a field in your model. By default, this field must be named
32
+ +:slug+, though you may change this using the
33
+ {FriendlyId::Slugged::Configuration#slug_column slug_column} configuration
34
+ option. You should add an index to this column, and in most cases, make it
35
+ unique. You may also wish to constrain it to NOT NULL, but this depends on your
36
+ app's behavior and requirements.
37
+
38
+ === Example Setup
39
+
40
+ # your model
41
+ class Post < ActiveRecord::Base
42
+ extend FriendlyId
43
+ friendly_id :title, :use => :slugged
44
+ validates_presence_of :title, :slug, :body
45
+ end
46
+
47
+ # a migration
48
+ class CreatePosts < ActiveRecord::Migration
49
+ def self.up
50
+ create_table :posts do |t|
51
+ t.string :title, :null => false
52
+ t.string :slug, :null => false
53
+ t.text :body
54
+ end
55
+
56
+ add_index :posts, :slug, :unique => true
57
+ end
58
+
59
+ def self.down
60
+ drop_table :posts
61
+ end
62
+ end
63
+
64
+ === Working With Slugs
65
+
66
+ ==== Formatting
67
+
68
+ By default, FriendlyId uses Active Support's
69
+ paramaterize[http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize]
70
+ method to create slugs. This method will intelligently replace spaces with
71
+ dashes, and Unicode Latin characters with ASCII approximations:
72
+
73
+ movie = Movie.create! :title => "Der Preis fürs Überleben"
74
+ movie.slug #=> "der-preis-furs-uberleben"
75
+
76
+ ==== Uniqueness
77
+
78
+ When you try to insert a record that would generate a duplicate friendly id,
79
+ FriendlyId will append a sequence to the generated slug to ensure uniqueness:
80
+
81
+ car = Car.create :title => "Peugot 206"
82
+ car2 = Car.create :title => "Peugot 206"
83
+
84
+ car.friendly_id #=> "peugot-206"
85
+ car2.friendly_id #=> "peugot-206--2"
86
+
87
+ ==== Sequence Separator - The Two Dashes
88
+
89
+ By default, FriendlyId uses two dashes to separate the slug from a sequence.
90
+
91
+ You can change this with the {FriendlyId::Slugged::Configuration#sequence_separator
92
+ sequence_separator} configuration option.
93
+
94
+ ==== Column or Method?
95
+
96
+ FriendlyId always uses a method as the basis of the slug text - not a column. It
97
+ first glance, this may sound confusing, but remember that Active Record provides
98
+ methods for each column in a model's associated table, and that's what
99
+ FriendlyId uses.
100
+
101
+ Here's an example of a class that uses a custom method to generate the slug:
102
+
103
+ class Person < ActiveRecord::Base
104
+ friendly_id :name_and_location
105
+ def name_and_location
106
+ "#{name} from #{location}"
107
+ end
108
+ end
109
+
110
+ bob = Person.create! :name => "Bob Smith", :location => "New York City"
111
+ bob.friendly_id #=> "bob-smith-from-new-york-city"
112
+
113
+ ==== Providing Your Own Slug Processing Method
114
+
115
+ You can override {FriendlyId::Slugged#normalize_friendly_id} in your model for
116
+ total control over the slug format.
117
+
118
+ ==== Deciding When to Generate New Slugs
119
+
120
+ Overriding {FriendlyId::Slugged#should_generate_new_friendly_id?} lets you
121
+ control whether new friendly ids are created when a model is updated. For
122
+ example, if you only want to generate slugs once and then treat them as
123
+ read-only:
124
+
125
+ class Post < ActiveRecord::Base
126
+ extend FriendlyId
127
+ friendly_id :title, :use => :slugged
128
+
129
+ def should_generate_new_friendly_id?
130
+ new_record?
131
+ end
132
+ end
133
+
134
+ post = Post.create!(:title => "Hello world!")
135
+ post.slug #=> "hello-world"
136
+ post.title = "Hello there, world!"
137
+ post.save!
138
+ post.slug #=> "hello-world"
139
+
140
+ ==== Locale-specific Transliterations
141
+
142
+ Active Support's +parameterize+ uses
143
+ transliterate[http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-transliterate],
144
+ which in turn can use I18n's transliteration rules to consider the current
145
+ locale when replacing Latin characters:
146
+
147
+ # config/locales/de.yml
148
+ de:
149
+ i18n:
150
+ transliterate:
151
+ rule:
152
+ ü: "ue"
153
+ ö: "oe"
154
+ etc...
155
+
156
+ movie = Movie.create! :title => "Der Preis fürs Überleben"
157
+ movie.slug #=> "der-preis-fuers-ueberleben"
158
+
159
+ This functionality was in fact taken from earlier versions of FriendlyId.
160
+
161
+ ==== Gotchas: Common Problems
162
+
163
+ ===== Slugs That Begin With Numbers
164
+
165
+ Ruby's `to_i` function casts strings to integers in such a way that +23abc.to_i+
166
+ returns 23. Because FriendlyId falls back to finding by numeric id, this means
167
+ that if you attempt to find a record with a non-existant slug, and that slug
168
+ begins with a number, your find will probably return the wrong record.
169
+
170
+ There are two fairly simple ways to avoid this:
171
+
172
+ * Use validations to ensure that slugs don't begin with numbers.
173
+ * Use explicit finders like +find_by_id+ to always find by the numeric id, or
174
+ +find_by_slug+ to always find using the friendly id.
175
+
176
+ ===== Concurrency Issues
177
+
178
+ FriendlyId uses a before_validation callback to generate and set the slug. This
179
+ means that if you create two model instances before saving them, it's possible
180
+ they will generate the same slug, and the second save will fail.
181
+
182
+ This can happen in two fairly normal cases: the first, when a model using nested
183
+ attributes creates more than one record for a model that uses friendly_id. The
184
+ second, in concurrent code, either in threads or multiple processes.
185
+
186
+ To solve the nested attributes issue, I recommend simply avoiding them when
187
+ creating more than one nested record for a model that uses FriendlyId. See {this
188
+ Github issue}[https://github.com/norman/friendly_id/issues/185] for discussion.
189
+
190
+ To solve the concurrency issue, I recommend locking the model's table against
191
+ inserts while when saving the record. See {this Github
192
+ issue}[https://github.com/norman/friendly_id/issues/180] for discussion.
193
+
194
+ =end
195
+ module Slugged
196
+
197
+ # Sets up behavior and configuration options for FriendlyId's slugging
198
+ # feature.
199
+ def self.included(model_class)
200
+ model_class.friendly_id_config.instance_eval do
201
+ self.class.send :include, Configuration
202
+ self.slug_generator_class ||= Class.new(SlugGenerator)
203
+ defaults[:slug_column] ||= 'slug'
204
+ defaults[:sequence_separator] ||= '--'
205
+ end
206
+ model_class.before_validation :set_slug
207
+ end
208
+
209
+ # Process the given value to make it suitable for use as a slug.
210
+ #
211
+ # This method is not intended to be invoked directly; FriendlyId uses it
212
+ # internaly to process strings into slugs.
213
+ #
214
+ # However, if FriendlyId's default slug generation doesn't suite your needs,
215
+ # you can override this method in your model class to control exactly how
216
+ # slugs are generated.
217
+ #
218
+ # === Example
219
+ #
220
+ # class Person < ActiveRecord::Base
221
+ # friendly_id :name_and_location
222
+ #
223
+ # def name_and_location
224
+ # "#{name} from #{location}"
225
+ # end
226
+ #
227
+ # # Use default slug, but upper case and with underscores
228
+ # def normalize_friendly_id(string)
229
+ # super.upcase.gsub("-", "_")
230
+ # end
231
+ # end
232
+ #
233
+ # bob = Person.create! :name => "Bob Smith", :location => "New York City"
234
+ # bob.friendly_id #=> "BOB_SMITH_FROM_NEW_YORK_CITY"
235
+ #
236
+ # === More Resources
237
+ #
238
+ # You might want to look into Babosa[https://github.com/norman/babosa],
239
+ # which is the slugging library used by FriendlyId prior to version 4, which
240
+ # offers some specialized functionality missing from Active Support.
241
+ #
242
+ # @param [#to_s] value The value used as the basis of the slug.
243
+ # @return The candidate slug text, without a sequence.
244
+ def normalize_friendly_id(value)
245
+ # Fix to number based slugs which get mistaken as id's
246
+ value = value.to_s.parameterize
247
+ is_number = true if Float(value) rescue false
248
+ if is_number
249
+ return "#{rand_slug}#{value}"
250
+ end
251
+ value
252
+ end
253
+
254
+ # Generate random 10 character string for use in number error situations
255
+ # Instead of rejecting id/number based slug
256
+ def rand_slug
257
+ (0...10).map{ ('a'..'z').to_a[rand(26)] }.join
258
+ end
259
+
260
+ # Whether to generate a new slug.
261
+ #
262
+ # You can override this method in your model if, for example, you only want
263
+ # slugs to be generated once, and then never updated.
264
+ def should_generate_new_friendly_id?
265
+ base = send(friendly_id_config.base)
266
+ slug_value = send(friendly_id_config.slug_column)
267
+
268
+ # If the slug base is nil, and the slug field is nil, then we're going to
269
+ # leave the slug column NULL.
270
+ return false if base.nil? && slug_value.nil?
271
+ # Otherwise, if this is a new record, we're definitely going to try to
272
+ # create a new slug.
273
+ return true if new_record?
274
+ slug_base = normalize_friendly_id(base)
275
+ separator = Regexp.escape friendly_id_config.sequence_separator
276
+ # If the slug base (with and without sequence) is different from either the current
277
+ # friendly id or the slug value, then we'll generate a new friendly_id.
278
+ compare = (current_friendly_id || slug_value)
279
+ slug_base != compare && slug_base != compare.try(:sub, /#{separator}[\d]*\z/, '')
280
+ end
281
+
282
+ # Sets the slug.
283
+ # FIXME: This method sucks and the logic is pretty dubious.
284
+ def set_slug(normalized_slug = nil)
285
+ if normalized_slug || should_generate_new_friendly_id?
286
+ normalized_slug ||= normalize_friendly_id send(friendly_id_config.base)
287
+ generator = friendly_id_config.slug_generator_class.new self, normalized_slug
288
+ send "#{friendly_id_config.slug_column}=", generator.generate
289
+ end
290
+ end
291
+ private :set_slug
292
+
293
+ # This module adds the +:slug_column+, and +:sequence_separator+, and
294
+ # +:slug_generator_class+ configuration options to
295
+ # {FriendlyId::Configuration FriendlyId::Configuration}.
296
+ module Configuration
297
+ attr_writer :slug_column, :sequence_separator
298
+ attr_accessor :slug_generator_class
299
+
300
+ # Makes FriendlyId use the slug column for querying.
301
+ # @return String The slug column.
302
+ def query_field
303
+ slug_column
304
+ end
305
+
306
+ # The string used to separate a slug base from a numeric sequence.
307
+ #
308
+ # By default, +--+ is used to separate the slug from the sequence.
309
+ # FriendlyId uses two dashes to distinguish sequences from slugs with
310
+ # numbers in their name.
311
+ #
312
+ # You can change the default separator by setting the
313
+ # {FriendlyId::Slugged::Configuration#sequence_separator
314
+ # sequence_separator} configuration option.
315
+ #
316
+ # For obvious reasons, you should avoid setting it to "+-+" unless you're
317
+ # sure you will never want to have a friendly id with a number in it.
318
+ # @return String The sequence separator string. Defaults to "+--+".
319
+ def sequence_separator
320
+ @sequence_separator or defaults[:sequence_separator]
321
+ end
322
+
323
+ # The column that will be used to store the generated slug.
324
+ def slug_column
325
+ @slug_column or defaults[:slug_column]
326
+ end
327
+ end
328
+ end
329
+ end
@@ -0,0 +1,114 @@
1
+ # encoding: utf-8
2
+ require "thread"
3
+ require "friendly_id/base"
4
+ require "friendly_id/object_utils"
5
+ require "friendly_id/configuration"
6
+ require "friendly_id/finder_methods"
7
+
8
+ =begin
9
+
10
+ == About FriendlyId
11
+
12
+ FriendlyId is an add-on to Ruby's Active Record that allows you to replace ids
13
+ in your URLs with strings:
14
+
15
+ # without FriendlyId
16
+ http://example.com/states/4323454
17
+
18
+ # with FriendlyId
19
+ http://example.com/states/washington
20
+
21
+ It requires few changes to your application code and offers flexibility,
22
+ performance and a well-documented codebase.
23
+
24
+ === Core Concepts
25
+
26
+ ==== Slugs
27
+
28
+ The concept of "slugs[http://en.wikipedia.org/wiki/Slug_(web_publishing)]" is at
29
+ the heart of FriendlyId.
30
+
31
+ A slug is the part of a URL which identifies a page using human-readable
32
+ keywords, rather than an opaque identifier such as a numeric id. This can make
33
+ your application more friendly both for users and search engine.
34
+
35
+ ==== Finders: Slugs Act Like Numeric IDs
36
+
37
+ To the extent possible, FriendlyId lets you treat text-based identifiers like
38
+ normal IDs. This means that you can perform finds with slugs just like you do
39
+ with numeric ids:
40
+
41
+ Person.find(82542335)
42
+ Person.find("joe")
43
+
44
+ =end
45
+ module FriendlyId
46
+
47
+ # The current version.
48
+ VERSION = "4.0.9.1"
49
+
50
+ @mutex = Mutex.new
51
+
52
+ autoload :History, "friendly_id/history"
53
+ autoload :Slug, "friendly_id/slug"
54
+ autoload :SimpleI18n, "friendly_id/simple_i18n"
55
+ autoload :Reserved, "friendly_id/reserved"
56
+ autoload :Scoped, "friendly_id/scoped"
57
+ autoload :Slugged, "friendly_id/slugged"
58
+ autoload :Globalize, "friendly_id/globalize"
59
+
60
+ # FriendlyId takes advantage of `extended` to do basic model setup, primarily
61
+ # extending {FriendlyId::Base} to add {FriendlyId::Base#friendly_id
62
+ # friendly_id} as a class method.
63
+ #
64
+ # Previous versions of FriendlyId simply patched ActiveRecord::Base, but this
65
+ # version tries to be less invasive.
66
+ #
67
+ # In addition to adding {FriendlyId::Base#friendly_id friendly_id}, the class
68
+ # instance variable +@friendly_id_config+ is added. This variable is an
69
+ # instance of an anonymous subclass of {FriendlyId::Configuration}. This
70
+ # allows subsequently loaded modules like {FriendlyId::Slugged} and
71
+ # {FriendlyId::Scoped} to add functionality to the configuration class only
72
+ # for the current class, rather than monkey patching
73
+ # {FriendlyId::Configuration} directly. This isolates other models from large
74
+ # feature changes an addon to FriendlyId could potentially introduce.
75
+ #
76
+ # The upshot of this is, you can have two Active Record models that both have
77
+ # a @friendly_id_config, but each config object can have different methods
78
+ # and behaviors depending on what modules have been loaded, without
79
+ # conflicts. Keep this in mind if you're hacking on FriendlyId.
80
+ #
81
+ # For examples of this, see the source for {Scoped.included}.
82
+ def self.extended(model_class)
83
+ return if model_class.respond_to? :friendly_id
84
+ class << model_class
85
+ alias relation_without_friendly_id relation
86
+ end
87
+ model_class.instance_eval do
88
+ extend Base
89
+ @friendly_id_config = Class.new(Configuration).new(self)
90
+ FriendlyId.defaults.call @friendly_id_config
91
+ end
92
+ end
93
+
94
+ # Allow developers to `include` FriendlyId or `extend` it.
95
+ def self.included(model_class)
96
+ model_class.extend self
97
+ end
98
+
99
+ # Set global defaults for all models using FriendlyId.
100
+ #
101
+ # The default defaults are to use the +:reserved+ module and nothing else.
102
+ #
103
+ # @example
104
+ # FriendlyId.defaults do |config|
105
+ # config.base = :name
106
+ # config.use :slugged
107
+ # end
108
+ def self.defaults(&block)
109
+ @mutex.synchronize do
110
+ @defaults = block if block_given?
111
+ @defaults ||= lambda {|config| config.use :reserved}
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,17 @@
1
+ require 'rails/generators'
2
+ require "rails/generators/active_record"
3
+
4
+ # This generator adds a migration for the {FriendlyId::History
5
+ # FriendlyId::History} addon.
6
+ class FriendlyIdGenerator < Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+ extend ActiveRecord::Generators::Migration
9
+
10
+ source_root File.expand_path('../../friendly_id', __FILE__)
11
+
12
+ # Copies the migration template to db/migrate.
13
+ def copy_files(*args)
14
+ migration_template 'migration.rb', 'db/migrate/create_friendly_id_slugs.rb'
15
+ end
16
+
17
+ end
data/test/base_test.rb ADDED
@@ -0,0 +1,72 @@
1
+ require "helper"
2
+
3
+ class CoreTest < MiniTest::Unit::TestCase
4
+ include FriendlyId::Test
5
+
6
+ test "friendly_id can be added using 'extend'" do
7
+ klass = Class.new(ActiveRecord::Base) do
8
+ extend FriendlyId
9
+ end
10
+ assert klass.respond_to? :friendly_id
11
+ end
12
+
13
+ test "friendly_id can be added using 'include'" do
14
+ klass = Class.new(ActiveRecord::Base) do
15
+ include FriendlyId
16
+ end
17
+ assert klass.respond_to? :friendly_id
18
+ end
19
+
20
+ test "friendly_id should accept a base and a hash" do
21
+ klass = Class.new(ActiveRecord::Base) do
22
+ self.abstract_class = true
23
+ extend FriendlyId
24
+ friendly_id :foo, :use => :slugged, :slug_column => :bar
25
+ end
26
+ assert klass < FriendlyId::Slugged
27
+ assert_equal :foo, klass.friendly_id_config.base
28
+ assert_equal :bar, klass.friendly_id_config.slug_column
29
+ end
30
+
31
+
32
+ test "friendly_id should accept a block" do
33
+ klass = Class.new(ActiveRecord::Base) do
34
+ self.abstract_class = true
35
+ extend FriendlyId
36
+ friendly_id :foo do |config|
37
+ config.use :slugged
38
+ config.base = :foo
39
+ config.slug_column = :bar
40
+ end
41
+ end
42
+ assert klass < FriendlyId::Slugged
43
+ assert_equal :foo, klass.friendly_id_config.base
44
+ assert_equal :bar, klass.friendly_id_config.slug_column
45
+ end
46
+
47
+ test "the block passed to friendly_id should be evaluated before arguments" do
48
+ klass = Class.new(ActiveRecord::Base) do
49
+ self.abstract_class = true
50
+ extend FriendlyId
51
+ friendly_id :foo do |config|
52
+ config.base = :bar
53
+ end
54
+ end
55
+ assert_equal :foo, klass.friendly_id_config.base
56
+ end
57
+
58
+ test "should allow defaults to be set via a block" do
59
+ begin
60
+ FriendlyId.defaults do |config|
61
+ config.base = :foo
62
+ end
63
+ klass = Class.new(ActiveRecord::Base) do
64
+ self.abstract_class = true
65
+ extend FriendlyId
66
+ end
67
+ assert_equal :foo, klass.friendly_id_config.base
68
+ ensure
69
+ FriendlyId.instance_variable_set :@defaults, nil
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,8 @@
1
+ source :rubygems
2
+
3
+ gem "sqlite3", "~> 1.3.4"
4
+ gem "activerecord", "3.0.10"
5
+ gem "minitest", "~> 2.4.0"
6
+ gem "mocha", "~> 0.9.12"
7
+ gem "ancestry"
8
+ gem "rake"
@@ -0,0 +1,34 @@
1
+ require File.expand_path("../../../helper", __FILE__)
2
+
3
+ require "ancestry"
4
+
5
+ ActiveRecord::Migration.create_table("things") do |t|
6
+ t.string :name
7
+ t.string :slug
8
+ t.string :ancestry
9
+ end
10
+ ActiveRecord::Migration.add_index :things, :ancestry
11
+
12
+ class Thing < ActiveRecord::Base
13
+ extend FriendlyId
14
+ friendly_id do |config|
15
+ config.use :slugged
16
+ config.use :scoped
17
+ config.base = :name
18
+ config.scope = :ancestry
19
+ end
20
+ has_ancestry
21
+ end
22
+
23
+ class AncestryTest < MiniTest::Unit::TestCase
24
+ include FriendlyId::Test
25
+
26
+ test "should sequence slugs when scoped by ancestry" do
27
+ 3.times.inject([]) do |memo, _|
28
+ memo << Thing.create!(:name => "a", :parent => memo.last)
29
+ end.each do |thing|
30
+ assert_equal "a", thing.friendly_id
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,8 @@
1
+ source :rubygems
2
+
3
+ gem "pg"
4
+ gem "mysql2"
5
+ gem "mocha"
6
+ gem "activerecord", "3.1.1"
7
+ gem "sqlite3"
8
+ gem "fatalistic"
@@ -0,0 +1,37 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.1.1)
5
+ activesupport (= 3.1.1)
6
+ builder (~> 3.0.0)
7
+ i18n (~> 0.6)
8
+ activerecord (3.1.1)
9
+ activemodel (= 3.1.1)
10
+ activesupport (= 3.1.1)
11
+ arel (~> 2.2.1)
12
+ tzinfo (~> 0.3.29)
13
+ activesupport (3.1.1)
14
+ multi_json (~> 1.0)
15
+ arel (2.2.1)
16
+ builder (3.0.0)
17
+ fatalistic (0.0.1)
18
+ i18n (0.6.0)
19
+ metaclass (0.0.1)
20
+ mocha (0.10.0)
21
+ metaclass (~> 0.0.1)
22
+ multi_json (1.0.3)
23
+ mysql2 (0.3.11)
24
+ pg (0.11.0)
25
+ sqlite3 (1.3.5)
26
+ tzinfo (0.3.31)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ activerecord (= 3.1.1)
33
+ fatalistic
34
+ mocha
35
+ mysql2
36
+ pg
37
+ sqlite3
@@ -0,0 +1,45 @@
1
+ ENV["DB"] = "postgres"
2
+
3
+ require "thread"
4
+ require File.expand_path("../../../helper", __FILE__)
5
+ require "active_record/locking/fatalistic"
6
+
7
+ ActiveRecord::Migration.tap do |m|
8
+ m.drop_table "things"
9
+ m.create_table("things") do |t|
10
+ t.string :name
11
+ t.string :slug
12
+ end
13
+ m.add_index :things, :slug, :unique => true
14
+ end
15
+
16
+ class Thing < ActiveRecord::Base
17
+ extend FriendlyId
18
+ friendly_id :name, :use => :slugged
19
+ end
20
+
21
+ $things = 10.times.map do
22
+ Thing.new :name => "a b c"
23
+ end
24
+
25
+ $mutex = Mutex.new
26
+
27
+ def save_thing
28
+ thing = $mutex.synchronize do
29
+ $things.pop
30
+ end
31
+ if thing.nil? then return end
32
+ Thing.lock do
33
+ thing.save!
34
+ print "#{thing.friendly_id}\n"
35
+ end
36
+ true
37
+ end
38
+
39
+ 2.times.map do
40
+ Thread.new do
41
+ while true do
42
+ break unless save_thing
43
+ end
44
+ end
45
+ end.map(&:value)