friendly_id 4.0.0.beta7 → 4.0.0.beta8

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.
data/Changelog.md CHANGED
@@ -178,11 +178,11 @@ fork, then this upgrade may causes issues.
178
178
  * New option to pass arguments to `FriendlyId::SlugString#approximate_ascii!`,
179
179
  allowing custom approximations specific to German or Spanish.
180
180
  * FriendlyId now queries against the cached_slug column, which improves performance.
181
- * {FriendlyId::SlugString} class added, allowing finer-grained control over
181
+ * FriendlyId::SlugString class added, allowing finer-grained control over
182
182
  Unicode friendly_id strings.
183
- * {FriendlyId::Configuration} class added, offering more flexible/hackable
183
+ * FriendlyId::Configuration class added, offering more flexible/hackable
184
184
  options.
185
- * FriendlyId now raises subclasses of {FriendlyId::SlugGenerationError}
185
+ * FriendlyId now raises subclasses of FriendlyId::SlugGenerationError
186
186
  depending on the error context.
187
187
  * Simple models now correctly validate friendly_id length.
188
188
  * Passing block into FriendlyId deprecated in favor of overriding
data/README.md CHANGED
@@ -24,8 +24,10 @@ FriendlyId is compatible with Active Record **3.0** and **3.1**.
24
24
 
25
25
  ## Version 4.x
26
26
 
