slugable 0.0.4 → 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.
- 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
|