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.
Files changed (185) hide show
  1. data/README.markdown +64 -0
  2. data/lib/data/x00.yml +257 -0
  3. data/lib/data/x01.yml +257 -0
  4. data/lib/data/x02.yml +256 -0
  5. data/lib/data/x03.yml +256 -0
  6. data/lib/data/x04.yml +256 -0
  7. data/lib/data/x05.yml +256 -0
  8. data/lib/data/x06.yml +256 -0
  9. data/lib/data/x07.yml +256 -0
  10. data/lib/data/x09.yml +256 -0
  11. data/lib/data/x0a.yml +256 -0
  12. data/lib/data/x0b.yml +256 -0
  13. data/lib/data/x0c.yml +256 -0
  14. data/lib/data/x0d.yml +256 -0
  15. data/lib/data/x0e.yml +256 -0
  16. data/lib/data/x0f.yml +256 -0
  17. data/lib/data/x10.yml +256 -0
  18. data/lib/data/x11.yml +256 -0
  19. data/lib/data/x12.yml +257 -0
  20. data/lib/data/x13.yml +256 -0
  21. data/lib/data/x14.yml +257 -0
  22. data/lib/data/x15.yml +257 -0
  23. data/lib/data/x16.yml +256 -0
  24. data/lib/data/x17.yml +256 -0
  25. data/lib/data/x18.yml +256 -0
  26. data/lib/data/x1e.yml +256 -0
  27. data/lib/data/x1f.yml +256 -0
  28. data/lib/data/x20.yml +256 -0
  29. data/lib/data/x21.yml +256 -0
  30. data/lib/data/x22.yml +256 -0
  31. data/lib/data/x23.yml +256 -0
  32. data/lib/data/x24.yml +256 -0
  33. data/lib/data/x25.yml +256 -0
  34. data/lib/data/x26.yml +256 -0
  35. data/lib/data/x27.yml +256 -0
  36. data/lib/data/x28.yml +257 -0
  37. data/lib/data/x2e.yml +256 -0
  38. data/lib/data/x2f.yml +256 -0
  39. data/lib/data/x30.yml +256 -0
  40. data/lib/data/x31.yml +256 -0
  41. data/lib/data/x32.yml +256 -0
  42. data/lib/data/x33.yml +256 -0
  43. data/lib/data/x4d.yml +256 -0
  44. data/lib/data/x4e.yml +257 -0
  45. data/lib/data/x4f.yml +257 -0
  46. data/lib/data/x50.yml +257 -0
  47. data/lib/data/x51.yml +257 -0
  48. data/lib/data/x52.yml +257 -0
  49. data/lib/data/x53.yml +257 -0
  50. data/lib/data/x54.yml +257 -0
  51. data/lib/data/x55.yml +257 -0
  52. data/lib/data/x56.yml +257 -0
  53. data/lib/data/x57.yml +257 -0
  54. data/lib/data/x58.yml +257 -0
  55. data/lib/data/x59.yml +257 -0
  56. data/lib/data/x5a.yml +257 -0
  57. data/lib/data/x5b.yml +257 -0
  58. data/lib/data/x5c.yml +257 -0
  59. data/lib/data/x5d.yml +257 -0
  60. data/lib/data/x5e.yml +257 -0
  61. data/lib/data/x5f.yml +257 -0
  62. data/lib/data/x60.yml +257 -0
  63. data/lib/data/x61.yml +257 -0
  64. data/lib/data/x62.yml +257 -0
  65. data/lib/data/x63.yml +257 -0
  66. data/lib/data/x64.yml +257 -0
  67. data/lib/data/x65.yml +257 -0
  68. data/lib/data/x66.yml +257 -0
  69. data/lib/data/x67.yml +257 -0
  70. data/lib/data/x68.yml +257 -0
  71. data/lib/data/x69.yml +257 -0
  72. data/lib/data/x6a.yml +257 -0
  73. data/lib/data/x6b.yml +257 -0
  74. data/lib/data/x6c.yml +257 -0
  75. data/lib/data/x6d.yml +257 -0
  76. data/lib/data/x6e.yml +257 -0
  77. data/lib/data/x6f.yml +257 -0
  78. data/lib/data/x70.yml +257 -0
  79. data/lib/data/x71.yml +257 -0
  80. data/lib/data/x72.yml +257 -0
  81. data/lib/data/x73.yml +257 -0
  82. data/lib/data/x74.yml +257 -0
  83. data/lib/data/x75.yml +257 -0
  84. data/lib/data/x76.yml +257 -0
  85. data/lib/data/x77.yml +257 -0
  86. data/lib/data/x78.yml +257 -0
  87. data/lib/data/x79.yml +257 -0
  88. data/lib/data/x7a.yml +257 -0
  89. data/lib/data/x7b.yml +257 -0
  90. data/lib/data/x7c.yml +257 -0
  91. data/lib/data/x7d.yml +257 -0
  92. data/lib/data/x7e.yml +257 -0
  93. data/lib/data/x7f.yml +257 -0
  94. data/lib/data/x80.yml +257 -0
  95. data/lib/data/x81.yml +257 -0
  96. data/lib/data/x82.yml +257 -0
  97. data/lib/data/x83.yml +257 -0
  98. data/lib/data/x84.yml +257 -0
  99. data/lib/data/x85.yml +257 -0
  100. data/lib/data/x86.yml +257 -0
  101. data/lib/data/x87.yml +257 -0
  102. data/lib/data/x88.yml +257 -0
  103. data/lib/data/x89.yml +257 -0
  104. data/lib/data/x8a.yml +257 -0
  105. data/lib/data/x8b.yml +257 -0
  106. data/lib/data/x8c.yml +257 -0
  107. data/lib/data/x8d.yml +257 -0
  108. data/lib/data/x8e.yml +257 -0
  109. data/lib/data/x8f.yml +257 -0
  110. data/lib/data/x90.yml +257 -0
  111. data/lib/data/x91.yml +257 -0
  112. data/lib/data/x92.yml +257 -0
  113. data/lib/data/x93.yml +257 -0
  114. data/lib/data/x94.yml +257 -0
  115. data/lib/data/x95.yml +257 -0
  116. data/lib/data/x96.yml +257 -0
  117. data/lib/data/x97.yml +257 -0
  118. data/lib/data/x98.yml +257 -0
  119. data/lib/data/x99.yml +257 -0
  120. data/lib/data/x9a.yml +257 -0
  121. data/lib/data/x9b.yml +257 -0
  122. data/lib/data/x9c.yml +257 -0
  123. data/lib/data/x9d.yml +257 -0
  124. data/lib/data/x9e.yml +257 -0
  125. data/lib/data/x9f.yml +256 -0
  126. data/lib/data/xa0.yml +257 -0
  127. data/lib/data/xa1.yml +257 -0
  128. data/lib/data/xa2.yml +257 -0
  129. data/lib/data/xa3.yml +257 -0
  130. data/lib/data/xa4.yml +256 -0
  131. data/lib/data/xac.yml +257 -0
  132. data/lib/data/xad.yml +257 -0
  133. data/lib/data/xae.yml +257 -0
  134. data/lib/data/xaf.yml +257 -0
  135. data/lib/data/xb0.yml +257 -0
  136. data/lib/data/xb1.yml +257 -0
  137. data/lib/data/xb2.yml +257 -0
  138. data/lib/data/xb3.yml +257 -0
  139. data/lib/data/xb4.yml +257 -0
  140. data/lib/data/xb5.yml +257 -0
  141. data/lib/data/xb6.yml +257 -0
  142. data/lib/data/xb7.yml +257 -0
  143. data/lib/data/xb8.yml +257 -0
  144. data/lib/data/xb9.yml +257 -0
  145. data/lib/data/xba.yml +257 -0
  146. data/lib/data/xbb.yml +257 -0
  147. data/lib/data/xbc.yml +257 -0
  148. data/lib/data/xbd.yml +257 -0
  149. data/lib/data/xbe.yml +257 -0
  150. data/lib/data/xbf.yml +257 -0
  151. data/lib/data/xc0.yml +257 -0
  152. data/lib/data/xc1.yml +257 -0
  153. data/lib/data/xc2.yml +257 -0
  154. data/lib/data/xc3.yml +257 -0
  155. data/lib/data/xc4.yml +257 -0
  156. data/lib/data/xc5.yml +257 -0
  157. data/lib/data/xc6.yml +257 -0
  158. data/lib/data/xc7.yml +257 -0
  159. data/lib/data/xc8.yml +257 -0
  160. data/lib/data/xc9.yml +257 -0
  161. data/lib/data/xca.yml +257 -0
  162. data/lib/data/xcb.yml +257 -0
  163. data/lib/data/xcc.yml +257 -0
  164. data/lib/data/xcd.yml +257 -0
  165. data/lib/data/xce.yml +257 -0
  166. data/lib/data/xcf.yml +257 -0
  167. data/lib/data/xd0.yml +257 -0
  168. data/lib/data/xd1.yml +257 -0
  169. data/lib/data/xd2.yml +257 -0
  170. data/lib/data/xd3.yml +257 -0
  171. data/lib/data/xd4.yml +257 -0
  172. data/lib/data/xd5.yml +257 -0
  173. data/lib/data/xd6.yml +257 -0
  174. data/lib/data/xd7.yml +256 -0
  175. data/lib/data/xf9.yml +257 -0
  176. data/lib/data/xfa.yml +256 -0
  177. data/lib/data/xfb.yml +257 -0
  178. data/lib/data/xfc.yml +257 -0
  179. data/lib/data/xfd.yml +256 -0
  180. data/lib/data/xfe.yml +257 -0
  181. data/lib/data/xff.yml +257 -0
  182. data/lib/permalink_fu.rb +176 -0
  183. data/test/permalink_fu_test.rb +476 -0
  184. data/test/test_helper.rb +13 -0
  185. metadata +251 -0
@@ -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