friendly_id4 4.0.0.beta1

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.
@@ -0,0 +1,111 @@
1
+ require "rubygems"
2
+ require "rake/testtask"
3
+
4
+ def rubies(&block)
5
+ ["ruby-1.9.2-p180", "ree-1.8.7-2011.03", "jruby-1.6.2", "rbx-2.0.0pre"].each do |ruby|
6
+ ENV["RB"] = ruby
7
+ yield
8
+ ENV["RB"] = nil
9
+ end
10
+ end
11
+
12
+ def versions(&block)
13
+ ["3.1.0.rc4", "3.0.9"].each do |version|
14
+ ENV["AR"] = version
15
+ yield
16
+ ENV["AR"] = nil
17
+ end
18
+ end
19
+
20
+ def adapters(&block)
21
+ ["mysql", "postgres", "sqlite3"].each do |adapter|
22
+ ENV["DB"] = adapter
23
+ yield
24
+ ENV["DB"] = nil
25
+ end
26
+ end
27
+
28
+ task :default => :test
29
+
30
+ Rake::TestTask.new do |t|
31
+ t.test_files = FileList['test/*_test.rb']
32
+ t.verbose = true
33
+ end
34
+
35
+ task :clean do
36
+ %x{rm -rf *.gem doc pkg}
37
+ %x{rm -f `find . -name '*.rbc'`}
38
+ end
39
+
40
+ task :gem do
41
+ %x{gem build friendly_id.gemspec}
42
+ end
43
+
44
+ task :yard do
45
+ %x{bundle exec yard doc --files=*.md}
46
+ end
47
+
48
+ desc "Bundle for all supported Ruby/AR versions"
49
+ task :bundle do
50
+ rubies do
51
+ versions do
52
+ command = "#{ENV["RB"]} -S bundle"
53
+ puts "#{command} (with #{ENV['AR']})"
54
+ `#{command}`
55
+ end
56
+ end
57
+ end
58
+
59
+ namespace :test do
60
+
61
+ desc "Test with all configured adapters"
62
+ task :adapters do
63
+ adapters {|a| puts %x{rake test}}
64
+ end
65
+
66
+ desc "Test with all configured Rubies"
67
+ task :rubies do
68
+ rubies {|r| puts %x{rake-#{ENV["RB"]} test}}
69
+ end
70
+
71
+ desc "Test with all configured versions"
72
+ task :versions do
73
+ versions {|v| puts %x{rake test}}
74
+ end
75
+
76
+ desc "Test all rubies, versions and adapters"
77
+ task :prerelease do
78
+ rubies do
79
+ versions do
80
+ adapters do
81
+ puts %x{rake test}
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ desc "Run each test class in a separate process"
88
+ task :isolated do
89
+ dir = File.expand_path("../test", __FILE__)
90
+ Dir["#{dir}/*_test.rb"].each do |test|
91
+ puts "Running #{test}:"
92
+ puts %x{ruby #{test}}
93
+ end
94
+ end
95
+ end
96
+
97
+ namespace :db do
98
+ desc "Set up the database schema"
99
+ task :up do
100
+ require File.expand_path("../test/helper", __FILE__)
101
+ FriendlyId::Test::Schema.up
102
+ end
103
+
104
+ desc "Destroy the database schema"
105
+ task :down do
106
+ require File.expand_path("../test/helper", __FILE__)
107
+ FriendlyId::Test::Schema.down
108
+ end
109
+ end
110
+
111
+ task :doc => :yard
@@ -0,0 +1,142 @@
1
+ # What's new in FriendlyId 4.0?
2
+
3
+ This is a rewrite/rethink of FriendlyId. It will probably be released some time
4
+ in August or September 2011, once I've had the chance to actually use it in a
5
+ real website for a while.
6
+
7
+ It's probably not wise to use this on a real site right now unless you're
8
+ comfortable with the source code and willing to fix bugs that will likely occur.
9
+
10
+ That said, I will soon be deploying this on a high-traffic, production site, so
11
+ I have a personal stake in making this work well. Your feedback is most welcome.
12
+
13
+ If you want to try it out, grab the source, or [install the
14
+ gem](https://rubygems.org/gems/friendly_id4).
15
+
16
+ ## Back to basics
17
+
18
+ This isn't the "big rewrite," it's the "small rewrite."
19
+
20
+ Adding new features with each release is not sustainable. This release *removes*
21
+ features, but makes it possible to add them back as addons. We can also remove
22
+ some complexity by relying on the better default functionality provided by newer
23
+ versions of Active Support and Active Record. Let's see how small we can make
24
+ this!
25
+
26
+ Here's what's changed:
27
+
28
+ ## Active Record 3+ only
29
+
30
+ For 2.3 support, you can use FriendlyId 3, which will continue to be maintained
31
+ until people don't want it any more.
32
+
33
+ ## In-table slugs
34
+
35
+ FriendlyId no longer creates a separate slugs table - it just stores the
36
+ generated slug value in the model table, which is simpler, faster and what most
37
+ people seem to want. Keeping slug history in a separate table is an optional
38
+ add-on for FriendlyId 4.
39
+
40
+ ## No more multiple finds
41
+
42
+ Person.find "joe-schmoe" # Supported
43
+ Person.find ["joe-schmoe", "john-doe"] # No longer supported
44
+
45
+ If you want find by more than one friendly id, build your own query:
46
+
47
+ Person.where(:slug => ["joe-schmoe", "john-doe"])
48
+
49
+ This lets us do *far* less monkeypatching in Active Record. How much less?
50
+ FriendlyId overrides the base find with a mere 2 lines of code, and otherwise
51
+ changes nothing else. This means more stability and less breakage between Rails
52
+ updates.
53
+
54
+ ## No more finder status
55
+
56
+ FriendlyId 3 offered finder statuses to help you determine when an outdated
57
+ or non-friendly id was used to find the record, so that you could decide whether
58
+ to permanently redirect to the canonical URL. However, there's a simpler way to
59
+ do that, so this feature has been removed:
60
+
61
+ if request.path != person_path(@person)
62
+ return redirect_to @person, :status => :moved_permanently
63
+ end
64
+
65
+ ## No more slug history - unless you want it
66
+
67
+ Since slugs are now stored in-table, when you update them, finds for the
68
+ previous slug will no longer work. This can be a problem for permalinks, since
69
+ renaming a friendly_id will lead to 404's.
70
+
71
+ This was transparently handled by FriendlyId 3, but there were three problems:
72
+
73
+ * Not everybody wants or needs this
74
+ * Performance was negatively affected
75
+ * Determining whether a current or old id was used was expensive, clunky, and
76
+ inconsistent when finding inside relations.
77
+
78
+ Here's how to do this in FriendlyId 4:
79
+
80
+ class PostsController < ApplicationController
81
+
82
+ before_filter :find_post
83
+
84
+ ...
85
+
86
+ def find_post
87
+ return unless params[:id]
88
+ @post = begin
89
+ Post.find params[:id]
90
+ rescue ActiveRecord::RecordNotFound
91
+ Post.find_by_friendly_id params[:id]
92
+ end
93
+ # If an old id or a numeric id was used to find the record, then
94
+ # the request path will not match the post_path, and we should do
95
+ # a 301 redirect that uses the current friendly_id
96
+ if request.path != post_path(@post)
97
+ return redirect_to @post, :status => :moved_permanently
98
+ end
99
+ end
100
+
101
+ Under FriendlyId 4 this is a little more verbose, but offers much finer-grained
102
+ control over the finding process, performs better, and has a much simpler
103
+ implementation.
104
+
105
+ ## "Reserved words" are now just a normal validation
106
+
107
+ Rather than use a custom reserved words validator, use the validations provided
108
+ by Active Record. FriendlyId still reserves "new" and "edit" by default to avoid
109
+ routing problems.
110
+
111
+ validates_exclusion_of :name, :in => ["bad", "word"]
112
+
113
+ You can configure the default words reserved by FriendlyId in
114
+ `FriendlyId::Configuration::DEFAULTS[:reserved_words]`.
115
+
116
+ ## "Allow nil" is now just another validation
117
+
118
+ Previous versions of FriendlyId offered a special option to allow nil slug
119
+ values, but this is now the default. If you don't want this, then simply add a
120
+ validation to the slug column, and/or declare the column `NOT NULL` in your
121
+ database.
122
+
123
+ ## Bye-bye Babosa
124
+
125
+ [Babosa](http://github.com/norman/babosa) is FriendlyId 3's slugging library.
126
+
127
+ FriendlyId 4 doesn't use it by default because the most important pieces of it
128
+ were already accepted into Active Support 3.
129
+
130
+ However, Babosa is still useful, for example, for idiomatically transliterating
131
+ Cyrillic ([or other
132
+ language](https://github.com/norman/babosa/tree/master/lib/babosa/transliterator))
133
+ strings to ASCII. It's very easy to include - just override
134
+ `#normalize_friendly_id` in your model:
135
+
136
+ class MyModel < ActiveRecord::Base
137
+ ...
138
+
139
+ def normalize_friendly_id(text)
140
+ text.to_slug.normalize! :transliterate => :russian
141
+ end
142
+ end
@@ -0,0 +1,71 @@
1
+ require File.expand_path("../test/helper", __FILE__)
2
+ require "ffaker"
3
+ require "friendly_id/migration"
4
+
5
+ N = 1000
6
+
7
+ migration do |m|
8
+ m.add_column :users, :slug, :string
9
+ m.add_index :users, :slug, :unique => true
10
+ end
11
+
12
+ migration do |m|
13
+ m.create_table :posts do |t|
14
+ t.string :name
15
+ t.string :slug
16
+ end
17
+ m.add_index :posts, :slug, :unique => true
18
+ end
19
+ CreateFriendlyIdSlugs.up
20
+
21
+
22
+ class Array
23
+ def rand
24
+ self[Kernel.rand(length)]
25
+ end
26
+ end
27
+
28
+ class User
29
+ include FriendlyId::Slugged
30
+ has_friendly_id :name
31
+ end
32
+
33
+ class Post < ActiveRecord::Base
34
+ include FriendlyId::History
35
+ has_friendly_id :name
36
+ end
37
+
38
+ USERS = []
39
+ BOOKS = []
40
+ POSTS = []
41
+
42
+ 100.times do
43
+ name = Faker::Name.name
44
+ USERS << (User.create! :name => name).friendly_id
45
+ POSTS << (Post.create! :name => name).friendly_id
46
+ BOOKS << (Book.create! :name => name).id
47
+ end
48
+
49
+ Benchmark.bmbm do |x|
50
+ x.report 'find (without FriendlyId)' do
51
+ N.times {Book.find BOOKS.rand}
52
+ end
53
+ x.report 'find (in-table slug)' do
54
+ N.times {User.find USERS.rand}
55
+ end
56
+ x.report 'find (external slug)' do
57
+ N.times {Post.find_by_friendly_id POSTS.rand}
58
+ end
59
+
60
+ x.report 'insert (without FriendlyId)' do
61
+ N.times {transaction {Book.create :name => Faker::Name.name}}
62
+ end
63
+
64
+ x.report 'insert (in-table-slug)' do
65
+ N.times {transaction {User.create :name => Faker::Name.name}}
66
+ end
67
+
68
+ x.report 'insert (external slug)' do
69
+ N.times {transaction {Post.create :name => Faker::Name.name}}
70
+ end
71
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ require "friendly_id/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "friendly_id4"
8
+ s.version = FriendlyId::Version::STRING
9
+ s.authors = ["Norman Clarke"]
10
+ s.email = ["norman@njclarke.com"]
11
+ s.homepage = "http://norman.github.com/friendly_id"
12
+ s.summary = "A comprehensive slugging and pretty-URL plugin."
13
+ s.rubyforge_project = "friendly_id"
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test}/*`.split("\n")
16
+ s.require_paths = ["lib"]
17
+
18
+ s.add_development_dependency "activerecord", "~> 3.0"
19
+ s.add_development_dependency "sqlite3", "~> 1.3"
20
+ s.add_development_dependency "cutest", "~> 1.1.2"
21
+ s.add_development_dependency "ffaker"
22
+ s.add_development_dependency "maruku"
23
+ s.add_development_dependency "yard"
24
+ s.add_development_dependency "mocha"
25
+
26
+ s.description = <<-EOM
27
+ FriendlyId is the "Swiss Army bulldozer" of slugging and permalink plugins
28
+ for Ruby on Rails. It allows you to create pretty URL's and work with
29
+ human-friendly strings as if they were numeric ids for ActiveRecord models.
30
+ EOM
31
+ end
@@ -0,0 +1,14 @@
1
+ require "friendly_id/base"
2
+ require "friendly_id/model"
3
+ require "friendly_id/object_utils"
4
+ require "friendly_id/configuration"
5
+ require "friendly_id/finder_methods"
6
+
7
+ # FriendlyId is a comprehensive Ruby library for ActiveRecord permalinks and
8
+ # slugs.
9
+ # @author Norman Clarke
10
+ module FriendlyId
11
+ autoload :Slugged, "friendly_id/slugged"
12
+ autoload :Scoped, "friendly_id/scoped"
13
+ autoload :History, "friendly_id/history"
14
+ end
@@ -0,0 +1,29 @@
1
+ module FriendlyId
2
+ # Class methods that will be added to ActiveRecord::Base.
3
+ module Base
4
+ extend self
5
+
6
+ def has_friendly_id(*args)
7
+ options = args.extract_options!
8
+ base = args.shift
9
+ friendly_id_config.set options.merge(:base => base)
10
+ include Model
11
+ # @NOTE: AR-specific code here
12
+ validates_exclusion_of base, :in => Configuration::DEFAULTS[:reserved_words]
13
+ before_save do |record|
14
+ record.instance_eval {@current_friendly_id = friendly_id}
15
+ end
16
+ self
17
+ end
18
+
19
+ def friendly_id_config
20
+ @friendly_id_config ||= Configuration.new(self)
21
+ end
22
+
23
+ def uses_friendly_id?
24
+ defined? @friendly_id_config
25
+ end
26
+ end
27
+ end
28
+
29
+ ActiveRecord::Base.extend FriendlyId::Base
@@ -0,0 +1,31 @@
1
+ module FriendlyId
2
+ # The configuration paramters passed to +has_friendly_id+ will be stored
3
+ # in this object.
4
+ class Configuration
5
+ attr_accessor :base
6
+ attr_reader :klass
7
+
8
+ DEFAULTS = {
9
+ :config_error_message => 'FriendlyId has no such config option "%s"',
10
+ :reserved_words => ["new", "edit"]
11
+ }
12
+
13
+ def initialize(klass, values = nil)
14
+ @klass = klass
15
+ set values
16
+ end
17
+
18
+ def method_missing(symbol, *args, &block)
19
+ option = symbol.to_s.gsub(/=\z/, '')
20
+ raise ArgumentError, DEFAULTS[:config_error_message] % option
21
+ end
22
+
23
+ def set(values)
24
+ values and values.each {|name, value| self.send "#{name}=", value}
25
+ end
26
+
27
+ def query_field
28
+ base
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,14 @@
1
+ module FriendlyId
2
+ # These methods will override the finder methods in ActiveRecord::Relation.
3
+ module FinderMethods
4
+
5
+ protected
6
+
7
+ def find_one(id)
8
+ return super if !@klass.uses_friendly_id? or id.unfriendly_id?
9
+ where(@klass.friendly_id_config.query_field => id).first or super
10
+ end
11
+ end
12
+ end
13
+
14
+ ActiveRecord::Relation.send :include, FriendlyId::FinderMethods