geothird_friendly_id 4.0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gemtest +0 -0
- data/.gitignore +12 -0
- data/.travis.yml +20 -0
- data/.yardopts +4 -0
- data/Changelog.md +86 -0
- data/Gemfile +15 -0
- data/Guide.rdoc +553 -0
- data/MIT-LICENSE +19 -0
- data/README.md +150 -0
- data/Rakefile +108 -0
- data/WhatsNew.md +95 -0
- data/bench.rb +63 -0
- data/friendly_id.gemspec +43 -0
- data/gemfiles/Gemfile.rails-3.0.rb +21 -0
- data/gemfiles/Gemfile.rails-3.1.rb +22 -0
- data/gemfiles/Gemfile.rails-3.2.rb +22 -0
- data/geothird_friendly_id.gemspec +43 -0
- data/lib/friendly_id/base.rb +291 -0
- data/lib/friendly_id/configuration.rb +80 -0
- data/lib/friendly_id/finder_methods.rb +35 -0
- data/lib/friendly_id/globalize.rb +115 -0
- data/lib/friendly_id/history.rb +134 -0
- data/lib/friendly_id/migration.rb +18 -0
- data/lib/friendly_id/object_utils.rb +50 -0
- data/lib/friendly_id/reserved.rb +68 -0
- data/lib/friendly_id/scoped.rb +149 -0
- data/lib/friendly_id/simple_i18n.rb +95 -0
- data/lib/friendly_id/slug.rb +14 -0
- data/lib/friendly_id/slug_generator.rb +80 -0
- data/lib/friendly_id/slugged.rb +329 -0
- data/lib/friendly_id.rb +114 -0
- data/lib/generators/friendly_id_generator.rb +17 -0
- data/test/base_test.rb +72 -0
- data/test/compatibility/ancestry/Gemfile +8 -0
- data/test/compatibility/ancestry/ancestry_test.rb +34 -0
- data/test/compatibility/threading/Gemfile +8 -0
- data/test/compatibility/threading/Gemfile.lock +37 -0
- data/test/compatibility/threading/threading.rb +45 -0
- data/test/configuration_test.rb +48 -0
- data/test/core_test.rb +48 -0
- data/test/databases.yml +19 -0
- data/test/generator_test.rb +20 -0
- data/test/globalize_test.rb +57 -0
- data/test/helper.rb +87 -0
- data/test/history_test.rb +149 -0
- data/test/object_utils_test.rb +28 -0
- data/test/reserved_test.rb +40 -0
- data/test/schema.rb +79 -0
- data/test/scoped_test.rb +83 -0
- data/test/shared.rb +156 -0
- data/test/simple_i18n_test.rb +133 -0
- data/test/slugged_test.rb +280 -0
- data/test/sti_test.rb +77 -0
- 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
|
data/lib/friendly_id.rb
ADDED
@@ -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,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,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)
|