slugable 0.0.4 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -1
- data/.rvmrc +1 -0
- data/.travis.yml +26 -0
- data/Appraisals +24 -0
- data/README.md +152 -80
- data/Rakefile +14 -1
- data/changelog.md +50 -0
- data/db/schema.rb +22 -15
- data/gemfiles/activerecord_3.2.gemfile +9 -0
- data/gemfiles/activerecord_4.0.gemfile +8 -0
- data/gemfiles/activerecord_4.1.gemfile +8 -0
- data/gemfiles/activerecord_4.2.gemfile +8 -0
- data/lib/slugable.rb +10 -6
- data/lib/slugable/cache_layer.rb +24 -0
- data/lib/slugable/formatter/parameterize.rb +9 -0
- data/lib/slugable/has_slug.rb +99 -168
- data/lib/slugable/railtie.rb +1 -1
- data/lib/slugable/slug_builder/caching_tree_ancestry.rb +16 -0
- data/lib/slugable/slug_builder/flat.rb +24 -0
- data/lib/slugable/slug_builder/tree_ancestry.rb +28 -0
- data/lib/slugable/version.rb +1 -1
- data/slugable.gemspec +14 -12
- data/spec/slugable/cache_layer_spec.rb +35 -0
- data/spec/slugable/has_slug_spec.rb +136 -199
- data/spec/slugable/slugable_builder/caching_tree_ancestry_spec.rb +38 -0
- data/spec/slugable/slugable_builder/flat_spec.rb +42 -0
- data/spec/slugable/slugable_builder/tree_ancestry_spec.rb +55 -0
- data/spec/spec_helper.rb +25 -5
- data/spec/support/hash_cache_storage.rb +27 -0
- data/spec/support/set_up_models.rb +27 -0
- data/spec/support/simple_to_slug.rb +13 -0
- metadata +108 -64
data/lib/slugable/version.rb
CHANGED
data/slugable.gemspec
CHANGED
@@ -4,25 +4,27 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
require 'slugable/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |gem|
|
7
|
-
gem.name =
|
7
|
+
gem.name = 'slugable'
|
8
8
|
gem.version = Slugable::VERSION
|
9
|
-
gem.authors = [
|
10
|
-
gem.email = [
|
9
|
+
gem.authors = ['Miroslav Hettes']
|
10
|
+
gem.email = ['hettes@webynamieru.sk']
|
11
11
|
gem.description = %q{Add dsl method for automatic storing seo friendly url in database column}
|
12
12
|
gem.summary = %q{Storing seo friendly url in column}
|
13
|
-
gem.homepage =
|
13
|
+
gem.homepage = 'https://github.com/mirrec/slugable'
|
14
14
|
|
15
15
|
gem.files = `git ls-files`.split($/)
|
16
16
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
-
gem.require_paths = [
|
18
|
+
gem.require_paths = ['lib']
|
19
19
|
|
20
|
-
gem.add_runtime_dependency
|
21
|
-
gem.add_runtime_dependency "activesupport", ">= 3.0"
|
22
|
-
gem.add_runtime_dependency "wnm_support", "~> 0.0.4"
|
20
|
+
gem.add_runtime_dependency 'activerecord', '>= 3.0', '< 5.0'
|
23
21
|
|
24
|
-
gem.add_development_dependency
|
25
|
-
gem.add_development_dependency
|
26
|
-
gem.add_development_dependency
|
27
|
-
gem.add_development_dependency
|
22
|
+
gem.add_development_dependency 'appraisal', '~> 2.1.0'
|
23
|
+
gem.add_development_dependency 'rspec', '~> 3.4.0'
|
24
|
+
gem.add_development_dependency 'rake', '~> 0.9.2.2'
|
25
|
+
gem.add_development_dependency 'sqlite3', '~> 1.3.11'
|
26
|
+
gem.add_development_dependency 'ancestry', '>= 1.3.0', '< 3.0'
|
27
|
+
gem.add_development_dependency 'pry'
|
28
|
+
gem.add_development_dependency 'simplecov'
|
29
|
+
gem.add_development_dependency 'codeclimate-test-reporter'
|
28
30
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'slugable/cache_layer'
|
2
|
+
require 'active_support/core_ext/string/inflections'
|
3
|
+
|
4
|
+
describe Slugable::CacheLayer do
|
5
|
+
MyModel = Class.new
|
6
|
+
|
7
|
+
let(:model) { MyModel }
|
8
|
+
let(:cache_storage) { double(:cache_storage) }
|
9
|
+
|
10
|
+
subject { Slugable::CacheLayer.new(cache_storage, model) }
|
11
|
+
|
12
|
+
describe '#read_slug' do
|
13
|
+
it 'use fetch for reading value for slug from cache storage' do
|
14
|
+
allow(cache_storage).to receive(:fetch).with('my_model/slug_column/1').and_return('hello')
|
15
|
+
|
16
|
+
expect(subject.read_slug(:slug_column, 1)).to eq 'hello'
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'passes block that will be executed if cache storage does not have given value yet' do
|
20
|
+
allow(model).to receive(:find).with(1).and_return(double(:record, slug_column: 'hello'))
|
21
|
+
|
22
|
+
allow(cache_storage).to receive(:fetch).with('my_model/slug_column/1').and_yield
|
23
|
+
|
24
|
+
expect(subject.read_slug(:slug_column, 1)).to eq 'hello'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#update' do
|
29
|
+
it 'writes new value to cache storage' do
|
30
|
+
expect(cache_storage).to receive(:write).with('my_model/slug_column/1', 'hello')
|
31
|
+
|
32
|
+
subject.update(:slug_column, 1, 'hello')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -1,280 +1,217 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/hash_cache_storage'
|
2
3
|
|
3
|
-
|
4
|
+
hash_cache_storage = HashCacheStorage.new
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
has_slug
|
9
|
-
end
|
10
|
-
|
11
|
-
class Page < ActiveRecord::Base
|
12
|
-
attr_accessible :title, :seo_url
|
13
|
-
|
14
|
-
has_slug :from => :title, :to => :seo_url
|
15
|
-
end
|
16
|
-
|
17
|
-
class Category < ActiveRecord::Base
|
18
|
-
attr_accessible :name, :slug
|
19
|
-
|
20
|
-
has_ancestry
|
21
|
-
has_slug
|
22
|
-
end
|
23
|
-
|
24
|
-
class TreeItem < ActiveRecord::Base
|
25
|
-
attr_accessible :name, :slug
|
26
|
-
|
27
|
-
has_ancestry
|
28
|
-
has_slug :cache_tree => false
|
29
|
-
end
|
30
|
-
|
31
|
-
class Product < ActiveRecord::Base
|
32
|
-
attr_accessible :name, :slug
|
33
|
-
|
34
|
-
has_slug :formatter => :my_formatter
|
6
|
+
Slugable.configure do |config|
|
7
|
+
config.tree_cache_storage = hash_cache_storage
|
35
8
|
end
|
36
9
|
|
10
|
+
require 'support/set_up_models'
|
37
11
|
|
38
12
|
describe Slugable::HasSlug do
|
39
13
|
before(:each) do
|
40
|
-
|
14
|
+
hash_cache_storage.clear
|
41
15
|
end
|
42
16
|
|
43
|
-
context
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
name = "my name is"
|
54
|
-
name.should_receive(:parameterize).and_return("my-name-is")
|
55
|
-
|
56
|
-
item = Item.create!(:name => name)
|
57
|
-
item.slug.should eq "my-name-is"
|
58
|
-
end
|
59
|
-
|
60
|
-
it "should fill in slug from attribute name if parameterize version of slug is blank" do
|
61
|
-
item = Item.create!(:name => "my name is", :slug => "/")
|
62
|
-
item.slug.should eq "my-name-is"
|
17
|
+
context 'method definitions' do
|
18
|
+
context 'default options' do
|
19
|
+
it 'creates all needed methods for slug' do
|
20
|
+
record = FlatItem.new
|
21
|
+
expect(record).to respond_to :slug_builder_for_slug
|
22
|
+
expect(record).to respond_to :prepare_slug_in_slug
|
23
|
+
expect(record).to respond_to :to_slug
|
24
|
+
expect(record).to respond_to :to_slug_was
|
25
|
+
expect(record).to respond_to :to_slug_will
|
26
|
+
end
|
63
27
|
end
|
64
28
|
|
65
|
-
|
66
|
-
|
67
|
-
|
29
|
+
context 'option with cache' do
|
30
|
+
it 'creates method that updates cache' do
|
31
|
+
record = TreeItem.new
|
32
|
+
expect(record).to respond_to :update_my_slug_cache
|
33
|
+
end
|
68
34
|
end
|
69
35
|
end
|
70
36
|
|
71
|
-
context
|
72
|
-
|
73
|
-
|
74
|
-
|
37
|
+
context 'callbacks' do
|
38
|
+
context 'default options' do
|
39
|
+
it 'fills in slug parameter from attribute apply a parameterize format to it' do
|
40
|
+
name = 'my name is'
|
75
41
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
it "should fill in slug parameter from attribute title and parametrize it" do
|
81
|
-
page = Page.create!(:title => "my name is")
|
82
|
-
page.seo_url.should eq "my-name-is"
|
83
|
-
end
|
42
|
+
item = FlatItem.create!(name: name)
|
43
|
+
expect(item.slug).to eq 'my-name-is'
|
44
|
+
end
|
84
45
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
46
|
+
it 'fills in slug from attribute name if parameterize version of slug is blank' do
|
47
|
+
item = FlatItem.create!(name: 'my name is', slug: '')
|
48
|
+
expect(item.slug).to eq 'my-name-is'
|
49
|
+
end
|
89
50
|
|
90
|
-
|
91
|
-
|
92
|
-
|
51
|
+
it 'does not change slug attribute if slug is present' do
|
52
|
+
item = FlatItem.create!(name: 'my name is', slug: 'my url')
|
53
|
+
expect(item.slug).to eq 'my-url'
|
54
|
+
end
|
93
55
|
end
|
94
56
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
product.slug.should eq "hello"
|
100
|
-
end
|
57
|
+
context 'given options' do
|
58
|
+
it 'creates fill_slug_from_title_to_seo_url' do
|
59
|
+
expect(FlatPage.new).to respond_to :prepare_slug_in_seo_url
|
60
|
+
end
|
101
61
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
tree_item.should_receive(:path).and_return([])
|
107
|
-
tree_item.to_slug
|
62
|
+
it 'fills in slug parameter from attribute title and parametrize it' do
|
63
|
+
page = FlatPage.create!(title: 'my name is')
|
64
|
+
expect(page.seo_url).to eq 'my-name-is'
|
65
|
+
end
|
108
66
|
|
109
|
-
|
110
|
-
|
111
|
-
|
67
|
+
it 'fills in slug parameter and use custom formatter' do
|
68
|
+
product = FlatProduct.create!(name: 'my name is', slug: 'product')
|
69
|
+
expect(product.slug).to eq 'hello-all-the-time'
|
70
|
+
end
|
112
71
|
end
|
113
72
|
end
|
114
73
|
|
115
|
-
describe
|
116
|
-
context
|
117
|
-
it
|
118
|
-
|
74
|
+
describe '#to_slug' do
|
75
|
+
context 'default options' do
|
76
|
+
it 'defines to_slug method' do
|
77
|
+
expect(FlatItem.new).to respond_to :to_slug
|
119
78
|
end
|
120
79
|
|
121
|
-
it
|
122
|
-
item =
|
123
|
-
item.to_slug.
|
80
|
+
it 'returns slug in string' do
|
81
|
+
item = FlatItem.create!(name: 'my name is', slug: 'my-url')
|
82
|
+
expect(item.to_slug).to eq 'my-url'
|
124
83
|
end
|
125
84
|
end
|
126
85
|
|
127
|
-
context
|
128
|
-
it
|
129
|
-
|
86
|
+
context 'given options' do
|
87
|
+
it 'defines method to_seo_url' do
|
88
|
+
expect(FlatPage.new).to respond_to :to_seo_url
|
130
89
|
end
|
131
90
|
|
132
|
-
it
|
133
|
-
page =
|
134
|
-
page.to_seo_url.
|
91
|
+
it 'returns slug in string' do
|
92
|
+
page = FlatPage.create!(title: 'my name is', seo_url: 'my-url')
|
93
|
+
expect(page.to_seo_url).to eq 'my-url'
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'returns result from custom to_slug_builder' do
|
97
|
+
news = FlatNews.create!(name: 'news')
|
98
|
+
expect(news.to_slug).to eq "to_slug_#{news.id}"
|
135
99
|
end
|
136
100
|
end
|
137
101
|
|
138
|
-
context
|
139
|
-
it
|
140
|
-
root =
|
141
|
-
child =
|
102
|
+
context 'tree models' do
|
103
|
+
it 'returns array of slugs' do
|
104
|
+
root = TreeCategory.create!(name: 'root', slug: 'root')
|
105
|
+
child = TreeCategory.new(name: 'child', slug: 'child')
|
142
106
|
child.parent = root
|
143
107
|
child.save!
|
144
108
|
|
145
|
-
child.to_slug.
|
109
|
+
expect(child.to_slug).to eq ['root', 'child']
|
146
110
|
end
|
147
111
|
|
148
|
-
it
|
149
|
-
root =
|
150
|
-
child =
|
112
|
+
it 'skips nil values from slug path' do
|
113
|
+
root = TreeCategory.create!(name: 'root', slug: 'root')
|
114
|
+
child = TreeCategory.new(name: 'child', slug: 'child')
|
151
115
|
child.parent = root
|
152
116
|
child.save!
|
153
117
|
|
154
|
-
|
155
|
-
|
118
|
+
TreeCategory.where(id: root.id).update_all(slug: nil)
|
119
|
+
hash_cache_storage.clear
|
120
|
+
|
121
|
+
expect(child.to_slug).to eq ['child']
|
122
|
+
end
|
156
123
|
|
157
|
-
|
124
|
+
it 'returns correct results also with caching support' do
|
125
|
+
child = TreeItem.create!(slug: 'child', parent: TreeItem.create!(slug: 'root'))
|
126
|
+
expect(child.to_slug).to eq ['root', 'child']
|
127
|
+
expect(child.to_slug).to eq ['root', 'child']
|
158
128
|
end
|
159
129
|
end
|
160
130
|
end
|
161
131
|
|
162
|
-
describe
|
163
|
-
context
|
164
|
-
it
|
165
|
-
|
132
|
+
describe '#to_slug_was' do
|
133
|
+
context 'default options' do
|
134
|
+
it 'defines method to_slug_was' do
|
135
|
+
expect(FlatItem.new).to respond_to :to_slug_was
|
166
136
|
end
|
167
137
|
|
168
|
-
it
|
169
|
-
item =
|
170
|
-
item.slug =
|
171
|
-
item.to_slug_was.
|
138
|
+
it 'returns old slug in string' do
|
139
|
+
item = FlatItem.create!(name: 'my name is', slug: 'my-url')
|
140
|
+
item.slug = 'new-slug'
|
141
|
+
expect(item.to_slug_was).to eq 'my-url'
|
172
142
|
end
|
173
143
|
end
|
174
144
|
|
175
|
-
context
|
176
|
-
it
|
177
|
-
|
145
|
+
context 'given options' do
|
146
|
+
it 'defines method to_seo_url_was' do
|
147
|
+
expect(FlatPage.new).to respond_to :to_seo_url_was
|
178
148
|
end
|
179
149
|
|
180
|
-
it
|
181
|
-
page =
|
182
|
-
page.seo_url =
|
183
|
-
page.to_seo_url_was.
|
150
|
+
it 'returns future slug in string' do
|
151
|
+
page = FlatPage.create!(title: 'my name is', seo_url: 'my-url')
|
152
|
+
page.seo_url = 'hello-world'
|
153
|
+
expect(page.to_seo_url_was).to eq 'my-url'
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'returns result from custom to_slug_builder' do
|
157
|
+
news = FlatNews.create!(name: 'news')
|
158
|
+
expect(news.to_slug_was).to eq "to_slug_was_#{news.id}"
|
184
159
|
end
|
185
160
|
end
|
186
161
|
|
187
|
-
context
|
188
|
-
it
|
189
|
-
root =
|
190
|
-
child =
|
162
|
+
context 'tree models' do
|
163
|
+
it 'returns array of old slugs' do
|
164
|
+
root = TreeCategory.create!(name: 'root', slug: 'root')
|
165
|
+
child = TreeCategory.new(name: 'child', slug: 'child')
|
191
166
|
child.save!
|
192
167
|
|
193
168
|
child.parent = root
|
194
|
-
child.slug =
|
195
|
-
child.to_slug_was.
|
169
|
+
child.slug = 'moved'
|
170
|
+
expect(child.to_slug_was).to eq ['child']
|
196
171
|
end
|
197
172
|
end
|
198
173
|
end
|
199
174
|
|
200
|
-
describe
|
201
|
-
context
|
202
|
-
it
|
203
|
-
|
175
|
+
describe '#to_slug_will' do
|
176
|
+
context 'default options' do
|
177
|
+
it 'defines method to_slug_will' do
|
178
|
+
expect(FlatItem.new).to respond_to :to_slug_will
|
204
179
|
end
|
205
180
|
|
206
|
-
it
|
207
|
-
item =
|
208
|
-
item.slug =
|
209
|
-
item.to_slug_will.
|
181
|
+
it 'returns future slug in string' do
|
182
|
+
item = FlatItem.create!(name: 'my name is', slug: 'my-url')
|
183
|
+
item.slug = 'new slug'
|
184
|
+
expect(item.to_slug_will).to eq 'new-slug'
|
210
185
|
end
|
211
186
|
end
|
212
187
|
|
213
|
-
context
|
214
|
-
it
|
215
|
-
|
188
|
+
context 'given options' do
|
189
|
+
it 'defines method to_seo_url_will' do
|
190
|
+
expect(FlatPage.new).to respond_to :to_seo_url_will
|
216
191
|
end
|
217
192
|
|
218
|
-
it
|
219
|
-
page =
|
220
|
-
page.seo_url =
|
221
|
-
page.to_seo_url_will.
|
193
|
+
it 'returns slug in string' do
|
194
|
+
page = FlatPage.create!(title: 'my name is', seo_url: 'my-url')
|
195
|
+
page.seo_url = 'hello world'
|
196
|
+
expect(page.to_seo_url_will).to eq 'hello-world'
|
222
197
|
end
|
223
|
-
end
|
224
198
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
child = Category.new(:name => "child", :slug => "child")
|
229
|
-
child.save!
|
230
|
-
|
231
|
-
child.parent = root
|
232
|
-
child.slug = "move d"
|
233
|
-
child.to_slug_will.should eq ["root", "move-d"]
|
199
|
+
it 'returns result from custom to_slug_builder' do
|
200
|
+
news = FlatNews.create!(name: 'news')
|
201
|
+
expect(news.to_slug_will).to eq "to_slug_will_#{news.id}"
|
234
202
|
end
|
235
203
|
end
|
236
|
-
end
|
237
204
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
end
|
243
|
-
|
244
|
-
it "should return all slugs in hash where key is id and value is slug by itself" do
|
245
|
-
root = Category.create!(:name => "root", :slug => "root")
|
246
|
-
child = Category.new(:name => "child", :slug => "child")
|
247
|
-
child.parent = root
|
205
|
+
context 'tree models' do
|
206
|
+
it 'returns array of slugs' do
|
207
|
+
root = TreeCategory.create!(name: 'root', slug: 'root')
|
208
|
+
child = TreeCategory.new(name: 'child', slug: 'child')
|
248
209
|
child.save!
|
249
210
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
it "should update slug cache after save" do
|
254
|
-
root = Category.create!(:name => "root", :slug => "root")
|
255
|
-
Category.all_slugs.should eq({root.id => "root"})
|
256
|
-
|
257
|
-
child = Category.new(:name => "child", :slug => "child")
|
258
|
-
child.save! # force save
|
259
|
-
Category.all_slugs.should eq({root.id => "root", child.id => "child"})
|
260
|
-
|
261
|
-
child.slug = "updated-child"
|
262
|
-
child.save # standard save
|
263
|
-
Category.all_slugs.should eq({root.id => "root", child.id => "updated-child"})
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
describe "clear_cached_slugs" do
|
268
|
-
it "should clear cache for slug" do
|
269
|
-
root = Category.create!(:name => "root", :slug => "root")
|
270
|
-
Category.all_slugs.should eq({root.id => "root"})
|
271
|
-
|
272
|
-
root.destroy
|
273
|
-
Category.all_slugs.should eq({root.id => "root"})
|
274
|
-
|
275
|
-
Category.clear_cached_slugs
|
276
|
-
Category.all_slugs.should eq({})
|
211
|
+
child.parent = root
|
212
|
+
child.slug = 'move d'
|
213
|
+
expect(child.to_slug_will).to eq ['root', 'move-d']
|
277
214
|
end
|
278
215
|
end
|
279
216
|
end
|
280
|
-
end
|
217
|
+
end
|