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.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_support'
|
3
|
+
require 'slugable/version'
|
4
|
+
require 'slugable/has_slug'
|
5
|
+
require 'slugable/formatter/parameterize'
|
6
|
+
require 'slugable/slug_builder/flat'
|
7
|
+
require 'slugable/slug_builder/tree_ancestry'
|
8
|
+
require 'slugable/slug_builder/caching_tree_ancestry'
|
9
|
+
require 'slugable/cache_layer'
|
10
|
+
require 'slugable/railtie' if defined?(Rails)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Slugable
|
2
|
+
class CacheLayer
|
3
|
+
attr_reader :storage, :model
|
4
|
+
|
5
|
+
def initialize(storage, model)
|
6
|
+
@storage = storage
|
7
|
+
@model = model
|
8
|
+
end
|
9
|
+
|
10
|
+
def read_slug(slug_column, id)
|
11
|
+
storage.fetch(cache_key(slug_column, id)) { model.find(id).public_send(slug_column) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def update(slug_column, id, value)
|
15
|
+
storage.write(cache_key(slug_column, id), value)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def cache_key(slug_column, id)
|
21
|
+
"#{model.to_s.underscore}/#{slug_column}/#{id}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/slugable/has_slug.rb
CHANGED
@@ -1,4 +1,21 @@
|
|
1
1
|
module Slugable
|
2
|
+
Configuration = Struct.new(:formatter, :tree_cache_storage) do
|
3
|
+
def initialize(params)
|
4
|
+
params.each { |key, value| public_send(:"#{key}=", value) }
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.configuration
|
9
|
+
@configuration ||= Configuration.new(
|
10
|
+
formatter: Slugable::Formatter::Parameterize,
|
11
|
+
tree_cache_storage: nil
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.configure
|
16
|
+
yield configuration
|
17
|
+
end
|
18
|
+
|
2
19
|
module HasSlug
|
3
20
|
#
|
4
21
|
# USAGE
|
@@ -8,184 +25,98 @@ module Slugable
|
|
8
25
|
# it also generate method to_slug (depanding od :to param), which generate slug url for link_path
|
9
26
|
#
|
10
27
|
# has_slug # generate to_slug
|
11
|
-
# has_slug :
|
12
|
-
# has_slug :
|
13
|
-
# has_slug :
|
28
|
+
# has_slug from: :title # generate to_slug
|
29
|
+
# has_slug to: :seo_url # generate to_url
|
30
|
+
# has_slug from: :name, to: :slug # generate to_slug
|
14
31
|
#
|
15
|
-
def has_slug(options={})
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
# @@all[id] = send(:slug)
|
53
|
-
# end
|
54
|
-
code =<<-method
|
55
|
-
def update_my_#{to}_cache
|
56
|
-
@@all ||= {}
|
57
|
-
@@all[id] = send(:#{to})
|
58
|
-
end
|
59
|
-
method
|
60
|
-
class_eval(code)
|
61
|
-
|
62
|
-
# generate this
|
63
|
-
#
|
64
|
-
# def self.all_slugs
|
65
|
-
# slug_column = :slug
|
66
|
-
# @@all ||= self.all.map_to_hash{|slug_element| {slug_element.id => slug_element.send(slug_column)}}
|
67
|
-
# end
|
68
|
-
code =<<-method
|
69
|
-
def self.all_#{to}s
|
70
|
-
@@all ||= self.all.map_to_hash{|slug_element| {slug_element.id => slug_element.send(:#{to})}}
|
71
|
-
end
|
72
|
-
method
|
73
|
-
class_eval(code)
|
74
|
-
|
75
|
-
# generate this
|
76
|
-
#
|
77
|
-
# def self.clear_cached_slugs
|
78
|
-
# @@all = nil
|
79
|
-
# end
|
80
|
-
code =<<-method
|
81
|
-
def self.clear_cached_#{to}s
|
82
|
-
@@all = nil
|
83
|
-
end
|
84
|
-
method
|
85
|
-
class_eval(code)
|
86
|
-
|
87
|
-
# generate this
|
88
|
-
#
|
89
|
-
# def self.cached_slug(id)
|
90
|
-
# all_slugs[id]
|
91
|
-
# end
|
92
|
-
code =<<-method
|
93
|
-
def self.cached_#{to}(id)
|
94
|
-
all_#{to}s[id].to_s
|
32
|
+
def has_slug(options = {})
|
33
|
+
MethodBuilder.build(self, options)
|
34
|
+
end
|
35
|
+
|
36
|
+
class MethodBuilder
|
37
|
+
def self.default_options
|
38
|
+
{
|
39
|
+
from: :name,
|
40
|
+
to: :slug,
|
41
|
+
formatter: Slugable.configuration.formatter,
|
42
|
+
tree_cache_storage: Slugable.configuration.tree_cache_storage,
|
43
|
+
to_slug_builder: nil,
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.build(model, options)
|
48
|
+
options = options.reverse_merge(default_options)
|
49
|
+
|
50
|
+
from = options.delete(:from)
|
51
|
+
to = options.delete(:to)
|
52
|
+
formatter = options.delete(:formatter)
|
53
|
+
tree_cache_storage = options.delete(:tree_cache_storage)
|
54
|
+
to_slug_builder = options.delete(:to_slug_builder)
|
55
|
+
|
56
|
+
cache_layer = nil
|
57
|
+
if tree_cache_storage
|
58
|
+
cache_layer = Slugable::CacheLayer.new(tree_cache_storage, model)
|
59
|
+
builder_options = {
|
60
|
+
slug_column: to,
|
61
|
+
formatter: formatter,
|
62
|
+
cache: cache_layer
|
63
|
+
}
|
64
|
+
else
|
65
|
+
builder_options = {
|
66
|
+
slug_column: to,
|
67
|
+
formatter: formatter,
|
68
|
+
}
|
95
69
|
end
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
# slugs = if true
|
104
|
-
# path_ids.map{|id| self.class.cached_slug(id)}.compact.select{|i| i.size > 0 }
|
105
|
-
# else
|
106
|
-
# path.map{|record| record.send(:"slug")}.compact.select{|i| i.size > 0 }
|
107
|
-
# end
|
108
|
-
# slugs.empty? ? "" : slugs
|
109
|
-
# else
|
110
|
-
# send(:slug)
|
111
|
-
# end
|
112
|
-
# end
|
113
|
-
code =<<-method
|
114
|
-
def to_#{to}
|
115
|
-
if respond_to?(:path_ids)
|
116
|
-
slugs = if #{cache_tree}
|
117
|
-
path_ids.map{|id| self.class.cached_#{to}(id)}.compact.select{|i| i.size > 0 }
|
70
|
+
|
71
|
+
if to_slug_builder.nil?
|
72
|
+
builder = Slugable::SlugBuilder::Flat
|
73
|
+
|
74
|
+
if model.respond_to?(:ancestry_column)
|
75
|
+
if cache_layer
|
76
|
+
builder = Slugable::SlugBuilder::CachingTreeAncestry
|
118
77
|
else
|
119
|
-
|
78
|
+
builder = Slugable::SlugBuilder::TreeAncestry
|
120
79
|
end
|
121
|
-
slugs.empty? ? "" : slugs
|
122
|
-
else
|
123
|
-
send(:#{to})
|
124
80
|
end
|
81
|
+
to_slug_builder = builder.new(builder_options)
|
125
82
|
end
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
# else
|
141
|
-
# send(:slug_was)
|
142
|
-
# end
|
143
|
-
# end
|
144
|
-
code =<<-method
|
145
|
-
def to_#{to}_was
|
146
|
-
if respond_to?(:ancestry_was)
|
147
|
-
old_slugs = if #{cache_tree}
|
148
|
-
ancestry_was.to_s.split("/").map { |ancestor_id| self.class.cached_#{to}(ancestor_id.to_i) }
|
149
|
-
else
|
150
|
-
ancestry_was.to_s.split("/").map { |ancestor_id| self.class.find(ancestor_id).send(:#{to}) }
|
83
|
+
|
84
|
+
model.class_eval do
|
85
|
+
before_save :"prepare_slug_in_#{to}"
|
86
|
+
if cache_layer
|
87
|
+
after_save :"update_my_#{to}_cache"
|
88
|
+
end
|
89
|
+
|
90
|
+
define_method :"slug_builder_for_#{to}" do
|
91
|
+
to_slug_builder
|
92
|
+
end
|
93
|
+
|
94
|
+
define_method :"prepare_slug_in_#{to}" do
|
95
|
+
if public_send(to).blank? || formatter.call(public_send(to)).blank?
|
96
|
+
public_send(:"#{to}=", public_send(from))
|
151
97
|
end
|
152
|
-
|
153
|
-
else
|
154
|
-
send(:#{to}_was)
|
98
|
+
public_send(:"#{to}=", formatter.call(public_send(to)))
|
155
99
|
end
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
# end
|
173
|
-
# end
|
174
|
-
code =<<-method
|
175
|
-
def to_#{to}_will
|
176
|
-
if respond_to?(:ancestry)
|
177
|
-
new_slugs = if #{cache_tree}
|
178
|
-
ancestry.to_s.split("/").map { |ancestor_id| self.class.cached_#{to}(ancestor_id.to_i) }
|
179
|
-
else
|
180
|
-
ancestry.to_s.split("/").map { |ancestor_id| self.class.find(ancestor_id).send(:#{to}) }
|
181
|
-
end
|
182
|
-
new_slugs << send(:#{to}).send(:#{formatter})
|
183
|
-
else
|
184
|
-
send(:#{to}).send(:#{formatter})
|
100
|
+
|
101
|
+
define_method :"to_#{to}" do
|
102
|
+
public_send(:"slug_builder_for_#{to}").to_slug(self)
|
103
|
+
end
|
104
|
+
|
105
|
+
define_method :"to_#{to}_was" do
|
106
|
+
public_send(:"slug_builder_for_#{to}").to_slug_was(self)
|
107
|
+
end
|
108
|
+
|
109
|
+
define_method :"to_#{to}_will" do
|
110
|
+
public_send(:"slug_builder_for_#{to}").to_slug_will(self)
|
111
|
+
end
|
112
|
+
|
113
|
+
if cache_layer
|
114
|
+
define_method :"update_my_#{to}_cache" do
|
115
|
+
cache_layer.update(to, id, public_send(to))
|
185
116
|
end
|
186
117
|
end
|
187
|
-
|
188
|
-
|
118
|
+
end
|
119
|
+
end
|
189
120
|
end
|
190
121
|
end
|
191
122
|
end
|
data/lib/slugable/railtie.rb
CHANGED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Slugable
|
2
|
+
module SlugBuilder
|
3
|
+
class CachingTreeAncestry < TreeAncestry
|
4
|
+
attr_reader :cache
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
super
|
8
|
+
@cache = options.fetch(:cache)
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_slug(record)
|
12
|
+
record.path_ids.map{ |id| cache.read_slug(slug_column, id) }.select(&:present?)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Slugable
|
2
|
+
module SlugBuilder
|
3
|
+
class Flat
|
4
|
+
attr_reader :slug_column, :formatter
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
@slug_column = options.fetch(:slug_column)
|
8
|
+
@formatter = options.fetch(:formatter)
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_slug(record)
|
12
|
+
record.public_send(slug_column)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_slug_was(record)
|
16
|
+
record.public_send(:"#{slug_column}_was")
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_slug_will(record)
|
20
|
+
formatter.call(record.public_send(slug_column))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Slugable
|
2
|
+
module SlugBuilder
|
3
|
+
class TreeAncestry
|
4
|
+
attr_reader :slug_column, :formatter
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
@slug_column = options.fetch(:slug_column)
|
8
|
+
@formatter = options.fetch(:formatter)
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_slug(record)
|
12
|
+
record.path.map{ |path_record| path_record.public_send(slug_column) }.select(&:present?)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_slug_was(record)
|
16
|
+
old_slugs = record.ancestry_was.to_s.split('/').map { |ancestor_id| record.class.find(ancestor_id).public_send(slug_column) }
|
17
|
+
old_slugs << record.public_send(:"#{slug_column}_was")
|
18
|
+
old_slugs
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_slug_will(record)
|
22
|
+
new_slugs = record.ancestry.to_s.split('/').map { |ancestor_id| record.class.find(ancestor_id).public_send(slug_column) }
|
23
|
+
new_slugs << formatter.call(record.public_send(slug_column))
|
24
|
+
new_slugs
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|