isa-friendly_id_datamapper 3.2.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog.md +8 -0
- data/MIT-LICENSE +19 -0
- data/README.md +63 -0
- data/Rakefile +29 -0
- data/lib/friendly_id/datamapper_adapter/configuration.rb +67 -0
- data/lib/friendly_id/datamapper_adapter/simple_model.rb +83 -0
- data/lib/friendly_id/datamapper_adapter/slug.rb +72 -0
- data/lib/friendly_id/datamapper_adapter/slugged_model.rb +186 -0
- data/lib/friendly_id/datamapper_adapter/tasks.rb +69 -0
- data/lib/friendly_id/datamapper_adapter/version.rb +11 -0
- data/lib/friendly_id_datamapper.rb +34 -0
- data/test/basic_slugged_model_test.rb +16 -0
- data/test/cached_slug_test.rb +74 -0
- data/test/core.rb +72 -0
- data/test/custom_normalizer_test.rb +20 -0
- data/test/custom_table_name_test.rb +22 -0
- data/test/scoped_model_test.rb +120 -0
- data/test/simple_test.rb +76 -0
- data/test/slug_test.rb +35 -0
- data/test/slugged.rb +24 -0
- data/test/slugged_status_test.rb +27 -0
- data/test/sti_test.rb +22 -0
- data/test/support/models.rb +185 -0
- data/test/tasks_test.rb +84 -0
- data/test/test_helper.rb +37 -0
- metadata +158 -0
data/ChangeLog.md
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2010 Norman Clarke, Alex Coles
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# FriendlyId DataMapper Adapter
|
2
|
+
|
3
|
+
This is an pre-release (beta) adapter for
|
4
|
+
[FriendlyId](http://norman.github.com/friendly_id) using DataMapper.
|
5
|
+
|
6
|
+
## FriendlyId Features
|
7
|
+
|
8
|
+
It currently supports all of FriendlyId's features except:
|
9
|
+
|
10
|
+
* Rails Generator
|
11
|
+
* Support for multiple finders
|
12
|
+
|
13
|
+
Currently, only finds using `get` is supported.
|
14
|
+
|
15
|
+
@post = Post.get("this-is-a-title")
|
16
|
+
@post.friendly_id # this-is-a-title
|
17
|
+
|
18
|
+
## Compatibility
|
19
|
+
|
20
|
+
The FriendlyId DataMapper Adapter keeps in lock-step with major and
|
21
|
+
minor versions of the FriendlyId gem, i.e.
|
22
|
+
`friendly_id_datamapper 3.1.x` is compatible with `friendly_id 3.1.x series`.
|
23
|
+
Patch and build versions are not kept in lock-step.
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
gem install friendly_id friendly_id_datamapper
|
28
|
+
|
29
|
+
require "friendly_id"
|
30
|
+
require "friendly_id/datamapper"
|
31
|
+
|
32
|
+
class Post
|
33
|
+
include DataMapper::Resource
|
34
|
+
|
35
|
+
property :id, Serial
|
36
|
+
property :title, String
|
37
|
+
|
38
|
+
has_friendly_id :title, :use_slug => true
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
For more information on the available features, please see the
|
43
|
+
[FriendlyId Guide](http://norman.github.com/friendly_id/file.Guide.html).
|
44
|
+
|
45
|
+
## Bugs
|
46
|
+
|
47
|
+
Please report them on the [Github issue tracker](http://github.com/myabc/friendly_id_datamapper/issues)
|
48
|
+
for this project.
|
49
|
+
|
50
|
+
If you have a bug to report, please include the following information:
|
51
|
+
|
52
|
+
* **Version information for FriendlyId, friendly_id_datamapper, Rails and Ruby.**
|
53
|
+
* Stack trace and error message.
|
54
|
+
* Any snippets of relevant model, view or controller code that shows how your
|
55
|
+
are using FriendlyId.
|
56
|
+
|
57
|
+
If you are able to, it helps even more if you can fork FriendlyId on Github,
|
58
|
+
and add a test that reproduces the error you are experiencing.
|
59
|
+
|
60
|
+
## Credits
|
61
|
+
|
62
|
+
Copyright (c) 2010, released under the MIT license.
|
63
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require "rake"
|
2
|
+
require "rake/testtask"
|
3
|
+
require "rake/gempackagetask"
|
4
|
+
require "rake/clean"
|
5
|
+
|
6
|
+
CLEAN << "pkg" << "doc" << "coverage" << ".yardoc"
|
7
|
+
|
8
|
+
Rake::GemPackageTask.new(eval(File.read("friendly_id_datamapper.gemspec"))) { |pkg| }
|
9
|
+
Rake::TestTask.new(:test) { |t| t.pattern = "test/*_test.rb" }
|
10
|
+
|
11
|
+
task :default => :test
|
12
|
+
|
13
|
+
begin
|
14
|
+
require "yard"
|
15
|
+
YARD::Rake::YardocTask.new do |t|
|
16
|
+
t.options = ["--output-dir=docs"]
|
17
|
+
end
|
18
|
+
rescue LoadError
|
19
|
+
end
|
20
|
+
|
21
|
+
begin
|
22
|
+
require "rcov/rcovtask"
|
23
|
+
Rcov::RcovTask.new do |r|
|
24
|
+
r.test_files = FileList["test/**/*_test.rb"]
|
25
|
+
r.verbose = true
|
26
|
+
r.rcov_opts << "--exclude gems/*"
|
27
|
+
end
|
28
|
+
rescue LoadError
|
29
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module FriendlyId
|
2
|
+
module DataMapperAdapter
|
3
|
+
|
4
|
+
# Extends FriendlyId::Configuration with some implementation details and
|
5
|
+
# features specific to DataMapper.
|
6
|
+
class Configuration < FriendlyId::Configuration
|
7
|
+
|
8
|
+
# The column used to cache the friendly_id string. If no column is specified,
|
9
|
+
# FriendlyId will look for a column named +cached_slug+ and use it automatically
|
10
|
+
# if it exists. If for some reason you have a column named +cached_slug+
|
11
|
+
# but don't want FriendlyId to modify it, pass the option
|
12
|
+
# +:cache_column => false+ to {FriendlyId::DataMapperAdapter#has_friendly_id has_friendly_id}.
|
13
|
+
attr_accessor :cache_column
|
14
|
+
|
15
|
+
# An array of classes for which the configured class serves as a
|
16
|
+
# FriendlyId scope.
|
17
|
+
attr_reader :child_scopes
|
18
|
+
|
19
|
+
attr_reader :custom_cache_column
|
20
|
+
|
21
|
+
def cache_column
|
22
|
+
return @cache_column if defined?(@cache_column)
|
23
|
+
@cache_column = autodiscover_cache_column
|
24
|
+
end
|
25
|
+
|
26
|
+
def cache_column?
|
27
|
+
!! cache_column
|
28
|
+
end
|
29
|
+
|
30
|
+
def cache_column=(cache_column)
|
31
|
+
@cache_column = cache_column
|
32
|
+
@custom_cache_column = cache_column
|
33
|
+
end
|
34
|
+
|
35
|
+
def child_scopes
|
36
|
+
@child_scopes ||= associated_friendly_classes.select do |klass|
|
37
|
+
klass.friendly_id_config.scopes_over?(configured_class)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def custom_cache_column?
|
42
|
+
!! custom_cache_column
|
43
|
+
end
|
44
|
+
|
45
|
+
def scope_for(record)
|
46
|
+
scope? ? record.send(scope).to_param : nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def scopes_over?(klass)
|
50
|
+
scope? && scope == klass.to_s.underscore.to_sym
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def autodiscover_cache_column
|
56
|
+
:cached_slug if configured_class.properties[:cached_slug]
|
57
|
+
end
|
58
|
+
|
59
|
+
def associated_friendly_classes
|
60
|
+
configured_class.relationships.values.select { |relationship|
|
61
|
+
relationship.child_model.respond_to?(:friendly_id_config)
|
62
|
+
}.map(&:child_model)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module FriendlyId
|
4
|
+
module DataMapperAdapter
|
5
|
+
|
6
|
+
module SimpleModel
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.class_eval do
|
10
|
+
column = friendly_id_config.column
|
11
|
+
validates_presence_of column, :unless => :skip_friendly_id_validations
|
12
|
+
validates_length_of column, :maximum => friendly_id_config.max_length, :unless => :skip_friendly_id_validations
|
13
|
+
validates_with_method column, :method => :validate_friendly_id, :unless => :skip_friendly_id_validations
|
14
|
+
|
15
|
+
before :update do
|
16
|
+
@old_friendly_id = original_attributes[properties[friendly_id_config.column]]
|
17
|
+
end
|
18
|
+
|
19
|
+
after :update, :update_scopes
|
20
|
+
end
|
21
|
+
|
22
|
+
def base.get(*key)
|
23
|
+
if key.size == 1
|
24
|
+
return super if key.first.unfriendly_id?
|
25
|
+
column = self.friendly_id_config.column
|
26
|
+
repository = self.repository
|
27
|
+
key = self.key(repository.name).typecast(key)
|
28
|
+
result = self.first(column.to_sym => key)
|
29
|
+
return super unless result
|
30
|
+
result.friendly_id_status.name = name
|
31
|
+
result
|
32
|
+
else
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get the {FriendlyId::Status} after the find has been performed.
|
39
|
+
def friendly_id_status
|
40
|
+
@friendly_id_status ||= Status.new(:record => self)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the friendly_id.
|
44
|
+
def friendly_id
|
45
|
+
send friendly_id_config.column
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the friendly id, or if none is available, the numeric id.
|
49
|
+
def to_param
|
50
|
+
(friendly_id || id).to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# Update the slugs for any model that is using this model as its
|
56
|
+
# FriendlyId scope.
|
57
|
+
def update_scopes
|
58
|
+
if @old_friendly_id != friendly_id
|
59
|
+
friendly_id_config.child_scopes.each do |klass|
|
60
|
+
Slug.all(:sluggable_type => klass, :scope => @old_friendly_id).update(:scope => friendly_id)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def friendly_id_config
|
66
|
+
self.class.friendly_id_config
|
67
|
+
end
|
68
|
+
|
69
|
+
def skip_friendly_id_validations
|
70
|
+
friendly_id.nil? && friendly_id_config.allow_nil?
|
71
|
+
end
|
72
|
+
|
73
|
+
def validate_friendly_id
|
74
|
+
if result = friendly_id_config.reserved_error_message(friendly_id)
|
75
|
+
return [false, result.join(' ')]
|
76
|
+
else
|
77
|
+
return true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# A Slug is a unique, human-friendly identifier for a DataMapper model
|
2
|
+
class Slug
|
3
|
+
include ::DataMapper::Resource
|
4
|
+
|
5
|
+
property :id, Serial
|
6
|
+
property :name, String, :index => :index_slugs_on_n_s_s_and_s, :required => true, :length => 1..255
|
7
|
+
property :sluggable_id, Integer, :index => :sluggable_id
|
8
|
+
property :sequence, Integer, :index => :index_slugs_on_n_s_s_and_s, :required => true, :default => 1
|
9
|
+
property :sluggable_type, Class, :index => :index_slugs_on_n_s_s_and_s
|
10
|
+
property :scope, String, :index => :index_slugs_on_n_s_s_and_s
|
11
|
+
property :created_at, DateTime
|
12
|
+
|
13
|
+
before :save do
|
14
|
+
self.sequence = next_sequence
|
15
|
+
self.created_at = DateTime.now
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.similar_to(slug)
|
19
|
+
all({
|
20
|
+
:name => slug.name,
|
21
|
+
:scope => slug.scope,
|
22
|
+
:sluggable_type => slug.sluggable_type,
|
23
|
+
:order => [:sequence.asc]
|
24
|
+
})
|
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
|
+
def sluggable
|
41
|
+
sluggable_type.get(sluggable_id)
|
42
|
+
end
|
43
|
+
|
44
|
+
def sluggable=(instance)
|
45
|
+
attribute_set(:sluggable_type, instance.class)
|
46
|
+
attribute_set(:sluggable_id, instance.id)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def enable_name_reversion
|
52
|
+
conditions = { :sluggable_id => sluggable_id, :sluggable_type => sluggable_type,
|
53
|
+
:name => name, :scope => scope }
|
54
|
+
self.class.all(conditions).destroy
|
55
|
+
end
|
56
|
+
|
57
|
+
def friendly_id_with_sequence
|
58
|
+
"#{name}#{separator}#{sequence}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def next_sequence
|
62
|
+
enable_name_reversion
|
63
|
+
conditions = { :name => name, :scope => scope, :sluggable_type => sluggable_type }
|
64
|
+
prev = self.class.first(conditions.update(:order => :sequence.desc))
|
65
|
+
prev ? prev.sequence.succ : 1
|
66
|
+
end
|
67
|
+
|
68
|
+
def separator
|
69
|
+
sluggable_type.friendly_id_config.sequence_separator
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'friendly_id/slugged'
|
2
|
+
require 'friendly_id/status'
|
3
|
+
|
4
|
+
module FriendlyId
|
5
|
+
module DataMapperAdapter
|
6
|
+
|
7
|
+
module SluggedModel
|
8
|
+
|
9
|
+
include FriendlyId::Slugged::Model
|
10
|
+
|
11
|
+
def self.included(base)
|
12
|
+
base.class_eval do
|
13
|
+
has n, :slugs,
|
14
|
+
:model => ::Slug,
|
15
|
+
:child_key => [:sluggable_id],
|
16
|
+
:conditions => { :sluggable_type => base },
|
17
|
+
:order => [:id.desc]
|
18
|
+
|
19
|
+
before :save do
|
20
|
+
begin
|
21
|
+
build_slug if new_slug_needed?
|
22
|
+
method = friendly_id_config.method
|
23
|
+
rescue FriendlyId::BlankError
|
24
|
+
@errors ||= ValidationErrors.new
|
25
|
+
@errors[method] = "can't be blank"
|
26
|
+
throw :halt, false
|
27
|
+
rescue FriendlyId::ReservedError
|
28
|
+
@errors ||= ValidationErrors.new
|
29
|
+
@errors[method] = "is reserved"
|
30
|
+
throw :halt, false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
after(:save) do
|
35
|
+
throw :halt, false if friendly_id_config.allow_nil? && !slug
|
36
|
+
|
37
|
+
slug.sluggable_id = id
|
38
|
+
slug.save
|
39
|
+
set_slug_cache
|
40
|
+
end
|
41
|
+
|
42
|
+
after :update, :update_scope
|
43
|
+
after :update, :update_dependent_scopes
|
44
|
+
|
45
|
+
before(:destroy) do
|
46
|
+
slugs.destroy!
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
51
|
+
def self.get(*key)
|
52
|
+
options = key.extract_options!
|
53
|
+
|
54
|
+
if key.size == 1
|
55
|
+
return super if key.first.is_a?(Array) || key.first.unfriendly_id?
|
56
|
+
name, sequence = key.first.to_s.parse_friendly_id
|
57
|
+
|
58
|
+
if !friendly_id_config.scope? && friendly_id_config.cache_column?
|
59
|
+
result = self.first(friendly_id_config.cache_column => key.first)
|
60
|
+
end
|
61
|
+
|
62
|
+
conditions = {
|
63
|
+
slugs.name => name,
|
64
|
+
slugs.sequence => sequence
|
65
|
+
}
|
66
|
+
conditions.merge!({
|
67
|
+
slugs.scope => (options[:scope].to_param if options[:scope] && options[:scope].respond_to?(:to_param))
|
68
|
+
}) if friendly_id_config.scope?
|
69
|
+
|
70
|
+
result ||= self.first(conditions)
|
71
|
+
return super unless result
|
72
|
+
result.friendly_id_status.name = name
|
73
|
+
result.friendly_id_status.sequence = sequence
|
74
|
+
result
|
75
|
+
else
|
76
|
+
super
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.get!(*key)
|
81
|
+
return super unless friendly_id_config.scope?
|
82
|
+
|
83
|
+
result = get(*key)
|
84
|
+
if result
|
85
|
+
result
|
86
|
+
else
|
87
|
+
options = key.extract_options!
|
88
|
+
scope = options[:scope]
|
89
|
+
message = "Could not find \#{self.name} with key \#{key.inspect}"
|
90
|
+
message << " and scope \#{scope.inspect}" if scope
|
91
|
+
message << ". Scope expected but none given." unless scope
|
92
|
+
raise(::DataMapper::ObjectNotFoundError, message)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
RUBY
|
96
|
+
end
|
97
|
+
|
98
|
+
def slug
|
99
|
+
@slug ||= slugs.first
|
100
|
+
end
|
101
|
+
|
102
|
+
def find_slug(name, sequence)
|
103
|
+
@slug = slugs.first(:name => name, :sequence => sequence)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns the friendly id, or if none is available, the numeric id. Note that this
|
107
|
+
# method will use the cached_slug value if present, unlike {#friendly_id}.
|
108
|
+
def to_param
|
109
|
+
friendly_id_config.cache_column ? to_param_from_cache : to_param_from_slug
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def scope_changed?
|
115
|
+
friendly_id_config.scope? && send(friendly_id_config.scope).to_param != slug.scope
|
116
|
+
end
|
117
|
+
|
118
|
+
# Respond with the cached value if available.
|
119
|
+
def to_param_from_cache
|
120
|
+
attribute_get(friendly_id_config.cache_column) || id.to_s
|
121
|
+
end
|
122
|
+
|
123
|
+
# Respond with the slugged value if available.
|
124
|
+
def to_param_from_slug
|
125
|
+
slug? ? slug.to_friendly_id : id.to_s
|
126
|
+
end
|
127
|
+
|
128
|
+
# Build the new slug using the generated friendly id.
|
129
|
+
def build_slug
|
130
|
+
return unless new_slug_needed?
|
131
|
+
@slug = slugs.new(:name => slug_text, :scope => friendly_id_config.scope_for(self))
|
132
|
+
raise FriendlyId::BlankError unless @slug.valid?
|
133
|
+
@new_friendly_id = @slug.to_friendly_id
|
134
|
+
@slug
|
135
|
+
end
|
136
|
+
|
137
|
+
# Reset the cached friendly_id?
|
138
|
+
def new_cache_needed?
|
139
|
+
uses_slug_cache? && slug? && send(friendly_id_config.cache_column) != slug.to_friendly_id
|
140
|
+
end
|
141
|
+
|
142
|
+
# Reset the cached friendly_id.
|
143
|
+
def set_slug_cache
|
144
|
+
if new_cache_needed?
|
145
|
+
self.attribute_set(friendly_id_config.cache_column, slug.to_friendly_id)
|
146
|
+
self.save_self(false) # save!
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def skip_friendly_id_validations
|
151
|
+
friendly_id.nil? && friendly_id_config.allow_nil?
|
152
|
+
end
|
153
|
+
|
154
|
+
def update_scope
|
155
|
+
return unless slug && scope_changed?
|
156
|
+
transaction do
|
157
|
+
slug.scope = send(friendly_id_config.scope).to_param
|
158
|
+
similar = Slug.similar_to(slug)
|
159
|
+
if !similar.empty?
|
160
|
+
slug.sequence = similar.first.sequence.succ
|
161
|
+
end
|
162
|
+
slug.save
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Update the slugs for any model that is using this model as its
|
167
|
+
# FriendlyId scope.
|
168
|
+
def update_dependent_scopes
|
169
|
+
return unless friendly_id_config.class.scopes_used?
|
170
|
+
# slugs.reload.size == 1, slugs.dirty? == true
|
171
|
+
if slugs.size > 1 && @new_friendly_id
|
172
|
+
friendly_id_config.child_scopes.each do |klass|
|
173
|
+
# slugs.first -- ordering not respected when dirty
|
174
|
+
Slug.all(:sluggable_type => klass, :scope => slugs.first.to_friendly_id).update(:scope => @new_friendly_id)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Does the model use slug caching?
|
180
|
+
def uses_slug_cache?
|
181
|
+
friendly_id_config.cache_column?
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'active_support/core_ext'
|
2
|
+
|
3
|
+
module FriendlyId
|
4
|
+
class TaskRunner
|
5
|
+
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
attr_accessor :days
|
9
|
+
attr_accessor :klass
|
10
|
+
attr_accessor :task_options
|
11
|
+
|
12
|
+
def_delegators :klass, :find, :friendly_id_config
|
13
|
+
|
14
|
+
OLD_SLUG_DAYS = 45
|
15
|
+
|
16
|
+
def initialize(&block)
|
17
|
+
self.klass = ENV["MODEL"]
|
18
|
+
self.days = ENV["DAYS"]
|
19
|
+
end
|
20
|
+
|
21
|
+
def days=(days)
|
22
|
+
@days ||= days.blank? ? OLD_SLUG_DAYS : days.to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
def klass=(klass)
|
26
|
+
@klass ||= klass.to_s.classify.constantize unless klass.blank?
|
27
|
+
end
|
28
|
+
|
29
|
+
def make_slugs
|
30
|
+
validate_uses_slugs
|
31
|
+
options = {:limit => 100, :slugs => nil, :order => [:id.asc]}.merge(task_options || {})
|
32
|
+
while records = klass.all(options) do
|
33
|
+
break if records.size == 0
|
34
|
+
records.each do |record|
|
35
|
+
record.send(:build_slug) # FIXME: DataMapper Hack: this hook is not getting called
|
36
|
+
record.save
|
37
|
+
yield(record) if block_given?
|
38
|
+
end
|
39
|
+
options.merge!(:id.gt => records.last.id)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete_slugs
|
44
|
+
validate_uses_slugs
|
45
|
+
Slug.all(:sluggable_type => klass).destroy!
|
46
|
+
if column = friendly_id_config.cache_column
|
47
|
+
klass.all.update!(column => nil)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def delete_old_slugs
|
52
|
+
conditions = ["created_at < ?", DateTime.now - days]
|
53
|
+
if klass
|
54
|
+
conditions[0] << " AND sluggable_type = ?"
|
55
|
+
conditions << klass.to_s
|
56
|
+
end
|
57
|
+
Slug.all(:conditions => conditions).select(&:outdated?).map(&:destroy)
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate_uses_slugs
|
61
|
+
(raise "You need to pass a MODEL=<model name> argument to rake") if klass.blank?
|
62
|
+
unless friendly_id_config.use_slug?
|
63
|
+
raise "Class '%s' doesn't use slugs" % klass.to_s
|
64
|
+
end
|
65
|
+
rescue NoMethodError
|
66
|
+
raise "Class '%s' doesn't use FriendlyId" % klass.to_s
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'dm-core'
|
2
|
+
require 'dm-transactions'
|
3
|
+
require 'dm-validations'
|
4
|
+
require 'friendly_id/datamapper_adapter/configuration'
|
5
|
+
require 'friendly_id/datamapper_adapter/slug'
|
6
|
+
require 'friendly_id/datamapper_adapter/simple_model'
|
7
|
+
require 'friendly_id/datamapper_adapter/slugged_model'
|
8
|
+
# require 'friendly_id/datamapper_adapter/tasks'
|
9
|
+
require 'forwardable'
|
10
|
+
|
11
|
+
module FriendlyId
|
12
|
+
module DataMapperAdapter
|
13
|
+
|
14
|
+
include FriendlyId::Base
|
15
|
+
|
16
|
+
def has_friendly_id(method, options = {})
|
17
|
+
extend FriendlyId::DataMapperAdapter::ClassMethods
|
18
|
+
@friendly_id_config = Configuration.new(self, method, options)
|
19
|
+
|
20
|
+
if friendly_id_config.use_slug?
|
21
|
+
include ::FriendlyId::DataMapperAdapter::SluggedModel
|
22
|
+
else
|
23
|
+
include ::FriendlyId::DataMapperAdapter::SimpleModel
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
attr_accessor :friendly_id_config
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
DataMapper::Model.append_extensions FriendlyId::DataMapperAdapter
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
module FriendlyId
|
4
|
+
module Test
|
5
|
+
module DataMapperAdapter
|
6
|
+
|
7
|
+
# Tests for DataMapper models using FriendlyId with slugs.
|
8
|
+
class BasicSluggedModelTest < ::Test::Unit::TestCase
|
9
|
+
include FriendlyId::Test::Generic
|
10
|
+
include FriendlyId::Test::Slugged
|
11
|
+
include FriendlyId::Test::DataMapperAdapter::Slugged
|
12
|
+
include FriendlyId::Test::DataMapperAdapter::Core
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|