permalink_fu 1.0.0
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/README.markdown +64 -0
- data/lib/data/x00.yml +257 -0
- data/lib/data/x01.yml +257 -0
- data/lib/data/x02.yml +256 -0
- data/lib/data/x03.yml +256 -0
- data/lib/data/x04.yml +256 -0
- data/lib/data/x05.yml +256 -0
- data/lib/data/x06.yml +256 -0
- data/lib/data/x07.yml +256 -0
- data/lib/data/x09.yml +256 -0
- data/lib/data/x0a.yml +256 -0
- data/lib/data/x0b.yml +256 -0
- data/lib/data/x0c.yml +256 -0
- data/lib/data/x0d.yml +256 -0
- data/lib/data/x0e.yml +256 -0
- data/lib/data/x0f.yml +256 -0
- data/lib/data/x10.yml +256 -0
- data/lib/data/x11.yml +256 -0
- data/lib/data/x12.yml +257 -0
- data/lib/data/x13.yml +256 -0
- data/lib/data/x14.yml +257 -0
- data/lib/data/x15.yml +257 -0
- data/lib/data/x16.yml +256 -0
- data/lib/data/x17.yml +256 -0
- data/lib/data/x18.yml +256 -0
- data/lib/data/x1e.yml +256 -0
- data/lib/data/x1f.yml +256 -0
- data/lib/data/x20.yml +256 -0
- data/lib/data/x21.yml +256 -0
- data/lib/data/x22.yml +256 -0
- data/lib/data/x23.yml +256 -0
- data/lib/data/x24.yml +256 -0
- data/lib/data/x25.yml +256 -0
- data/lib/data/x26.yml +256 -0
- data/lib/data/x27.yml +256 -0
- data/lib/data/x28.yml +257 -0
- data/lib/data/x2e.yml +256 -0
- data/lib/data/x2f.yml +256 -0
- data/lib/data/x30.yml +256 -0
- data/lib/data/x31.yml +256 -0
- data/lib/data/x32.yml +256 -0
- data/lib/data/x33.yml +256 -0
- data/lib/data/x4d.yml +256 -0
- data/lib/data/x4e.yml +257 -0
- data/lib/data/x4f.yml +257 -0
- data/lib/data/x50.yml +257 -0
- data/lib/data/x51.yml +257 -0
- data/lib/data/x52.yml +257 -0
- data/lib/data/x53.yml +257 -0
- data/lib/data/x54.yml +257 -0
- data/lib/data/x55.yml +257 -0
- data/lib/data/x56.yml +257 -0
- data/lib/data/x57.yml +257 -0
- data/lib/data/x58.yml +257 -0
- data/lib/data/x59.yml +257 -0
- data/lib/data/x5a.yml +257 -0
- data/lib/data/x5b.yml +257 -0
- data/lib/data/x5c.yml +257 -0
- data/lib/data/x5d.yml +257 -0
- data/lib/data/x5e.yml +257 -0
- data/lib/data/x5f.yml +257 -0
- data/lib/data/x60.yml +257 -0
- data/lib/data/x61.yml +257 -0
- data/lib/data/x62.yml +257 -0
- data/lib/data/x63.yml +257 -0
- data/lib/data/x64.yml +257 -0
- data/lib/data/x65.yml +257 -0
- data/lib/data/x66.yml +257 -0
- data/lib/data/x67.yml +257 -0
- data/lib/data/x68.yml +257 -0
- data/lib/data/x69.yml +257 -0
- data/lib/data/x6a.yml +257 -0
- data/lib/data/x6b.yml +257 -0
- data/lib/data/x6c.yml +257 -0
- data/lib/data/x6d.yml +257 -0
- data/lib/data/x6e.yml +257 -0
- data/lib/data/x6f.yml +257 -0
- data/lib/data/x70.yml +257 -0
- data/lib/data/x71.yml +257 -0
- data/lib/data/x72.yml +257 -0
- data/lib/data/x73.yml +257 -0
- data/lib/data/x74.yml +257 -0
- data/lib/data/x75.yml +257 -0
- data/lib/data/x76.yml +257 -0
- data/lib/data/x77.yml +257 -0
- data/lib/data/x78.yml +257 -0
- data/lib/data/x79.yml +257 -0
- data/lib/data/x7a.yml +257 -0
- data/lib/data/x7b.yml +257 -0
- data/lib/data/x7c.yml +257 -0
- data/lib/data/x7d.yml +257 -0
- data/lib/data/x7e.yml +257 -0
- data/lib/data/x7f.yml +257 -0
- data/lib/data/x80.yml +257 -0
- data/lib/data/x81.yml +257 -0
- data/lib/data/x82.yml +257 -0
- data/lib/data/x83.yml +257 -0
- data/lib/data/x84.yml +257 -0
- data/lib/data/x85.yml +257 -0
- data/lib/data/x86.yml +257 -0
- data/lib/data/x87.yml +257 -0
- data/lib/data/x88.yml +257 -0
- data/lib/data/x89.yml +257 -0
- data/lib/data/x8a.yml +257 -0
- data/lib/data/x8b.yml +257 -0
- data/lib/data/x8c.yml +257 -0
- data/lib/data/x8d.yml +257 -0
- data/lib/data/x8e.yml +257 -0
- data/lib/data/x8f.yml +257 -0
- data/lib/data/x90.yml +257 -0
- data/lib/data/x91.yml +257 -0
- data/lib/data/x92.yml +257 -0
- data/lib/data/x93.yml +257 -0
- data/lib/data/x94.yml +257 -0
- data/lib/data/x95.yml +257 -0
- data/lib/data/x96.yml +257 -0
- data/lib/data/x97.yml +257 -0
- data/lib/data/x98.yml +257 -0
- data/lib/data/x99.yml +257 -0
- data/lib/data/x9a.yml +257 -0
- data/lib/data/x9b.yml +257 -0
- data/lib/data/x9c.yml +257 -0
- data/lib/data/x9d.yml +257 -0
- data/lib/data/x9e.yml +257 -0
- data/lib/data/x9f.yml +256 -0
- data/lib/data/xa0.yml +257 -0
- data/lib/data/xa1.yml +257 -0
- data/lib/data/xa2.yml +257 -0
- data/lib/data/xa3.yml +257 -0
- data/lib/data/xa4.yml +256 -0
- data/lib/data/xac.yml +257 -0
- data/lib/data/xad.yml +257 -0
- data/lib/data/xae.yml +257 -0
- data/lib/data/xaf.yml +257 -0
- data/lib/data/xb0.yml +257 -0
- data/lib/data/xb1.yml +257 -0
- data/lib/data/xb2.yml +257 -0
- data/lib/data/xb3.yml +257 -0
- data/lib/data/xb4.yml +257 -0
- data/lib/data/xb5.yml +257 -0
- data/lib/data/xb6.yml +257 -0
- data/lib/data/xb7.yml +257 -0
- data/lib/data/xb8.yml +257 -0
- data/lib/data/xb9.yml +257 -0
- data/lib/data/xba.yml +257 -0
- data/lib/data/xbb.yml +257 -0
- data/lib/data/xbc.yml +257 -0
- data/lib/data/xbd.yml +257 -0
- data/lib/data/xbe.yml +257 -0
- data/lib/data/xbf.yml +257 -0
- data/lib/data/xc0.yml +257 -0
- data/lib/data/xc1.yml +257 -0
- data/lib/data/xc2.yml +257 -0
- data/lib/data/xc3.yml +257 -0
- data/lib/data/xc4.yml +257 -0
- data/lib/data/xc5.yml +257 -0
- data/lib/data/xc6.yml +257 -0
- data/lib/data/xc7.yml +257 -0
- data/lib/data/xc8.yml +257 -0
- data/lib/data/xc9.yml +257 -0
- data/lib/data/xca.yml +257 -0
- data/lib/data/xcb.yml +257 -0
- data/lib/data/xcc.yml +257 -0
- data/lib/data/xcd.yml +257 -0
- data/lib/data/xce.yml +257 -0
- data/lib/data/xcf.yml +257 -0
- data/lib/data/xd0.yml +257 -0
- data/lib/data/xd1.yml +257 -0
- data/lib/data/xd2.yml +257 -0
- data/lib/data/xd3.yml +257 -0
- data/lib/data/xd4.yml +257 -0
- data/lib/data/xd5.yml +257 -0
- data/lib/data/xd6.yml +257 -0
- data/lib/data/xd7.yml +256 -0
- data/lib/data/xf9.yml +257 -0
- data/lib/data/xfa.yml +256 -0
- data/lib/data/xfb.yml +257 -0
- data/lib/data/xfc.yml +257 -0
- data/lib/data/xfd.yml +256 -0
- data/lib/data/xfe.yml +257 -0
- data/lib/data/xff.yml +257 -0
- data/lib/permalink_fu.rb +176 -0
- data/test/permalink_fu_test.rb +476 -0
- data/test/test_helper.rb +13 -0
- metadata +251 -0
data/lib/permalink_fu.rb
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'digest/sha1'
|
|
3
|
+
|
|
4
|
+
module PermalinkFu
|
|
5
|
+
def has_permalink(attr_names = [], permalink_field = nil, options = {})
|
|
6
|
+
if permalink_field.is_a?(Hash)
|
|
7
|
+
options = permalink_field
|
|
8
|
+
permalink_field = nil
|
|
9
|
+
end
|
|
10
|
+
ClassMethods.setup_permalink_fu_on self do
|
|
11
|
+
self.permalink_attributes = Array(attr_names)
|
|
12
|
+
self.permalink_field = (permalink_field || 'permalink').to_s
|
|
13
|
+
self.permalink_options = {:unique => true}.update(options)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
include InstanceMethods
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
# This method does the actual permalink escaping.
|
|
21
|
+
def escape(str)
|
|
22
|
+
s = ClassMethods.decode(str)#.force_encoding("UTF-8")
|
|
23
|
+
s.gsub!(/[^\x00-\x7F]+/, '') # Remove anything non-ASCII entirely (e.g. diacritics).
|
|
24
|
+
s.gsub!(/[^\w_ \-]+/i, '') # Remove unwanted chars.
|
|
25
|
+
s.gsub!(/[ \-]+/i, '-') # No more than one of the separator in a row.
|
|
26
|
+
s.gsub!(/^\-|\-$/i, '') # Remove leading/trailing separator.
|
|
27
|
+
s.downcase!
|
|
28
|
+
s.size == 0 ? ClassMethods.random_permalink(str) : s
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Contains class methods for ActiveRecord models that have permalinks
|
|
33
|
+
module ClassMethods
|
|
34
|
+
# Contains Unicode codepoints, loading as needed from YAML files
|
|
35
|
+
CODEPOINTS = Hash.new { |h, k|
|
|
36
|
+
h[k] = YAML::load_file(File.join(File.dirname(__FILE__), "data", "#{k}.yml"))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
def decode(string)
|
|
41
|
+
string.gsub(/[^\x00-\x7f]/u) do |codepoint|
|
|
42
|
+
begin
|
|
43
|
+
CODEPOINTS["x%02x" % (codepoint.unpack("U")[0] >> 8)][codepoint.unpack("U")[0] & 255]
|
|
44
|
+
rescue
|
|
45
|
+
"_"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def random_permalink(seed = nil)
|
|
51
|
+
Digest::SHA1.hexdigest("#{seed}#{Time.now.to_s.split(//).sort_by {rand}}")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.setup_permalink_fu_on(base)
|
|
56
|
+
base.extend self
|
|
57
|
+
class << base
|
|
58
|
+
attr_accessor :permalink_options
|
|
59
|
+
attr_accessor :permalink_attributes
|
|
60
|
+
attr_accessor :permalink_field
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
yield
|
|
64
|
+
|
|
65
|
+
if base.permalink_options[:unique]
|
|
66
|
+
base.before_validation :create_unique_permalink
|
|
67
|
+
else
|
|
68
|
+
base.before_validation :create_common_permalink
|
|
69
|
+
end
|
|
70
|
+
class << base
|
|
71
|
+
alias_method :define_attribute_methods_without_permalinks, :define_attribute_methods
|
|
72
|
+
alias_method :define_attribute_methods, :define_attribute_methods_with_permalinks
|
|
73
|
+
end unless base.respond_to?(:define_attribute_methods_without_permalinks)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def define_attribute_methods_with_permalinks
|
|
77
|
+
if (value = define_attribute_methods_without_permalinks) && self.permalink_field
|
|
78
|
+
class_eval <<-EOV
|
|
79
|
+
def #{self.permalink_field}=(new_value);
|
|
80
|
+
write_attribute(:#{self.permalink_field}, new_value.blank? ? '' : PermalinkFu.escape(new_value));
|
|
81
|
+
end
|
|
82
|
+
EOV
|
|
83
|
+
end
|
|
84
|
+
value
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# This contains instance methods for ActiveRecord models that have permalinks.
|
|
89
|
+
module InstanceMethods
|
|
90
|
+
protected
|
|
91
|
+
def create_common_permalink
|
|
92
|
+
return unless should_create_permalink?
|
|
93
|
+
if read_attribute(self.class.permalink_field).blank? || permalink_fields_changed?
|
|
94
|
+
send("#{self.class.permalink_field}=", create_permalink_for(self.class.permalink_attributes))
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Quit now if we have the changed method available and nothing has changed
|
|
98
|
+
permalink_changed = "#{self.class.permalink_field}_changed?"
|
|
99
|
+
return if respond_to?(permalink_changed) && !send(permalink_changed)
|
|
100
|
+
|
|
101
|
+
# Otherwise find the limit and crop the permalink
|
|
102
|
+
limit = self.class.columns_hash[self.class.permalink_field].limit
|
|
103
|
+
base = send("#{self.class.permalink_field}=", read_attribute(self.class.permalink_field)[0..limit - 1])
|
|
104
|
+
[limit, base]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def create_unique_permalink
|
|
108
|
+
limit, base = create_common_permalink
|
|
109
|
+
return if limit.nil? # nil if the permalink has not changed or :if/:unless fail
|
|
110
|
+
counter = 1
|
|
111
|
+
# oh how i wish i could use a hash for conditions
|
|
112
|
+
conditions = ["#{self.class.permalink_field} = ?", base]
|
|
113
|
+
unless new_record?
|
|
114
|
+
conditions.first << " and id != ?"
|
|
115
|
+
conditions << id
|
|
116
|
+
end
|
|
117
|
+
if self.class.permalink_options[:scope]
|
|
118
|
+
[self.class.permalink_options[:scope]].flatten.each do |scope|
|
|
119
|
+
value = send(scope)
|
|
120
|
+
if value
|
|
121
|
+
conditions.first << " and #{scope} = ?"
|
|
122
|
+
conditions << send(scope)
|
|
123
|
+
else
|
|
124
|
+
conditions.first << " and #{scope} IS NULL"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
while self.class.exists?(conditions)
|
|
129
|
+
suffix = "-#{counter += 1}"
|
|
130
|
+
conditions[1] = "#{base[0..limit-suffix.size-1]}#{suffix}"
|
|
131
|
+
send("#{self.class.permalink_field}=", conditions[1])
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def create_permalink_for(attr_names)
|
|
136
|
+
str = attr_names.collect { |attr_name| send(attr_name).to_s } * " "
|
|
137
|
+
str.blank? ? PermalinkFu::ClassMethods.random_permalink : str
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
def should_create_permalink?
|
|
142
|
+
if self.class.permalink_field.blank?
|
|
143
|
+
false
|
|
144
|
+
elsif self.class.permalink_options[:if]
|
|
145
|
+
evaluate_method(self.class.permalink_options[:if])
|
|
146
|
+
elsif self.class.permalink_options[:unless]
|
|
147
|
+
!evaluate_method(self.class.permalink_options[:unless])
|
|
148
|
+
else
|
|
149
|
+
true
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Don't even check _changed? methods unless :update is set
|
|
154
|
+
def permalink_fields_changed?
|
|
155
|
+
return false unless self.class.permalink_options[:update]
|
|
156
|
+
self.class.permalink_attributes.any? do |attribute|
|
|
157
|
+
changed_method = "#{attribute}_changed?"
|
|
158
|
+
respond_to?(changed_method) ? send(changed_method) : true
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def evaluate_method(method)
|
|
163
|
+
case method
|
|
164
|
+
when Symbol
|
|
165
|
+
send(method)
|
|
166
|
+
when String
|
|
167
|
+
eval(method, instance_eval { binding })
|
|
168
|
+
when Proc, Method
|
|
169
|
+
method.call(self)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Extend ActiveRecord functionality
|
|
176
|
+
ActiveRecord::Base.extend PermalinkFu
|
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
# Load the plugin's test_helper (Rails 2.x needs the path)
|
|
4
|
+
begin
|
|
5
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
|
6
|
+
rescue LoadError
|
|
7
|
+
require 'test_helper'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class PermalinkFuTest < Test::Unit::TestCase
|
|
11
|
+
@@samples = {
|
|
12
|
+
'This IS a Tripped out title!!.!1 (well/ not really)' => 'this-is-a-tripped-out-title1-well-not-really',
|
|
13
|
+
'////// meph1sto r0x ! \\\\\\' => 'meph1sto-r0x',
|
|
14
|
+
'āčēģīķļņū' => 'acegiklnu',
|
|
15
|
+
'中文測試 chinese text' => 'zhong-wen-ce-shi-chinese-text',
|
|
16
|
+
'fööbär' => 'foobar'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@@extra = { 'some-)()()-ExtRa!/// .data==?> to \/\/test' => 'some-extra-data-to-test' }
|
|
20
|
+
|
|
21
|
+
def test_basemodel
|
|
22
|
+
@m = BaseModel.new
|
|
23
|
+
assert @m.valid?
|
|
24
|
+
assert_equal @m.id, nil
|
|
25
|
+
assert_equal @m.title, nil
|
|
26
|
+
assert_equal @m.permalink, nil
|
|
27
|
+
assert_equal @m.extra, nil
|
|
28
|
+
assert_equal @m.foo, nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_set_new_permalink_attributes_on_sub_class
|
|
32
|
+
@m = ClassModel.new
|
|
33
|
+
@m.title = 'foo'
|
|
34
|
+
@m.extra = 'bar'
|
|
35
|
+
assert @m.valid?
|
|
36
|
+
assert_equal @m.permalink, 'foo'
|
|
37
|
+
|
|
38
|
+
@m = SubClassHasPermalinkModel.new
|
|
39
|
+
@m.title = 'foo'
|
|
40
|
+
@m.extra = 'bar'
|
|
41
|
+
assert @m.valid?
|
|
42
|
+
assert_equal @m.permalink, 'foo-bar'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_should_not_inherit_permalink_attributes
|
|
46
|
+
@m = SubClassNoPermalinkModel.new
|
|
47
|
+
@m.title = 'foo'
|
|
48
|
+
assert @m.valid?
|
|
49
|
+
assert_equal @m.permalink, nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_should_escape_permalinks
|
|
53
|
+
@@samples.each do |from, to|
|
|
54
|
+
assert_equal to, PermalinkFu.escape(from)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_should_escape_activerecord_model
|
|
59
|
+
@m = MockModel.new
|
|
60
|
+
@@samples.each do |from, to|
|
|
61
|
+
@m.title = from; @m.permalink = nil
|
|
62
|
+
assert @m.valid?
|
|
63
|
+
assert_equal to, @m.permalink
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_should_escape_activerecord_model_with_existing_permalink
|
|
68
|
+
@m = MockModel.new
|
|
69
|
+
@@samples.each do |from, to|
|
|
70
|
+
@m.title = 'whatever'; @m.permalink = from
|
|
71
|
+
assert @m.valid?
|
|
72
|
+
assert_equal to, @m.permalink
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def test_multiple_attribute_permalink
|
|
77
|
+
@m = MockModelExtra.new
|
|
78
|
+
@@samples.each do |from, to|
|
|
79
|
+
@@extra.each do |from_extra, to_extra|
|
|
80
|
+
@m.title = from; @m.extra = from_extra; @m.permalink = nil
|
|
81
|
+
assert @m.valid?
|
|
82
|
+
assert_equal "#{to}-#{to_extra}", @m.permalink
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_should_create_unique_permalink
|
|
88
|
+
@m = MockModel.new
|
|
89
|
+
@m.title = 'foo'
|
|
90
|
+
assert @m.valid?
|
|
91
|
+
assert_equal 'foo-2', @m.permalink
|
|
92
|
+
|
|
93
|
+
@m.title = 'bar'
|
|
94
|
+
@m.permalink = nil
|
|
95
|
+
assert @m.valid?
|
|
96
|
+
assert_equal 'bar-3', @m.permalink
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def test_should_create_unique_permalink_when_assigned_directly
|
|
100
|
+
@m = MockModel.new
|
|
101
|
+
@m.permalink = 'foo'
|
|
102
|
+
assert @m.valid?
|
|
103
|
+
assert_equal 'foo-2', @m.permalink
|
|
104
|
+
|
|
105
|
+
# should always check itself for uniqueness when not respond_to?(:permalink_changed?)
|
|
106
|
+
@m.permalink = 'bar'
|
|
107
|
+
assert @m.valid?
|
|
108
|
+
assert_equal 'bar-3', @m.permalink
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def test_should_common_permalink_if_unique_is_false
|
|
112
|
+
@m = CommonMockModel.new
|
|
113
|
+
@m.permalink = 'foo'
|
|
114
|
+
assert @m.valid?
|
|
115
|
+
assert_equal 'foo', @m.permalink
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def test_should_not_check_itself_for_unique_permalink_if_unchanged
|
|
119
|
+
@m = MockModel.new
|
|
120
|
+
@m.id = 2
|
|
121
|
+
@m.permalink = 'bar-2'
|
|
122
|
+
@m.instance_eval do
|
|
123
|
+
@changed_attributes = {}
|
|
124
|
+
end
|
|
125
|
+
assert @m.valid?
|
|
126
|
+
assert_equal 'bar-2', @m.permalink
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def test_should_check_itself_for_unique_permalink_if_permalink_field_changed
|
|
130
|
+
@m = PermalinkChangeableMockModel.new
|
|
131
|
+
@m.permalink_will_change!
|
|
132
|
+
@m.permalink = 'foo'
|
|
133
|
+
assert @m.valid?
|
|
134
|
+
assert_equal 'foo-2', @m.permalink
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def test_should_not_check_itself_for_unique_permalink_if_permalink_field_not_changed
|
|
138
|
+
@m = PermalinkChangeableMockModel.new
|
|
139
|
+
@m.permalink = 'foo'
|
|
140
|
+
assert @m.valid?
|
|
141
|
+
assert_equal 'foo', @m.permalink
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def test_should_create_unique_scoped_permalink
|
|
145
|
+
@m = ScopedModel.new
|
|
146
|
+
@m.permalink = 'foo'
|
|
147
|
+
assert @m.valid?
|
|
148
|
+
assert_equal 'foo-2', @m.permalink
|
|
149
|
+
|
|
150
|
+
@m.foo = 5
|
|
151
|
+
@m.permalink = 'foo'
|
|
152
|
+
assert @m.valid?
|
|
153
|
+
assert_equal 'foo', @m.permalink
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def test_should_limit_permalink
|
|
157
|
+
@old = MockModel.columns_hash['permalink'].instance_variable_get(:@limit)
|
|
158
|
+
MockModel.columns_hash['permalink'].instance_variable_set(:@limit, 2)
|
|
159
|
+
@m = MockModel.new
|
|
160
|
+
@m.title = 'BOO'
|
|
161
|
+
assert @m.valid?
|
|
162
|
+
assert_equal 'bo', @m.permalink
|
|
163
|
+
ensure
|
|
164
|
+
MockModel.columns_hash['permalink'].instance_variable_set(:@limit, @old)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def test_should_limit_unique_permalink
|
|
168
|
+
@old = MockModel.columns_hash['permalink'].instance_variable_get(:@limit)
|
|
169
|
+
MockModel.columns_hash['permalink'].instance_variable_set(:@limit, 3)
|
|
170
|
+
@m = MockModel.new
|
|
171
|
+
@m.title = 'foo'
|
|
172
|
+
assert @m.valid?
|
|
173
|
+
assert_equal 'f-2', @m.permalink
|
|
174
|
+
ensure
|
|
175
|
+
MockModel.columns_hash['permalink'].instance_variable_set(:@limit, @old)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def test_should_abide_by_if_proc_condition
|
|
179
|
+
@m = IfProcConditionModel.new
|
|
180
|
+
@m.title = 'dont make me a permalink'
|
|
181
|
+
assert @m.valid?
|
|
182
|
+
assert_nil @m.permalink
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def test_should_abide_by_if_method_condition
|
|
186
|
+
@m = IfMethodConditionModel.new
|
|
187
|
+
@m.title = 'dont make me a permalink'
|
|
188
|
+
assert @m.valid?
|
|
189
|
+
assert_nil @m.permalink
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def test_should_abide_by_if_string_condition
|
|
193
|
+
@m = IfStringConditionModel.new
|
|
194
|
+
@m.title = 'dont make me a permalink'
|
|
195
|
+
assert @m.valid?
|
|
196
|
+
assert_nil @m.permalink
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def test_should_abide_by_unless_proc_condition
|
|
200
|
+
@m = UnlessProcConditionModel.new
|
|
201
|
+
@m.title = 'make me a permalink'
|
|
202
|
+
assert @m.valid?
|
|
203
|
+
assert_not_nil @m.permalink
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def test_should_abide_by_unless_method_condition
|
|
207
|
+
@m = UnlessMethodConditionModel.new
|
|
208
|
+
@m.title = 'make me a permalink'
|
|
209
|
+
assert @m.valid?
|
|
210
|
+
assert_not_nil @m.permalink
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def test_should_abide_by_unless_string_condition
|
|
214
|
+
@m = UnlessStringConditionModel.new
|
|
215
|
+
@m.title = 'make me a permalink'
|
|
216
|
+
assert @m.valid?
|
|
217
|
+
assert_not_nil @m.permalink
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def test_should_allow_override_of_permalink_method
|
|
221
|
+
@m = OverrideModel.new
|
|
222
|
+
@m[:permalink] = 'the permalink'
|
|
223
|
+
assert_not_equal @m.permalink, @m[:permalink]
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def test_should_create_permalink_from_attribute_not_attribute_accessor
|
|
227
|
+
@m = OverrideModel.new
|
|
228
|
+
@m.title = 'the permalink'
|
|
229
|
+
assert @m.valid?
|
|
230
|
+
assert_equal 'the-permalink', @m[:permalink]
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def test_should_not_update_permalink_unless_field_changed
|
|
234
|
+
@m = NoChangeModel.new
|
|
235
|
+
@m.title = 'the permalink'
|
|
236
|
+
@m.permalink = 'unchanged'
|
|
237
|
+
assert @m.valid?
|
|
238
|
+
assert_equal 'unchanged', @m[:permalink]
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def test_should_not_update_permalink_without_update_set_even_if_field_changed
|
|
242
|
+
@m = ChangedWithoutUpdateModel.new
|
|
243
|
+
@m.title = 'the permalink'
|
|
244
|
+
@m.permalink = 'unchanged'
|
|
245
|
+
assert @m.valid?
|
|
246
|
+
assert_equal 'unchanged', @m[:permalink]
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def test_should_update_permalink_if_changed_method_does_not_exist
|
|
250
|
+
@m = OverrideModel.new
|
|
251
|
+
@m.title = 'the permalink'
|
|
252
|
+
assert @m.valid?
|
|
253
|
+
assert_equal 'the-permalink', @m[:permalink]
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def test_should_update_permalink_if_the_existing_permalink_is_nil
|
|
257
|
+
@m = NoChangeModel.new
|
|
258
|
+
@m.title = 'the permalink'
|
|
259
|
+
@m.permalink = nil
|
|
260
|
+
assert @m.valid?
|
|
261
|
+
assert_equal 'the-permalink', @m[:permalink]
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def test_should_update_permalink_if_the_existing_permalink_is_blank
|
|
265
|
+
@m = NoChangeModel.new
|
|
266
|
+
@m.title = 'the permalink'
|
|
267
|
+
@m.permalink = ''
|
|
268
|
+
assert @m.valid?
|
|
269
|
+
assert_equal 'the-permalink', @m[:permalink]
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def test_should_assign_a_random_permalink_if_the_title_is_nil
|
|
273
|
+
@m = NoChangeModel.new
|
|
274
|
+
@m.title = nil
|
|
275
|
+
assert @m.valid?
|
|
276
|
+
assert_not_nil @m[:permalink]
|
|
277
|
+
assert @m[:permalink].size > 0
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def test_should_assign_a_random_permalink_if_the_title_has_no_permalinkable_characters
|
|
281
|
+
@m = NoChangeModel.new
|
|
282
|
+
@m.title = '////'
|
|
283
|
+
assert @m.valid?
|
|
284
|
+
assert_not_nil @m[:permalink]
|
|
285
|
+
assert @m[:permalink].size > 0
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def test_should_update_permalink_the_first_time_the_title_is_set
|
|
289
|
+
@m = ChangedWithoutUpdateModel.new
|
|
290
|
+
@m.title = "old title"
|
|
291
|
+
assert @m.valid?
|
|
292
|
+
assert_equal "old-title", @m[:permalink]
|
|
293
|
+
@m.title = "new title"
|
|
294
|
+
assert @m.valid?
|
|
295
|
+
assert_equal "old-title", @m[:permalink]
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def test_should_not_update_permalink_if_already_set_even_if_title_changed
|
|
299
|
+
@m = ChangedWithoutUpdateModel.new
|
|
300
|
+
@m.permalink = "old permalink"
|
|
301
|
+
@m.title = "new title"
|
|
302
|
+
assert @m.valid?
|
|
303
|
+
assert_equal "old-permalink", @m[:permalink]
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def test_should_update_permalink_every_time_the_title_is_changed
|
|
307
|
+
@m = ChangedWithUpdateModel.new
|
|
308
|
+
@m.title = "old title"
|
|
309
|
+
assert @m.valid?
|
|
310
|
+
assert_equal "old-title", @m[:permalink]
|
|
311
|
+
@m.title = "new title"
|
|
312
|
+
assert @m.valid?
|
|
313
|
+
assert_equal "new-title", @m[:permalink]
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def test_should_work_correctly_for_scoped_fields_with_nil_value
|
|
317
|
+
s1 = ScopedModelForNilScope.new
|
|
318
|
+
s1.title = 'ack'
|
|
319
|
+
s1.foo = 3
|
|
320
|
+
assert s1.valid?
|
|
321
|
+
assert_equal 'ack', s1.permalink
|
|
322
|
+
|
|
323
|
+
s2 = ScopedModelForNilScope.new
|
|
324
|
+
s2.title = 'ack'
|
|
325
|
+
s2.foo = nil
|
|
326
|
+
assert s2.valid?
|
|
327
|
+
assert_equal 'ack-2', s2.permalink
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
class BaseModel < ActiveRecord::Base
|
|
332
|
+
cattr_accessor :columns
|
|
333
|
+
@@columns ||= []
|
|
334
|
+
|
|
335
|
+
def self.column(name, sql_type = nil, default = nil, null = true)
|
|
336
|
+
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type, null)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def self.exists?(*args)
|
|
340
|
+
false
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
column :id, 'int(11)'
|
|
344
|
+
column :title, 'varchar(100)'
|
|
345
|
+
column :permalink, 'varchar(100)'
|
|
346
|
+
column :extra, 'varchar(100)'
|
|
347
|
+
column :foo, 'varchar(100)'
|
|
348
|
+
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
class ClassModel < BaseModel
|
|
352
|
+
has_permalink :title
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
class SubClassHasPermalinkModel < ClassModel
|
|
356
|
+
has_permalink [:title, :extra]
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
class SubClassNoPermalinkModel < ClassModel
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
class MockModel < BaseModel
|
|
363
|
+
def self.exists?(conditions)
|
|
364
|
+
if conditions[1] == 'foo' || conditions[1] == 'bar' ||
|
|
365
|
+
(conditions[1] == 'bar-2' && conditions[2] != 2)
|
|
366
|
+
true
|
|
367
|
+
else
|
|
368
|
+
false
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
has_permalink :title
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
class PermalinkChangeableMockModel < BaseModel
|
|
376
|
+
def self.exists?(conditions)
|
|
377
|
+
if conditions[1] == 'foo'
|
|
378
|
+
true
|
|
379
|
+
else
|
|
380
|
+
false
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
has_permalink :title
|
|
385
|
+
|
|
386
|
+
def permalink_changed?
|
|
387
|
+
@permalink_changed
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def permalink_will_change!
|
|
391
|
+
@permalink_changed = true
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
class CommonMockModel < BaseModel
|
|
396
|
+
def self.exists?(conditions)
|
|
397
|
+
false # oh noes
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
has_permalink :title, :unique => false
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
class ScopedModel < BaseModel
|
|
404
|
+
def self.exists?(conditions)
|
|
405
|
+
if conditions[1] == 'foo' && conditions[2] != 5
|
|
406
|
+
true
|
|
407
|
+
else
|
|
408
|
+
false
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
has_permalink :title, :scope => :foo
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
class ScopedModelForNilScope < BaseModel
|
|
416
|
+
def self.exists?(conditions)
|
|
417
|
+
(conditions[0] == 'permalink = ? and foo IS NULL') ? (conditions[1] == 'ack') : false
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
has_permalink :title, :scope => :foo
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
class OverrideModel < BaseModel
|
|
424
|
+
has_permalink :title
|
|
425
|
+
|
|
426
|
+
def permalink
|
|
427
|
+
'not the permalink'
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
class ChangedWithoutUpdateModel < BaseModel
|
|
432
|
+
has_permalink :title
|
|
433
|
+
def title_changed?; true; end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
class ChangedWithUpdateModel < BaseModel
|
|
437
|
+
has_permalink :title, :update => true
|
|
438
|
+
def title_changed?; true; end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
class NoChangeModel < BaseModel
|
|
442
|
+
has_permalink :title, :update => true
|
|
443
|
+
def title_changed?; false; end
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
class IfProcConditionModel < BaseModel
|
|
447
|
+
has_permalink :title, :if => Proc.new { |obj| false }
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
class IfMethodConditionModel < BaseModel
|
|
451
|
+
has_permalink :title, :if => :false_method
|
|
452
|
+
|
|
453
|
+
def false_method; false; end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
class IfStringConditionModel < BaseModel
|
|
457
|
+
has_permalink :title, :if => 'false'
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
class UnlessProcConditionModel < BaseModel
|
|
461
|
+
has_permalink :title, :unless => Proc.new { |obj| false }
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
class UnlessMethodConditionModel < BaseModel
|
|
465
|
+
has_permalink :title, :unless => :false_method
|
|
466
|
+
|
|
467
|
+
def false_method; false; end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
class UnlessStringConditionModel < BaseModel
|
|
471
|
+
has_permalink :title, :unless => 'false'
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
class MockModelExtra < BaseModel
|
|
475
|
+
has_permalink [:title, :extra]
|
|
476
|
+
end
|