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.
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 3.2.1"
6
+ gem "ancestry", "~> 1.3.0"
7
+ gem "test-unit", "~> 3.0"
8
+
9
+ gemspec :path => "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 4.0.1"
6
+ gem "ancestry", "~> 2.1.0"
7
+
8
+ gemspec :path => "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 4.1.1"
6
+ gem "ancestry", "~> 2.1.0"
7
+
8
+ gemspec :path => "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 4.2.1"
6
+ gem "ancestry", "~> 2.1.0"
7
+
8
+ gemspec :path => "../"
@@ -1,6 +1,10 @@
1
- require "active_record"
2
- require "active_support"
3
- require "wnm_support"
4
- require "slugable/version"
5
- require "slugable/has_slug"
6
- require "slugable/railtie" if defined?(Rails)
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
@@ -0,0 +1,9 @@
1
+ module Slugable
2
+ module Formatter
3
+ class Parameterize
4
+ def self.call(string)
5
+ string.parameterize
6
+ end
7
+ end
8
+ end
9
+ end
@@ -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 :from => :title # generate to_slug
12
- # has_slug :to => :seo_url # generate to_url
13
- # has_slug :from => :name, :to => :slug # generate to_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
- defaults = {:from => :name, :to => :slug, :formatter => :parameterize, :cache_tree => true}
17
- options.reverse_merge!(defaults)
18
- from = options.delete(:from)
19
- to = options.delete(:to)
20
- formatter = options.delete(:formatter)
21
- cache_tree = options.delete(:cache_tree)
22
- before_save :"fill_slug_from_#{from}_to_#{to}", :"format_slug_from_#{from}_to_#{to}"
23
- after_save :"update_my_#{to}_cache"
24
-
25
- # generate this
26
- #
27
- # def fill_slug
28
- # self.slug = name if slug.blank? || slug.parameterize.blank?
29
- # end
30
- code =<<-method
31
- def fill_slug_from_#{from}_to_#{to}
32
- self.#{to} = #{from} if #{to}.blank? || #{to}.parameterize.blank?
33
- end
34
- method
35
- class_eval(code)
36
-
37
- # generate this
38
- #
39
- # def format_slug
40
- # self.slug = slug.parameterize
41
- # end
42
- code =<<-method
43
- def format_slug_from_#{from}_to_#{to}
44
- self.#{to} = #{to}.send(:#{formatter})
45
- end
46
- method
47
- class_eval(code)
48
-
49
- # generate this
50
- # def update_my_slug_cache
51
- # @@all ||= {}
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
- method
97
- class_eval(code)
98
-
99
- # generate this
100
- #
101
- # def to_slug
102
- # if respond_to?(:path_ids)
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
- path.map{|record| record.send(:"#{to}")}.compact.select{|i| i.size > 0 }
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
- method
127
- class_eval(code)
128
-
129
-
130
- # generate this
131
- #
132
- # def to_slug_was
133
- # if respond_to?(:ancestry_was)
134
- # old_slugs = if true
135
- # ancestry_was.to_s.split("/").map { |ancestor_id| self.class.cached_slug(ancestor_id.to_i) }
136
- # else
137
- # ancestry_was.to_s.split("/").map { |ancestor_id| self.class.find(ancestor_id).send(:slug) }
138
- # end
139
- # old_slugs << send(:slug_was)
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
- old_slugs << send(:#{to}_was)
153
- else
154
- send(:#{to}_was)
98
+ public_send(:"#{to}=", formatter.call(public_send(to)))
155
99
  end
156
- end
157
- method
158
- class_eval(code)
159
-
160
- # generate this
161
- #
162
- # def to_slug_will
163
- # if respond_to?(:ancestry)
164
- # old_slugs = if true
165
- # ancestry.to_s.split("/").map { |ancestor_id| self.class.cached_slug(ancestor_id.to_i) }
166
- # else
167
- # ancestry.to_s.split("/").map { |ancestor_id| self.class.find(ancestor_id).send(:slug) }
168
- # end
169
- # old_slugs << send(:slug)
170
- # else
171
- # send(:slug_was)
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
- method
188
- class_eval(code)
118
+ end
119
+ end
189
120
  end
190
121
  end
191
122
  end
@@ -1,6 +1,6 @@
1
1
  module Slugable
2
2
  class Railtie < Rails::Railtie
3
- initializer "slugable.active_record_ext" do
3
+ initializer 'slugable.active_record_ext' do
4
4
  ActiveSupport.on_load(:active_record) do
5
5
  ActiveRecord::Base.send :extend, Slugable::HasSlug
6
6
  end
@@ -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