27
- FriendlyId 4.x introduces many changes incompatible with 3.x. If you're upgrading,
28
- please read the docs to see what's new.
27
+ FriendlyId 4.x introduces many changes incompatible with 3.x. If you're
28
+ upgrading, please [read the
29
+ docs](http://norman.github.com/friendly_id/file.WhatsNew.html) to see what's
30
+ new.
29
31
 
30
32
  ## Rails Quickstart
31
33
 
@@ -35,7 +37,7 @@ please read the docs to see what's new.
35
37
 
36
38
  cd my_app
37
39
 
38
- gem "friendly_id", "~> 4.0.0.beta7"
40
+ gem "friendly_id", "~> 4.0.0.beta8"
39
41
 
40
42
 
41
43
  rails generate scaffold user name:string slug:string
@@ -60,7 +62,7 @@ please read the docs to see what's new.
60
62
  ## Docs
61
63
 
62
64
  The current docs can be found
63
- [here](http://rdoc.info/github/norman/friendly_id/a4128af31d85ee29ad8f/frames).
65
+ [here](http://norman.github.com/friendly_id/)
64
66
 
65
67
  ## Benchmarks
66
68
 
data/lib/friendly_id.rb CHANGED
@@ -81,7 +81,7 @@ In general, use slugs by default unless you know for sure you don't need them.
81
81
  module FriendlyId
82
82
 
83
83
  # The current version.
84
- VERSION = "4.0.0.beta7"
84
+ VERSION = "4.0.0.beta8"
85
85
 
86
86
  @mutex = Mutex.new
87
87
 
@@ -1,16 +1,16 @@
1
1
  require 'rails/generators'
2
2
  require 'rails/generators/migration'
3
3
 
4
+ # This generator adds a migration for the {FriendlyId::History
5
+ # FriendlyId::History} addon.
4
6
  class FriendlyIdGenerator < Rails::Generators::Base
5
-
6
7
  include Rails::Generators::Migration
7
8
 
8
- source_root File.expand_path('../../../generators/friendly_id/templates', __FILE__)
9
-
10
- class_option :"skip-migration", :type => :boolean, :desc => "Don't generate a migration for the slugs table"
9
+ source_root File.expand_path('../../friendly_id', __FILE__)
11
10
 
11
+ # Copies the migration template to db/migrate.
12
12
  def copy_files(*args)
13
- migration_template 'create_slugs.rb', "db/migrate/create_slugs.rb" unless options["skip-migration"]
13
+ migration_template 'migration.rb', 'db/migrate/create_friendly_id_slugs.rb'
14
14
  end
15
15
 
16
16
  # Taken from ActiveRecord's migration generator
@@ -21,5 +21,4 @@ class FriendlyIdGenerator < Rails::Generators::Base
21
21
  "%.3d" % (current_migration_number(dirname) + 1)
22
22
  end
23
23
  end
24
-
25
24
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: friendly_id
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0.beta7
4
+ version: 4.0.0.beta8
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -14,7 +14,7 @@ default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
17
- requirement: &70318438630040 !ruby/object:Gem::Requirement
17
+ requirement: &70172556099120 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ~>
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: '3.0'
23
23
  type: :development
24
24
  prerelease: false
25
- version_requirements: *70318438630040
25
+ version_requirements: *70172556099120
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: sqlite3
28
- requirement: &70318438629540 !ruby/object:Gem::Requirement
28
+ requirement: &70172556098620 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ~>
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: '1.3'
34
34
  type: :development
35
35
  prerelease: false
36
- version_requirements: *70318438629540
36
+ version_requirements: *70172556098620
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: minitest
39
- requirement: &70318438629080 !ruby/object:Gem::Requirement
39
+ requirement: &70172556098140 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ~>
@@ -44,10 +44,10 @@ dependencies:
44
44
  version: 2.4.0
45
45
  type: :development
46
46
  prerelease: false
47
- version_requirements: *70318438629080
47
+ version_requirements: *70172556098140
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: mocha
50
- requirement: &70318438628620 !ruby/object:Gem::Requirement
50
+ requirement: &70172556097640 !ruby/object:Gem::Requirement
51
51
  none: false
52
52
  requirements:
53
53
  - - ~>
@@ -55,10 +55,10 @@ dependencies:
55
55
  version: 0.9.12
56
56
  type: :development
57
57
  prerelease: false
58
- version_requirements: *70318438628620
58
+ version_requirements: *70172556097640
59
59
  - !ruby/object:Gem::Dependency
60
60
  name: ffaker
61
- requirement: &70318438628120 !ruby/object:Gem::Requirement
61
+ requirement: &70172556097180 !ruby/object:Gem::Requirement
62
62
  none: false
63
63
  requirements:
64
64
  - - ~>
@@ -66,10 +66,10 @@ dependencies:
66
66
  version: 1.8.0
67
67
  type: :development
68
68
  prerelease: false
69
- version_requirements: *70318438628120
69
+ version_requirements: *70172556097180
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: maruku
72
- requirement: &70318438627560 !ruby/object:Gem::Requirement
72
+ requirement: &70172556096720 !ruby/object:Gem::Requirement
73
73
  none: false
74
74
  requirements:
75
75
  - - ~>
@@ -77,10 +77,10 @@ dependencies:
77
77
  version: 0.6.0
78
78
  type: :development
79
79
  prerelease: false
80
- version_requirements: *70318438627560
80
+ version_requirements: *70172556096720
81
81
  - !ruby/object:Gem::Dependency
82
82
  name: yard
83
- requirement: &70318438627040 !ruby/object:Gem::Requirement
83
+ requirement: &70172556096260 !ruby/object:Gem::Requirement
84
84
  none: false
85
85
  requirements:
86
86
  - - ~>
@@ -88,7 +88,7 @@ dependencies:
88
88
  version: 0.7.2
89
89
  type: :development
90
90
  prerelease: false
91
- version_requirements: *70318438627040
91
+ version_requirements: *70172556096260
92
92
  description: ! 'FriendlyId is the "Swiss Army bulldozer" of slugging and permalink
93
93
  plugins for
94
94
 
@@ -120,32 +120,18 @@ files:
120
120
  - gemfiles/Gemfile.rails-3.1.rb
121
121
  - gemfiles/Gemfile.rails-3.1.rb.lock
122
122
  - lib/friendly_id.rb
123
- - lib/friendly_id/active_record.rb
124
- - lib/friendly_id/active_record_adapter/configuration.rb
125
- - lib/friendly_id/active_record_adapter/relation.rb
126
- - lib/friendly_id/active_record_adapter/simple_model.rb
127
- - lib/friendly_id/active_record_adapter/slug.rb
128
- - lib/friendly_id/active_record_adapter/slugged_model.rb
129
- - lib/friendly_id/active_record_adapter/tasks.rb
130
123
  - lib/friendly_id/base.rb
131
124
  - lib/friendly_id/configuration.rb
132
- - lib/friendly_id/datamapper.rb
133
125
  - lib/friendly_id/finder_methods.rb
134
126
  - lib/friendly_id/history.rb
135
127
  - lib/friendly_id/migration.rb
136
128
  - lib/friendly_id/model.rb
137
129
  - lib/friendly_id/object_utils.rb
138
- - lib/friendly_id/railtie.rb
139
130
  - lib/friendly_id/reserved.rb
140
131
  - lib/friendly_id/scoped.rb
141
- - lib/friendly_id/sequel.rb
142
132
  - lib/friendly_id/slug.rb
143
133
  - lib/friendly_id/slug_sequencer.rb
144
- - lib/friendly_id/slug_string.rb
145
134
  - lib/friendly_id/slugged.rb
146
- - lib/friendly_id/status.rb
147
- - lib/friendly_id/test.rb
148
- - lib/friendly_id/version.rb
149
135
  - lib/generators/friendly_id_generator.rb
150
136
  - test/base_test.rb
151
137
  - test/configuration_test.rb
@@ -1,56 +0,0 @@
1
- module FriendlyId
2
-
3
- module ActiveRecordAdapter
4
-
5
- include FriendlyId::Base
6
-
7
- def has_friendly_id(method, options = {})
8
-
9
- class_attribute :friendly_id_config
10
- self.friendly_id_config = Configuration.new(self, method, options)
11
-
12
- if friendly_id_config.use_slug?
13
- include SluggedModel
14
- else
15
- include SimpleModel
16
- end
17
- end
18
-
19
- private
20
-
21
- # Prevent the cached_slug column from being accidentally or maliciously
22
- # overwritten. Note that +attr_protected+ is used to protect the cached_slug
23
- # column, unless you have already invoked +attr_accessible+. So if you
24
- # wish to use +attr_accessible+, you must invoke it BEFORE you invoke
25
- # {#has_friendly_id} in your class.
26
- def protect_friendly_id_attributes
27
- # only protect the column if the class is not already using attr_accessible
28
- unless accessible_attributes.present?
29
- if friendly_id_config.custom_cache_column?
30
- attr_protected friendly_id_config.cache_column
31
- end
32
- attr_protected :cached_slug
33
- end
34
- end
35
-
36
- end
37
- end
38
-
39
- require "friendly_id/active_record_adapter/relation"
40
- require "friendly_id/active_record_adapter/configuration"
41
- require "friendly_id/active_record_adapter/simple_model"
42
- require "friendly_id/active_record_adapter/slugged_model"
43
- require "friendly_id/active_record_adapter/slug"
44
- require "friendly_id/active_record_adapter/tasks"
45
-
46
- module ActiveRecord
47
- class Base
48
- extend FriendlyId::ActiveRecordAdapter
49
- end
50
-
51
- class Relation
52
- alias find_one_without_friendly find_one
53
- alias find_some_without_friendly find_some
54
- include FriendlyId::ActiveRecordAdapter::Relation
55
- end
56
- end
@@ -1,68 +0,0 @@
1
- module FriendlyId
2
-
3
- module ActiveRecordAdapter
4
-
5
- # Extends FriendlyId::Configuration with some implementation details and
6
- # features specific to ActiveRecord.
7
- class Configuration < FriendlyId::Configuration
8
-
9
- # The column used to cache the friendly_id string. If no column is specified,
10
- # FriendlyId will look for a column named +cached_slug+ and use it automatically
11
- # if it exists. If for some reason you have a column named +cached_slug+
12
- # but don't want FriendlyId to modify it, pass the option
13
- # +:cache_column => false+ to {FriendlyId::ActiveRecordAdapter#has_friendly_id has_friendly_id}.
14
- attr_accessor :cache_column
15
-
16
- # An array of classes for which the configured class serves as a
17
- # FriendlyId scope.
18
- attr_reader :child_scopes
19
-
20
- attr_reader :custom_cache_column
21
-
22
- def cache_column
23
- return @cache_column if defined?(@cache_column)
24
- @cache_column = autodiscover_cache_column
25
- end
26
-
27
- def cache_column?
28
- !! cache_column
29
- end
30
-
31
- def cache_column=(cache_column)
32
- @cache_column = cache_column
33
- @custom_cache_column = cache_column
34
- end
35
-
36
- def child_scopes
37
- @child_scopes ||= associated_friendly_classes.select do |klass|
38
- klass.friendly_id_config.scopes_over?(configured_class)
39
- end
40
- end
41
-
42
- def custom_cache_column?
43
- !! custom_cache_column
44
- end
45
-
46
- def scope_for(record)
47
- return nil unless scope?
48
- record.send(scope).nil? ? nil : record.send(scope).to_param
49
- end
50
-
51
- def scopes_over?(klass)
52
- scope? && scope == klass.to_s.underscore.to_sym
53
- end
54
-
55
- private
56
-
57
- def autodiscover_cache_column
58
- :cached_slug if configured_class.columns.any? { |column| column.name == 'cached_slug' }
59
- end
60
-
61
- def associated_friendly_classes
62
- configured_class.reflect_on_all_associations.compact.select { |assoc|
63
- !assoc.options[:polymorphic] && assoc.klass.respond_to?(:friendly_id_config)
64
- }.map(&:klass)
65
- end
66
- end
67
- end
68
- end
@@ -1,173 +0,0 @@
1
- module FriendlyId
2
- module ActiveRecordAdapter
3
- module Relation
4
-
5
- class Find
6
- extend Forwardable
7
-
8
- attr :relation
9
- attr :ids
10
- alias id ids
11
-
12
- def_delegators :relation, :arel, :arel_table, :klass, :limit_value, :offset_value, :where
13
- def_delegators :klass, :connection, :friendly_id_config
14
- alias fc friendly_id_config
15
-
16
- def initialize(relation, ids)
17
- @relation = relation
18
- @ids = ids
19
- end
20
-
21
- def find_one
22
- if fc.cache_column?
23
- find_one_with_cached_slug
24
- elsif fc.use_slugs?
25
- find_one_with_slug
26
- else
27
- find_one_without_slug
28
- end
29
- end
30
-
31
- def find_some
32
- ids = @ids.compact.uniq.map {|id| id.respond_to?(:friendly_id_config) ? id.id : id}
33
- friendly_ids, unfriendly_ids = ids.partition {|id| id.friendly_id?}
34
- return if friendly_ids.empty?
35
- records = friendly_records(friendly_ids, unfriendly_ids).each do |record|
36
- record.friendly_id_status.name = ids
37
- end
38
- validate_expected_size!(ids, records)
39
- end
40
-
41
- private
42
-
43
- def assign_status
44
- return unless @result
45
- name, seq = id.to_s.parse_friendly_id
46
- @result.friendly_id_status.name = name
47
- @result.friendly_id_status.sequence = seq if fc.use_slugs?
48
- @result
49
- end
50
-
51
- def find_one_without_slug
52
- @result = where(fc.column => id).first
53
- assign_status
54
- end
55
-
56
- def find_one_with_cached_slug
57
- @result = where(fc.cache_column => id).first
58
- assign_status or find_one_with_slug
59
- end
60
-
61
- def find_one_with_slug
62
- sluggable_ids = sluggable_ids_for([id])
63
-
64
- if sluggable_ids.size > 1 && fc.scope?
65
- return relation.where(primary_key_column.in(sluggable_ids)).first
66
- end
67
-
68
- sluggable_id = sluggable_ids.first
69
-
70
- if sluggable_id
71
- name, seq = id.to_s.parse_friendly_id
72
- record = relation.send(:find_one_without_friendly, sluggable_id)
73
- record.friendly_id_status.name = name
74
- record.friendly_id_status.sequence = seq
75
- record
76
- else
77
- relation.send(:find_one_without_friendly, id)
78
- end
79
- end
80
-
81
- def friendly_records(friendly_ids, unfriendly_ids)
82
- use_slugs_table = fc.use_slugs? && (!fc.cache_column?)
83
- return find_some_using_slug(friendly_ids, unfriendly_ids) if use_slugs_table
84
- column = fc.cache_column || fc.column
85
- friendly = arel_table[column].in(friendly_ids)
86
- unfriendly = primary_key_column.in unfriendly_ids
87
- if friendly_ids.present? && unfriendly_ids.present?
88
- where(friendly.or(unfriendly))
89
- else
90
- where(friendly)
91
- end
92
- end
93
-
94
- def find_some_using_slug(friendly_ids, unfriendly_ids)
95
- ids = [unfriendly_ids + sluggable_ids_for(friendly_ids)].flatten.uniq
96
- where(primary_key_column.in(ids))
97
- end
98
-
99
- def sluggable_ids_for(ids)
100
- return [] if ids.empty?
101
- fragment = "(slugs.sluggable_type = %s AND slugs.name = %s AND slugs.sequence = %d)"
102
- conditions = ids.inject(nil) do |clause, id|
103
- name, seq = id.parse_friendly_id
104
- string = fragment % [connection.quote(klass.base_class.name), connection.quote(name), seq]
105
- clause ? clause + " OR #{string}" : string
106
- end
107
- sql = "SELECT sluggable_id FROM slugs WHERE (%s)" % conditions
108
- connection.select_values sql
109
- end
110
-
111
- def validate_expected_size!(ids, result)
112
- expected_size =
113
- if limit_value && ids.size > limit_value
114
- limit_value
115
- else
116
- ids.size
117
- end
118
-
119
- # 11 ids with limit 3, offset 9 should give 2 results.
120
- if offset_value && (ids.size - offset_value < expected_size)
121
- expected_size = ids.size - offset_value
122
- end
123
-
124
- if result.size == expected_size
125
- result
126
- else
127
- conditions = arel.send(:where_clauses).join(', ')
128
- conditions = " [WHERE #{conditions}]" if conditions.present?
129
- error = "Couldn't find all #{klass.name.pluralize} with IDs "
130
- error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
131
- raise ActiveRecord::RecordNotFound, error
132
- end
133
- end
134
-
135
- def primary_key_column
136
- if relation.primary_key.is_a?(String)
137
- arel_table[relation.primary_key]
138
- else
139
- arel_table[relation.primary_key.name]
140
- end
141
- end
142
- end
143
-
144
- def apply_finder_options(options)
145
- if options[:scope]
146
- raise "The :scope finder option has been removed from FriendlyId 3.2.0 " +
147
- "https://github.com/norman/friendly_id/issues#issue/88"
148
- end
149
- super
150
- end
151
-
152
- protected
153
-
154
- def find_one(id)
155
- begin
156
- return super if !klass.uses_friendly_id? or id.unfriendly_id?
157
- find = Find.new(self, id)
158
- find.find_one or super
159
- end
160
- end
161
-
162
- def find_some(ids)
163
- return super unless klass.uses_friendly_id?
164
- Find.new(self, ids).find_some or begin
165
- # A change in Arel 2.0.x causes find_some to fail with arrays of instances; not sure why.
166
- # This is an emergency, temporary fix.
167
- ids = ids.map {|id| (id.respond_to?(:friendly_id_config) ? id.id : id)}
168
- super
169
- end
170
- end
171
- end
172
- end
173
- end
@@ -1,62 +0,0 @@
1
- module FriendlyId
2
- module ActiveRecordAdapter
3
-
4
- module SimpleModel
5
-
6
- def self.included(base)
7
- base.class_eval do
8
- column = friendly_id_config.column
9
- validate :validate_friendly_id, :unless => :skip_friendly_id_validations
10
- validates_presence_of column, :unless => :skip_friendly_id_validations
11
- validates_length_of column, :maximum => friendly_id_config.max_length, :unless => :skip_friendly_id_validations
12
- after_update :update_scopes
13
- end
14
- end
15
-
16
- # Get the {FriendlyId::Status} after the find has been performed.
17
- def friendly_id_status
18
- @friendly_id_status ||= Status.new :record => self
19
- end
20
-
21
- # Returns the friendly_id.
22
- def friendly_id
23
- send friendly_id_config.column
24
- end
25
- alias best_id friendly_id
26
-
27
- # Returns the friendly id, or if none is available, the numeric id.
28
- def to_param
29
- (friendly_id || id).to_s
30
- end
31
-
32
- private
33
-
34
- # The old and new values for the friendly_id column.
35
- def friendly_id_changes
36
- changes[friendly_id_config.column.to_s]
37
- end
38
-
39
- # Update the slugs for any model that is using this model as its
40
- # FriendlyId scope.
41
- def update_scopes
42
- if changes = friendly_id_changes
43
- friendly_id_config.child_scopes.each do |klass|
44
- Slug.update_all "scope = '#{changes[1]}'", ["sluggable_type = ? AND scope = ?", klass.to_s, changes[0]]
45
- end
46
- end
47
- end
48
-
49
- def skip_friendly_id_validations
50
- friendly_id.nil? && friendly_id_config.allow_nil?
51
- end
52
-
53
- def validate_friendly_id
54
- if result = friendly_id_config.reserved_error_message(friendly_id)
55
- self.errors.add(*result)
56
- return false
57
- end
58
- end
59
-
60
- end
61
- end
62
- end
@@ -1,84 +0,0 @@
1
- # A Slug is a unique, human-friendly identifier for an ActiveRecord.
2
- class Slug < ::ActiveRecord::Base
3
- attr_writer :sluggable
4
- attr_accessible :name, :scope, :sluggable, :sequence
5
- table_name = "slugs"
6
- before_save :enable_name_reversion, :set_sequence
7
- validate :validate_name
8
- scope :similar_to, lambda {|slug| {:conditions => {
9
- :name => slug.name,
10
- :scope => slug.scope,
11
- :sluggable_type => slug.sluggable_type
12
- },
13
- :order => "sequence ASC"
14
- }
15
- }
16
-
17
- def sluggable
18
- sluggable_id && !@sluggable and begin
19
- klass = sluggable_type.constantize
20
- klass.send(:with_exclusive_scope) do
21
- @sluggable = klass.find(sluggable_id)
22
- end
23
- end
24
- @sluggable
25
- end
26
-
27
- # Whether this slug is the most recent of its owner's slugs.
28
- def current?
29
- sluggable.slug == self
30
- end
31
-
32
- def outdated?
33
- !current?
34
- end
35
-
36
- def to_friendly_id
37
- sequence > 1 ? friendly_id_with_sequence : name
38
- end
39
-
40
- # Raise a FriendlyId::SlugGenerationError if the slug name is blank.
41
- def validate_name
42
- if name.blank?
43
- raise FriendlyId::BlankError.new("slug.name can not be blank.")
44
- end
45
- end
46
-
47
- def save(*args)
48
- persisted? && !scope_changed? ? true : super
49
- end
50
-
51
- def save!(*args)
52
- persisted? && !scope_changed? ? true : super
53
- end
54
-
55
- private
56
-
57
- # If we're renaming back to a previously used friendly_id, delete the
58
- # slug so that we can recycle the name without having to use a sequence.
59
- def enable_name_reversion
60
- sluggable.slugs.find_all_by_name_and_scope(name, scope).each { |slug| slug.destroy }
61
- end
62
-
63
- def friendly_id_with_sequence
64
- "#{name}#{separator}#{sequence}"
65
- end
66
-
67
- def similar_to_other_slugs?
68
- !similar_slugs.empty?
69
- end
70
-
71
- def similar_slugs
72
- self.class.similar_to(self)
73
- end
74
-
75
- def separator
76
- sluggable.friendly_id_config.sequence_separator
77
- end
78
-
79
- def set_sequence
80
- return unless new_record?
81
- self.sequence = similar_slugs.last.sequence.succ if similar_to_other_slugs?
82
- end
83
-
84
- end
@@ -1,110 +0,0 @@
1
- module FriendlyId
2
- module ActiveRecordAdapter
3
- module SluggedModel
4
-
5
- def self.included(base)
6
- base.class_eval do
7
- has_many :slugs, :order => 'id DESC', :as => :sluggable, :dependent => :destroy
8
- has_one :slug, :order => 'id DESC', :as => :sluggable, :dependent => :nullify
9
- before_save :build_a_slug
10
- after_save :set_slug_cache
11
- after_update :update_scope
12
- after_update :update_dependent_scopes
13
- protect_friendly_id_attributes
14
- end
15
- end
16
-
17
- include FriendlyId::Slugged::Model
18
-
19
- def find_slug(name, sequence)
20
- slugs.find_by_name_and_sequence(name, sequence)
21
- end
22
-
23
- # Returns the friendly id, or if none is available, the numeric id. Note that this
24
- # method will use the cached_slug value if present, unlike {#friendly_id}.
25
- def to_param
26
- friendly_id_config.cache_column ? to_param_from_cache : to_param_from_slug
27
- end
28
-
29
- private
30
-
31
- def scope_changed?
32
- friendly_id_config.scope? && send(friendly_id_config.scope).to_param != slug.scope
33
- end
34
-
35
- # Respond with the cached value if available.
36
- def to_param_from_cache
37
- read_attribute(friendly_id_config.cache_column) || id.to_s
38
- end
39
-
40
- # Respond with the slugged value if available.
41
- def to_param_from_slug
42
- slug? ? slug.to_friendly_id : id.to_s
43
- end
44
-
45
- # Build the new slug using the generated friendly id.
46
- def build_a_slug
47
- return unless new_slug_needed?
48
- self.slug = slugs.build :name => slug_text.to_s, :scope => friendly_id_config.scope_for(self),
49
- :sluggable => self
50
- @new_friendly_id = self.slug.to_friendly_id
51
- end
52
-
53
- # Reset the cached friendly_id?
54
- def new_cache_needed?
55
- uses_slug_cache? && slug? && send(friendly_id_config.cache_column) != slug.to_friendly_id
56
- end
57
-
58
- # Reset the cached friendly_id.
59
- def set_slug_cache
60
- if new_cache_needed?
61
- begin
62
- send "#{friendly_id_config.cache_column}=", slug.to_friendly_id
63
- update_without_callbacks
64
- rescue ActiveRecord::StaleObjectError
65
- reload
66
- retry
67
- end
68
- end
69
- end
70
-
71
- def update_scope
72
- return unless slug && scope_changed?
73
- self.class.transaction do
74
- slug.scope = send(friendly_id_config.scope).to_param
75
- similar = Slug.similar_to(slug)
76
- if !similar.empty?
77
- slug.sequence = similar.first.sequence.succ
78
- end
79
- slug.save!
80
- end
81
- end
82
-
83
- # Update the slugs for any model that is using this model as its
84
- # FriendlyId scope.
85
- def update_dependent_scopes
86
- return unless friendly_id_config.class.scopes_used?
87
- if slugs(true).size > 1 && @new_friendly_id
88
- friendly_id_config.child_scopes.each do |klass|
89
- Slug.update_all "scope = '#{@new_friendly_id}'", ["sluggable_type = ? AND scope = ?",
90
- klass.to_s, slugs.second.to_friendly_id]
91
- end
92
- end
93
- end
94
-
95
- # Does the model use slug caching?
96
- def uses_slug_cache?
97
- friendly_id_config.cache_column?
98
- end
99
-
100
- # This method was removed in ActiveRecord 3.0.
101
- if !ActiveRecord::Base.private_method_defined? :update_without_callbacks
102
- def update_without_callbacks
103
- attributes_with_values = arel_attributes_values(false, false, attribute_names)
104
- return false if attributes_with_values.empty?
105
- self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
106
- end
107
- end
108
- end
109
- end
110
- end
@@ -1,72 +0,0 @@
1
- module FriendlyId
2
- class TaskRunner
3
-
4
- extend Forwardable
5
-
6
- attr_accessor :days
7
- attr_accessor :klass
8
- attr_accessor :task_options
9
-
10
- def_delegators :klass, :find, :friendly_id_config, :update_all
11
-
12
- OLD_SLUG_DAYS = 45
13
-
14
- def initialize(&block)
15
- self.klass = ENV["MODEL"]
16
- self.days = ENV["DAYS"]
17
- end
18
-
19
- def days=(days)
20
- @days ||= days.blank? ? OLD_SLUG_DAYS : days.to_i
21
- end
22
-
23
- def klass=(klass)
24
- @klass ||= klass.to_s.classify.constantize unless klass.blank?
25
- end
26
-
27
- def make_slugs
28
- validate_uses_slugs
29
- options = {
30
- :include => :slug,
31
- :limit => (ENV["LIMIT"] || 100).to_i,
32
- :offset => 0,
33
- :order => ENV["ORDER"] || "#{klass.table_name}.#{klass.primary_key} ASC",
34
- }.merge(task_options || {})
35
-
36
- while records = find(:all, options) do
37
- break if records.size == 0
38
- records.each do |record|
39
- record.save(:validate => false) unless record.slug?
40
- yield(record) if block_given?
41
- end
42
- options[:offset] += options[:limit]
43
- end
44
- end
45
-
46
- def delete_slugs
47
- validate_uses_slugs
48
- Slug.destroy_all(["sluggable_type = ?", klass.to_s])
49
- if column = friendly_id_config.cache_column
50
- update_all("#{column} = NULL")
51
- end
52
- end
53
-
54
- def delete_old_slugs
55
- conditions = ["created_at < ?", DateTime.now - days]
56
- if klass
57
- conditions[0] << " AND sluggable_type = ?"
58
- conditions << klass.to_s
59
- end
60
- Slug.all(:conditions => conditions).select(&:outdated?).map(&:destroy)
61
- end
62
-
63
- def validate_uses_slugs
64
- (raise "You need to pass a MODEL=<model name> argument to rake") if klass.blank?
65
- unless friendly_id_config.use_slug?
66
- raise "Class '%s' doesn't use slugs" % klass.to_s
67
- end
68
- rescue NoMethodError
69
- raise "Class '%s' doesn't use FriendlyId" % klass.to_s
70
- end
71
- end
72
- end
@@ -1,5 +0,0 @@
1
- begin
2
- require 'friendly_id_datamapper'
3
- rescue LoadError
4
- raise "To use FriendlyId's DataMapper adapter, please `gem install friendly_id_datamapper`"
5
- end
@@ -1,22 +0,0 @@
1
- module FriendlyId
2
- class Railtie < Rails::Railtie
3
-
4
- initializer "friendly_id.configure_rails_initialization" do |app|
5
- # Experimental Sequel support. See: http://github.com/norman/friendly_id_sequel
6
- if app.config.generators.rails[:orm] == :sequel
7
- require "friendly_id/sequel"
8
- # Experimental DataMapper support. See: http://github.com/myabc/friendly_id_datamapper
9
- elsif app.config.generators.rails[:orm] == :data_mapper
10
- require 'friendly_id/datamapper'
11
- else
12
- # AR is the default.
13
- require "friendly_id/active_record"
14
- end
15
- end
16
-
17
- rake_tasks do
18
- load "tasks/friendly_id.rake"
19
- end
20
-
21
- end
22
- end
@@ -1,5 +0,0 @@
1
- begin
2
- require "friendly_id_sequel"
3
- rescue LoadError
4
- raise "To use FriendlyId's Sequel adapter, please `gem install friendly_id_sequel`"
5
- end
@@ -1,25 +0,0 @@
1
- # encoding: utf-8
2
- module FriendlyId
3
-
4
- class SlugString < Babosa::Identifier
5
- # Normalize the string for a given {FriendlyId::Configuration}.
6
- # @param config [FriendlyId::Configuration]
7
- # @return String
8
- def normalize_for!(config)
9
- normalize!(config.babosa_options)
10
- end
11
-
12
- # Validate that the slug string is not blank or reserved, and truncate
13
- # it to the max length if necessary.
14
- # @param config [FriendlyId::Configuration]
15
- # @return String
16
- # @raise FriendlyId::BlankError
17
- # @raise FriendlyId::ReservedError
18
- def validate_for!(config)
19
- truncate_bytes!(config.max_length)
20
- raise FriendlyId::BlankError if empty?
21
- raise FriendlyId::ReservedError if config.reserved?(self)
22
- self
23
- end
24
- end
25
- end
@@ -1,35 +0,0 @@
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
@@ -1,364 +0,0 @@
1
- # encoding: utf-8
2
- Module.send :include, Module.new {
3
- def test(name, &block)
4
- define_method("test_#{name.gsub(/[^a-z0-9]/i, "_")}".to_sym, &block)
5
- end
6
- alias :should :test
7
- }
8
-
9
- module FriendlyId
10
- module Test
11
-
12
- # Tests for any model that implements FriendlyId. Any test that tests model
13
- # features should include this module.
14
- module Generic
15
-
16
- def setup
17
- klass.send delete_all_method
18
- end
19
-
20
- def teardown
21
- klass.send delete_all_method
22
- end
23
-
24
- def instance
25
- raise NotImplementedError
26
- end
27
-
28
- def klass
29
- raise NotImplementedError
30
- end
31
-
32
- def other_class
33
- raise NotImplementedError
34
- end
35
-
36
- def find_method
37
- raise NotImplementedError
38
- end
39
-
40
- def create_method
41
- raise NotImplementedError
42
- end
43
-
44
- def update_method
45
- raise NotImplementedError
46
- end
47
-
48
- def validation_exceptions
49
- return RuntimeError
50
- end
51
-
52
- def assert_validation_error
53
- if validation_exceptions
54
- assert_raise(*[validation_exceptions].flatten) do
55
- yield
56
- end
57
- else # DataMapper does not raise Validation Errors
58
- i = yield
59
- if i.kind_of?(TrueClass) || i.kind_of?(FalseClass)
60
- assert !i
61
- else
62
- instance = i
63
- assert !instance.errors.empty?
64
- end
65
- end
66
- end
67
-
68
- test "models should have a friendly id config" do
69
- assert_not_nil klass.friendly_id_config
70
- end
71
-
72
- test "instances should have a friendly id by default" do
73
- assert_not_nil instance.friendly_id
74
- end
75
-
76
- test "instances should have a friendly id status" do
77
- assert_not_nil instance.friendly_id_status
78
- end
79
-
80
- test "instances should be findable by their friendly id" do
81
- assert_equal instance, klass.send(find_method, instance.friendly_id)
82
- end
83
-
84
- test "instances should be findable by their numeric id as an integer" do
85
- assert_equal instance, klass.send(find_method, instance.id.to_i)
86
- end
87
-
88
- test "instances should be findable by their numeric id as a string" do
89
- assert_equal instance, klass.send(find_method, instance.id.to_s)
90
- end
91
-
92
- test "instances should be findable by a numeric friendly_id" do
93
- instance = klass.send(create_method, :name => "206")
94
- assert_equal instance, klass.send(find_method, "206")
95
- end
96
-
97
- test "creation should raise an error if the friendly_id text is reserved" do
98
- assert_validation_error do
99
- klass.send(create_method, :name => "new")
100
- end
101
- end
102
-
103
- test "creation should raise an error if the friendly_id text is an empty string" do
104
- assert_validation_error do
105
- klass.send(create_method, :name => "")
106
- end
107
- end
108
-
109
- test "creation should raise an error if the friendly_id text is a blank string" do
110
- assert_validation_error do
111
- klass.send(create_method, :name => " ")
112
- end
113
- end
114
-
115
- test "creation should raise an error if the friendly_id text is nil and allow_nil is false" do
116
- assert_validation_error do
117
- klass.send(create_method, :name => nil)
118
- end
119
- end
120
-
121
- test "creation should succeed if the friendly_id text is nil and allow_nil is true" do
122
- klass.friendly_id_config.stubs(:allow_nil?).returns(true)
123
- assert klass.send(create_method, :name => nil)
124
- end
125
-
126
- test "creation should succeed if the friendly_id text is empty and allow_nil is true" do
127
- klass.friendly_id_config.stubs(:allow_nil?).returns(true)
128
- begin
129
- assert klass.send(create_method, :name => "")
130
- rescue ActiveRecord::RecordInvalid => e
131
- raise unless e.message =~ /Name can't be blank/ # Test not applicable in this case
132
- end
133
- end
134
-
135
- test "creation should succeed if the friendly_id text converts to empty and allow_nil is true" do
136
- klass.friendly_id_config.stubs(:allow_nil?).returns(true)
137
- assert klass.send(create_method, :name => "*")
138
- end
139
-
140
- test "should allow the same friendly_id across models" do
141
- other_instance = other_class.send(create_method, :name => instance.name)
142
- assert_equal other_instance.friendly_id, instance.friendly_id
143
- end
144
-
145
- test "reserved words can be specified as a regular expression" do
146
- klass.friendly_id_config.stubs(:reserved_words).returns(/jo/)
147
- assert_validation_error do
148
- klass.send(create_method, :name => "joe")
149
- end
150
- end
151
-
152
- test "should not raise reserved error unless regexp matches" do
153
- klass.friendly_id_config.stubs(:reserved_words).returns(/ddsadad/)
154
- assert_nothing_raised do
155
- klass.send(create_method, :name => "joe")
156
- end
157
- end
158
-
159
- end
160
-
161
- module Simple
162
-
163
- test "should allow friendly_id to be nillable if allow_nil is true" do
164
- klass.friendly_id_config.stubs(:allow_nil?).returns(true)
165
- instance = klass.send(create_method, :name => "hello")
166
- assert instance.friendly_id
167
- instance.name = nil
168
- assert instance.send(save_method)
169
- end
170
-
171
- end
172
-
173
- # Tests for any model that implements slugs.
174
- module Slugged
175
-
176
- test "should have a slug" do
177
- assert_not_nil instance.slug
178
- end
179
-
180
- test "should not make a new slug unless the friendly_id method value has changed" do
181
- instance.note = instance.note.to_s << " updated"
182
- instance.send save_method
183
- assert_equal 1, instance.slugs.size
184
- end
185
-
186
- test "should make a new slug if the friendly_id method value has changed" do
187
- instance.name = "Changed title"
188
- instance.send save_method
189
- slugs = if instance.slugs.respond_to?(:reload)
190
- instance.slugs.reload
191
- else
192
- instance.slugs(true)
193
- end
194
- assert_equal 2, slugs.size
195
- end
196
-
197
- test "should be able to reuse an old friendly_id without incrementing the sequence" do
198
- old_title = instance.name
199
- old_friendly_id = instance.friendly_id
200
- instance.name = "A changed title"
201
- instance.send save_method
202
- instance.name = old_title
203
- instance.send save_method
204
- assert_equal old_friendly_id, instance.friendly_id
205
- end
206
-
207
- test "should increment the slug sequence for duplicate friendly ids" do
208
- instance2 = klass.send(create_method, :name => instance.name)
209
- assert_match(/2\z/, instance2.friendly_id)
210
- end
211
-
212
- test "should find instance with a sequenced friendly_id" do
213
- instance2 = klass.send(create_method, :name => instance.name)
214
- assert_equal instance2, klass.send(find_method, instance2.friendly_id)
215
- end
216
-
217
- test "should indicate correct status when found with a sequence" do
218
- instance2 = klass.send(create_method, :name => instance.name)
219
- instance2 = klass.send(find_method, instance2.friendly_id)
220
- assert instance2.friendly_id_status.best?
221
- end
222
-
223
- test "should indicate correct status when found by a numeric friendly_id" do
224
- instance = klass.send(create_method, :name => "100")
225
- instance2 = klass.send(find_method, "100")
226
- assert instance2.friendly_id_status.best?, "status expected to be best but isn't."
227
- assert instance2.friendly_id_status.current?, "status expected to be current but isn't."
228
- end
229
-
230
- test "should remain findable by previous slugs" do
231
- old_friendly_id = instance.friendly_id
232
- instance.name = "#{old_friendly_id} updated"
233
- instance.send(save_method)
234
- assert_not_equal old_friendly_id, instance.friendly_id
235
- assert_equal instance, klass.send(find_method, old_friendly_id)
236
- end
237
-
238
- test "should not create a slug when allow_nil is true and friendy_id text is blank" do
239
- klass.friendly_id_config.stubs(:allow_nil?).returns(true)
240
- instance = klass.send(create_method, :name => nil)
241
- assert_nil instance.slug
242
- end
243
-
244
- test "should not allow friendly_id to be nillable even if allow_nil is true" do
245
- klass.friendly_id_config.stubs(:allow_nil?).returns(true)
246
- instance = klass.send(create_method, :name => "hello")
247
- assert instance.friendly_id
248
- instance.name = nil
249
- assert_validation_error do
250
- instance.send(save_method)
251
- end
252
- end
253
-
254
- test "should approximate ascii if configured" do
255
- klass.friendly_id_config.stubs(:approximate_ascii?).returns(true)
256
- instance = klass.send(create_method, :name => "Cañón")
257
- assert_equal "canon", instance.friendly_id
258
- end
259
-
260
- test "should approximate ascii with options if configured" do
261
- klass.friendly_id_config.stubs(:approximate_ascii?).returns(true)
262
- klass.friendly_id_config.stubs(:ascii_approximation_options).returns(:spanish)
263
- instance = klass.send(create_method, :name => "Cañón")
264
- assert_equal "canion", instance.friendly_id
265
- end
266
- end
267
-
268
- # Tests for FriendlyId::Status.
269
- module Status
270
-
271
- test "should default to not friendly" do
272
- assert !status.friendly?
273
- end
274
-
275
- test "should default to numeric" do
276
- assert status.numeric?
277
- end
278
-
279
- end
280
-
281
- # Tests for FriendlyId::Status for a model that uses slugs.
282
- module SluggedStatus
283
-
284
- test "should be friendly if slug is set" do
285
- status.slug = Slug.new
286
- assert status.friendly?
287
- end
288
-
289
- test "should be friendly if name is set" do
290
- status.name = "name"
291
- assert status.friendly?
292
- end
293
-
294
- test "should be current if current slug is set" do
295
- status.slug = instance.slug
296
- assert status.current?
297
- end
298
-
299
- test "should not be current if non-current slug is set" do
300
- status.slug = Slug.new(:sluggable => instance)
301
- assert !status.current?
302
- end
303
-
304
- test "should be best if it is current" do
305
- status.slug = instance.slug
306
- assert status.best?
307
- end
308
-
309
- test "should be best if it is numeric, but record has no slug" do
310
- instance.slugs = []
311
- instance.slug = nil
312
- assert status.best?
313
- end
314
-
315
- [:record, :name].each do |symbol|
316
- test "should have #{symbol} after find using friendly_id" do
317
- instance2 = klass.send(find_method, instance.friendly_id)
318
- assert_not_nil instance2.friendly_id_status.send(symbol)
319
- end
320
- end
321
-
322
- def status
323
- @status ||= instance.friendly_id_status
324
- end
325
-
326
- def klass
327
- raise NotImplementedError
328
- end
329
-
330
- def instance
331
- raise NotImplementedError
332
- end
333
-
334
- end
335
-
336
- # Tests for models to ensure that they properly implement using the
337
- # +normalize_friendly_id+ method to allow developers to hook into the
338
- # slug string generation.
339
- module CustomNormalizer
340
-
341
- test "should invoke the custom normalizer" do
342
- assert_equal "JOE SCHMOE", klass.send(create_method, :name => "Joe Schmoe").friendly_id
343
- end
344
-
345
- test "should respect the max_length option" do
346
- klass.friendly_id_config.stubs(:max_length).returns(3)
347
- assert_equal "JOE", klass.send(create_method, :name => "Joe Schmoe").friendly_id
348
- end
349
-
350
- test "should raise an error if the friendly_id text is reserved" do
351
- klass.friendly_id_config.stubs(:reserved_words).returns(["JOE"])
352
- if validation_exceptions
353
- assert_raise(*[validation_exceptions].flatten) do
354
- klass.send(create_method, :name => "Joe")
355
- end
356
- else # DataMapper does not raise Validation Errors
357
- instance = klass.send(create_method, :name => "Joe")
358
- assert !instance.errors.empty?
359
- end
360
- end
361
-
362
- end
363
- end
364
- end
@@ -1,9 +0,0 @@
1
- module FriendlyId
2
- module Version
3
- MAJOR = 3
4
- MINOR = 3
5
- TINY = 0
6
- BUILD = 'rc2'
7
- STRING = [MAJOR, MINOR, TINY, BUILD].compact.join('.')
8
- end
9
- end