geothird_friendly_id 4.0.9.1

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