much-slug 0.0.1 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
- ---
2
- SHA256:
3
- metadata.gz: d78584c3013f0453ca63e2a99186b75ff087808b0c650e7db8e0d9bd50a39d66
4
- data.tar.gz: e5cf29930b1eedfa95830323de1f5191c938126a456ab655403d8e229048cc85
5
- SHA512:
6
- metadata.gz: aa0cc8ac3994c4932ca5f02d3a0fb540df1844d53da904daea826759cd35011508226eafcdefe2329e6c4ea9b53a9b75cf17223f8324f5c55e7d86c8f3291d88
7
- data.tar.gz: b3d55e93e545e620cd4ccd1af326b9d1354b95a97d02e5b819abd31de4f7c7fe8e7588023bc2ee6abb4fe8b9bc73ea2d83de6c620af89153833e20684b534e1a
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6372c10543d9dba9890742245b1166dd1465626c4f997c5fdf832edf10f62d9e
4
+ data.tar.gz: 05071c2003b22836474735f9f0fbb2cfbcb3daabdf197a00e62aba9f51300afb
5
+ SHA512:
6
+ metadata.gz: 15d9851869d05018c136b65e0d11d91e834ae20af6fe8149b790aeee32607c2a01d2abd24c1f2f987b01c053556d33590768e9a58753163238c856fd1b458ad5
7
+ data.tar.gz: 91da2c4a9735b817a50238d9ce96842280d9a119c638acbe9188a73a3a7f5708f8af2fa942821c1c6de4ebd056e5806d0582cbb28db920ce77aa9b6bb0aa959a
data/Gemfile CHANGED
@@ -1,5 +1,7 @@
1
1
  source "https://rubygems.org"
2
2
 
3
+ ruby "~> 2.4"
4
+
3
5
  gemspec
4
6
 
5
- gem 'pry', "~> 0.9.0"
7
+ gem "pry", "~> 0.12.2"
data/README.md CHANGED
@@ -4,7 +4,180 @@ Friendly, human-readable identifiers for database records.
4
4
 
5
5
  ## Usage
6
6
 
7
- TODO: Write code samples and usage instructions here
7
+ MuchSlug creates derived slug values on database records. Typically this means deriving the slug value from record attributes and syncing/caching that value in a dedicated field on the record.
8
+
9
+ ### ActiveRecord
10
+
11
+ Given a `:slug` field on a record:
12
+
13
+ ```ruby
14
+ class AddSlugToProjects < ActiveRecord::Migration[5.2]
15
+ def change
16
+ add_column(:projects, :slug, :string, index: { unique: true })
17
+ end
18
+ end
19
+ ```
20
+
21
+ Mix-in `MuchSlug::ActiveRecord` and configure:
22
+
23
+ ```ruby
24
+ require "much-slug/activerecord"
25
+
26
+ class ProjectRecord < ApplicationRecord
27
+ self.table_name = "projects"
28
+
29
+ include MuchSlug::ActiveRecord
30
+ has_slug(
31
+ source: -> { "#{self.id}-#{self.name}" },
32
+ )
33
+
34
+ # ...
35
+ end
36
+ ```
37
+
38
+ The record will save slug values as it is saved:
39
+
40
+ ```ruby
41
+ project = ProjectRecord.last
42
+ project.id # => 123
43
+ project.name # => "Sprockets 2.0"
44
+ project.slug # => nil
45
+
46
+ # slug values are updated late-bound after record saves
47
+ project.save
48
+ project.slug # => "123-Sprockets-2-0"
49
+
50
+ project.name = "Widgets For Me"
51
+ project.slug # => "123-Sprockets-2-0"
52
+ project.save
53
+ project.slug # => "123-Widgets-For-Me"
54
+
55
+ # new Projects also have their slugs assigned once they are saved
56
+ project = Project.new(name: "Do The Things")
57
+ project.slug # => nil
58
+ project.save!
59
+ project.slug # => "124-Do-The-Things"
60
+ ```
61
+
62
+ ### Notes
63
+
64
+ #### Attribute
65
+
66
+ By default, the record attribute for a slug is `"slug"`. You can override this when configuring slugs:
67
+
68
+ ```ruby
69
+ require "much-slug/activerecord"
70
+
71
+ class ProjectRecord < ApplicationRecord
72
+ self.table_name = "projects"
73
+
74
+ include MuchSlug::ActiveRecord
75
+ has_slug(
76
+ source: -> { "#{self.id}-#{self.name}" },
77
+ )
78
+ has_slug(
79
+ attribute: :full_slug
80
+ source: -> { "#{self.id}-#{self.full_name}" },
81
+ )
82
+
83
+ # ...
84
+ end
85
+ ```
86
+
87
+ #### Preprocessor
88
+
89
+ By default, MuchSlug doesn't pre-process the slug value source before generating the slug value. You can specify a custom pre-processor by passing any Proc-like object:
90
+
91
+ ```ruby
92
+ require "much-slug/activerecord"
93
+
94
+ class ProjectRecord < ApplicationRecord
95
+ self.table_name = "projects"
96
+
97
+ include MuchSlug::ActiveRecord
98
+ has_slug(
99
+ source: -> { "#{self.id}-#{self.name}" },
100
+ preprocessor: :downcase
101
+ )
102
+ has_slug(
103
+ attribute: :full_slug
104
+ source: -> { "#{self.id}-#{self.full_name}" },
105
+ preprocessor: -> { |source_value| source_value[0..30] }
106
+ )
107
+
108
+ # ...
109
+ end
110
+ ```
111
+
112
+ #### Separator
113
+
114
+ MuchSlug replaces any non-word characters with a separator. This helps make slugs URL-friendly. By default, MuchSlug uses `"-"` for the separator. You can specify a custom separator value when configuring slugs:
115
+
116
+ ```ruby
117
+ require "much-slug/activerecord"
118
+
119
+ class ProjectRecord < ApplicationRecord
120
+ self.table_name = "projects"
121
+
122
+ include MuchSlug::ActiveRecord
123
+ has_slug(
124
+ source: -> { "#{self.id}.#{self.name}" },
125
+ separator: "."
126
+ )
127
+
128
+ # ...
129
+ end
130
+
131
+ project = ProjectRecord.last
132
+ project.id # => 123
133
+ project.name # => "Sprockets 2.0"
134
+ project.save
135
+ project.slug # => "123.Sprockets.2.0"
136
+ ```
137
+
138
+ #### Allowing Underscores
139
+
140
+ By default, MuchSlug doesn't allow underscores in source values and treats them like non-word characters. This means it replaces underscores with the separator. You can override this to allow underscores when configuring slugs:
141
+
142
+ ```ruby
143
+ require "much-slug/activerecord"
144
+
145
+ class ProjectRecord < ApplicationRecord
146
+ self.table_name = "projects"
147
+
148
+ include MuchSlug::ActiveRecord
149
+ has_slug(
150
+ source: -> { "#{self.id}-#{self.name}" }
151
+ )
152
+ has_slug(
153
+ attribute: :full_slug
154
+ source: -> { "#{self.id}-#{self.full_name}" },
155
+ allow_underscores: true
156
+
157
+ # ...
158
+ end
159
+
160
+ project = ProjectRecord.last
161
+ project.id # => 123
162
+ project.name # => "SP_2.0"
163
+ project.full_name # => "Sprockets_2.0"
164
+ project.save
165
+ project.slug # => "123-SP-2-0"
166
+ project.full_slug # => "123-Sprockets_2-0"
167
+ ```
168
+
169
+ #### Manual Slug Updates
170
+
171
+ Slugs are updated anytime a record is saved with changes that affect the slug. If you want an explicit, intention-revealing way to update the slugs manually, use `MuchSlug.update_slugs`:
172
+
173
+ ```ruby
174
+ project = ProjectRecord.last
175
+ project.id # => 123
176
+ project.name # => "Sprockets 2.0"
177
+
178
+ MuchSlug.update_slugs(project)
179
+ project.slug # => "123-Sprockets-2-0"
180
+ ```
8
181
 
9
182
  ## Installation
10
183
 
@@ -1,4 +1,52 @@
1
1
  require "much-slug/version"
2
+ require "much-slug/has_slug_registry"
3
+ require "much-slug/slug"
2
4
 
3
5
  module MuchSlug
6
+ def self.default_attribute
7
+ "slug"
8
+ end
9
+
10
+ def self.default_preprocessor
11
+ :to_s
12
+ end
13
+
14
+ def self.default_separator
15
+ "-".freeze
16
+ end
17
+
18
+ def self.default_allow_underscores
19
+ false
20
+ end
21
+
22
+ def self.update_slugs(record)
23
+ record.send("much_slug_has_slug_update_slug_values")
24
+ true
25
+ end
26
+
27
+ def self.has_slug_changed_slug_values(record_instance)
28
+ record_instance.class.much_slug_has_slug_registry.each do |attribute, entry|
29
+ # ArgumentError: no receiver given` raised when calling `instance_exec`
30
+ # on non-lambda Procs, specifically e.g :downcase.to_proc.
31
+ # Can't call `instance_eval` on stabby lambdas b/c `instance_eval` auto
32
+ # passes the receiver as the first argument to the block and stabby
33
+ # lambdas may not expect that and will ArgumentError.
34
+ slug_source_value =
35
+ if entry.source_proc.lambda?
36
+ record_instance.instance_exec(&entry.source_proc)
37
+ else
38
+ record_instance.instance_eval(&entry.source_proc)
39
+ end
40
+
41
+ slug_value =
42
+ Slug.new(
43
+ slug_source_value,
44
+ preprocessor: entry.preprocessor_proc,
45
+ separator: entry.separator,
46
+ allow_underscores: entry.allow_underscores
47
+ )
48
+ next if record_instance.send(attribute) == slug_value
49
+ yield attribute, slug_value
50
+ end
51
+ end
4
52
  end
@@ -0,0 +1,65 @@
1
+ require "much-plugin"
2
+ require "much-slug"
3
+
4
+ module MuchSlug
5
+ module ActiveRecord
6
+ include MuchPlugin
7
+
8
+ plugin_included do
9
+ @much_slug_has_slug_registry = MuchSlug::HasSlugRegistry.new
10
+ end
11
+
12
+ plugin_class_methods do
13
+ def has_slug(
14
+ source:,
15
+ attribute: nil,
16
+ preprocessor: nil,
17
+ separator: nil,
18
+ allow_underscores: nil,
19
+ skip_unique_validation: false,
20
+ unique_scope: nil)
21
+ registered_attribute =
22
+ self.much_slug_has_slug_registry.register(
23
+ attribute: attribute,
24
+ source: source,
25
+ preprocessor: preprocessor,
26
+ separator: separator,
27
+ allow_underscores: allow_underscores,
28
+ )
29
+
30
+ # since the slug isn't written until an after callback we can't always
31
+ # validate presence of it
32
+ validates_presence_of(registered_attribute, :on => :update)
33
+
34
+ unless skip_unique_validation
35
+ validates_uniqueness_of(registered_attribute, {
36
+ :case_sensitive => true,
37
+ :scope => unique_scope,
38
+ :allow_nil => true,
39
+ :allow_blank => true
40
+ })
41
+ end
42
+
43
+ after_create :much_slug_has_slug_update_slug_values
44
+ after_update :much_slug_has_slug_update_slug_values
45
+
46
+ registered_attribute
47
+ end
48
+
49
+ def much_slug_has_slug_registry
50
+ @much_slug_has_slug_registry
51
+ end
52
+ end
53
+
54
+ plugin_instance_methods do
55
+ private
56
+
57
+ def much_slug_has_slug_update_slug_values
58
+ MuchSlug.has_slug_changed_slug_values(self) do |attribute, slug_value|
59
+ self.send("#{attribute}=", slug_value)
60
+ self.update_column(attribute, slug_value)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,32 @@
1
+ require "much-slug"
2
+
3
+ module MuchSlug
4
+ class HasSlugRegistry < ::Hash
5
+ def initialize
6
+ super{ |h, k| h[k] = Entry.new }
7
+ end
8
+
9
+ def register(
10
+ attribute:,
11
+ source:,
12
+ preprocessor:,
13
+ separator:,
14
+ allow_underscores:)
15
+ (attribute || MuchSlug.default_attribute).to_s.tap do |a|
16
+ if allow_underscores.nil?
17
+ allow_underscores = MuchSlug.default_allow_underscores
18
+ end
19
+
20
+ entry = self[a]
21
+ entry.source_proc = source.to_proc
22
+ entry.preprocessor_proc = (preprocessor || MuchSlug.default_preprocessor).to_proc
23
+ entry.separator = separator || MuchSlug.default_separator
24
+ entry.allow_underscores = !!allow_underscores
25
+ end
26
+ end
27
+
28
+ class Entry
29
+ attr_accessor :source_proc, :preprocessor_proc, :separator, :allow_underscores
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,18 @@
1
+ module MuchSlug
2
+ module Slug
3
+ def self.new(string, preprocessor:, separator:, allow_underscores: true)
4
+ regexp_escaped_sep = Regexp.escape(separator)
5
+
6
+ slug = preprocessor.call(string.to_s.dup)
7
+ # Turn unwanted chars into the separator
8
+ slug.gsub!(/[^\w#{regexp_escaped_sep}]+/, separator)
9
+ # Turn underscores into the separator, unless allowing
10
+ slug.gsub!(/_/, separator) unless allow_underscores
11
+ # No more than one of the separator in a row.
12
+ slug.gsub!(/#{regexp_escaped_sep}{2,}/, separator)
13
+ # Remove leading/trailing separator.
14
+ slug.gsub!(/\A#{regexp_escaped_sep}|#{regexp_escaped_sep}\z/, "")
15
+ slug
16
+ end
17
+ end
18
+ end
@@ -1,3 +1,3 @@
1
1
  module MuchSlug
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -18,6 +18,10 @@ Gem::Specification.new do |gem|
18
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
19
  gem.require_paths = ["lib"]
20
20
 
21
- gem.add_development_dependency("assert", ["~> 2.16.5"])
21
+ gem.required_ruby_version = "~> 2.4"
22
22
 
23
+ gem.add_dependency("much-plugin", ["~> 0.2.1"])
24
+
25
+ gem.add_development_dependency("assert", ["~> 2.18.1"])
26
+ gem.add_development_dependency("ardb", ["~> 0.28.3"])
23
27
  end
@@ -3,4 +3,13 @@ require 'assert/factory'
3
3
  module Factory
4
4
  extend Assert::Factory
5
5
 
6
+ def self.non_word_chars
7
+ ( (" ".."/").to_a +
8
+ (":".."@").to_a +
9
+ ("[".."`").to_a +
10
+ ("{".."~").to_a -
11
+ ["-", "_"]
12
+ ).freeze
13
+ end
14
+
6
15
  end
@@ -0,0 +1,228 @@
1
+ require "assert"
2
+ require "much-slug/activerecord"
3
+
4
+ require "much-plugin"
5
+ require "ardb/record_spy"
6
+
7
+ module MuchSlug::ActiveRecord
8
+ class UnitTests < Assert::Context
9
+ desc "MuchSlug::ActiveRecord"
10
+ setup do
11
+ source_attribute = @source_attribute = Factory.string.to_sym
12
+ slug_attribute = @slug_attribute = Factory.string
13
+ @record_class = Ardb::RecordSpy.new do
14
+ include MuchSlug::ActiveRecord
15
+ attr_accessor source_attribute, slug_attribute, MuchSlug.default_attribute
16
+ attr_reader :slug_db_column_updates
17
+ attr_reader :save_called, :save_bang_called
18
+
19
+ def update_column(*args)
20
+ @slug_db_column_updates ||= []
21
+ @slug_db_column_updates << args
22
+ end
23
+ end
24
+
25
+ @has_slug_attribute = Factory.string
26
+ @has_slug_preprocessor = :downcase
27
+ @has_slug_separator = Factory.non_word_chars.sample
28
+ @has_slug_allow_underscores = Factory.boolean
29
+
30
+ Assert.stub_tap(@record_class.much_slug_has_slug_registry, :register) { |**kargs|
31
+ @register_called_with = kargs
32
+ }
33
+ end
34
+ subject{ @record_class }
35
+
36
+ should have_imeths :has_slug
37
+ should have_imeths :much_slug_has_slug_registry
38
+
39
+ should "not have any has_slug registry entries by default" do
40
+ assert_kind_of MuchSlug::HasSlugRegistry, subject.much_slug_has_slug_registry
41
+ assert_empty subject.much_slug_has_slug_registry
42
+ end
43
+
44
+ should "register a new has_slug entry using `has_slug`" do
45
+ subject.has_slug(
46
+ source: @source_attribute,
47
+ attribute: @has_slug_attribute,
48
+ preprocessor: @has_slug_preprocessor,
49
+ separator: @has_slug_separator,
50
+ allow_underscores: @has_slug_allow_underscores
51
+ )
52
+
53
+ exp_kargs = {
54
+ attribute: @has_slug_attribute,
55
+ source: @source_attribute,
56
+ preprocessor: @has_slug_preprocessor,
57
+ separator: @has_slug_separator,
58
+ allow_underscores: @has_slug_allow_underscores
59
+ }
60
+ assert_equal exp_kargs, @register_called_with
61
+ end
62
+
63
+ should "add validations using `has_slug`" do
64
+ subject.has_slug(
65
+ source: @source_attribute,
66
+ attribute: @has_slug_attribute
67
+ )
68
+ exp_attr_name = @has_slug_attribute
69
+
70
+ validation = subject.validations.find{ |v| v.type == :presence }
71
+ assert_not_nil validation
72
+ assert_equal [exp_attr_name], validation.columns
73
+ assert_equal :update, validation.options[:on]
74
+
75
+ validation = subject.validations.find{ |v| v.type == :uniqueness }
76
+ assert_not_nil validation
77
+ assert_equal [exp_attr_name], validation.columns
78
+ assert_equal true, validation.options[:case_sensitive]
79
+ assert_nil validation.options[:scope]
80
+ assert_equal true, validation.options[:allow_nil]
81
+ assert_equal true, validation.options[:allow_blank]
82
+ end
83
+
84
+ should "not add a unique validation if skipping unique validation" do
85
+ subject.has_slug(
86
+ source: @source_attribute,
87
+ attribute: @has_slug_attribute,
88
+ skip_unique_validation: true
89
+ )
90
+
91
+ validation = subject.validations.find{ |v| v.type == :uniqueness }
92
+ assert_nil validation
93
+ end
94
+
95
+ should "allow customizing its validations using `has_slug`" do
96
+ unique_scope = Factory.string.to_sym
97
+ subject.has_slug(
98
+ source: @source_attribute,
99
+ attribute: @has_slug_attribute,
100
+ unique_scope: unique_scope
101
+ )
102
+
103
+ validation = subject.validations.find{ |v| v.type == :uniqueness }
104
+ assert_not_nil validation
105
+ assert_equal unique_scope, validation.options[:scope]
106
+ end
107
+
108
+ should "add callbacks using `has_slug`" do
109
+ subject.has_slug(source: @source_attribute)
110
+
111
+ callback = subject.callbacks.find{ |v| v.type == :after_create }
112
+ assert_not_nil callback
113
+ assert_equal [:much_slug_has_slug_update_slug_values], callback.args
114
+
115
+ callback = subject.callbacks.find{ |v| v.type == :after_update }
116
+ assert_not_nil callback
117
+ assert_equal [:much_slug_has_slug_update_slug_values], callback.args
118
+ end
119
+
120
+ should "raise an argument error if `has_slug` isn't passed a source" do
121
+ assert_raises(ArgumentError){ subject.has_slug }
122
+ end
123
+ end
124
+
125
+ class InitTests < UnitTests
126
+ desc "when init"
127
+ setup do
128
+ @preprocessor = [:downcase, :upcase, :capitalize].sample
129
+ @separator = Factory.non_word_chars.sample
130
+ @allow_underscores = Factory.boolean
131
+
132
+ @registered_default_attribute =
133
+ @record_class.has_slug(source: @source_attribute)
134
+
135
+ source_attribute = @source_attribute
136
+ @registered_custom_attribute =
137
+ @record_class.has_slug(
138
+ source: -> { self.send(source_attribute) },
139
+ attribute: @slug_attribute,
140
+ preprocessor: @preprocessor,
141
+ separator: @separator,
142
+ allow_underscores: @allow_underscores
143
+ )
144
+
145
+ @record = @record_class.new
146
+
147
+ # create a string that has mixed case and an underscore so we can test
148
+ # that it uses the preprocessor and allow underscores options when
149
+ # generating a slug
150
+ @source_value = "#{Factory.string.downcase}_#{Factory.string.upcase}"
151
+ @record.send("#{@source_attribute}=", @source_value)
152
+
153
+ @exp_default_slug =
154
+ MuchSlug::Slug.new(
155
+ @source_value,
156
+ preprocessor: MuchSlug.default_preprocessor.to_proc,
157
+ separator: MuchSlug.default_separator,
158
+ allow_underscores: false
159
+ )
160
+ @exp_custom_slug =
161
+ MuchSlug::Slug.new(
162
+ @source_value,
163
+ preprocessor: @preprocessor.to_proc,
164
+ separator: @separator,
165
+ allow_underscores: @allow_underscores
166
+ )
167
+ end
168
+ subject{ @record }
169
+
170
+ should "default its slug attribute" do
171
+ assert_equal MuchSlug.default_attribute, @registered_default_attribute
172
+ assert_equal @slug_attribute, @registered_custom_attribute
173
+
174
+ subject.instance_eval{ much_slug_has_slug_update_slug_values }
175
+ assert_equal 2, subject.slug_db_column_updates.size
176
+
177
+ exp = @exp_default_slug
178
+ assert_equal exp, subject.send(MuchSlug.default_attribute)
179
+ assert_includes [MuchSlug.default_attribute, exp], subject.slug_db_column_updates
180
+
181
+ exp = @exp_custom_slug
182
+ assert_equal exp, subject.send(@slug_attribute)
183
+ assert_includes [@slug_attribute, exp], subject.slug_db_column_updates
184
+ end
185
+
186
+ should "not set its slug if it hasn't changed" do
187
+ subject.send("#{MuchSlug.default_attribute}=", @exp_default_slug)
188
+ subject.send("#{@slug_attribute}=", @exp_custom_slug)
189
+
190
+ subject.instance_eval{ much_slug_has_slug_update_slug_values }
191
+ assert_nil subject.slug_db_column_updates
192
+ end
193
+
194
+ should "slug its source even if its already a valid slug" do
195
+ slug_source = Factory.slug
196
+ subject.send("#{@source_attribute}=", slug_source)
197
+ # ensure the preprocessor doesn't change our source
198
+ Assert.stub(slug_source, @preprocessor){ slug_source }
199
+
200
+ subject.instance_eval{ much_slug_has_slug_update_slug_values }
201
+
202
+ exp =
203
+ MuchSlug::Slug.new(
204
+ slug_source,
205
+ preprocessor: @preprocessor.to_proc,
206
+ separator: @separator,
207
+ allow_underscores: @allow_underscores
208
+ )
209
+ assert_equal exp, subject.send(@slug_attribute)
210
+ assert_includes [@slug_attribute, exp], subject.slug_db_column_updates
211
+ end
212
+
213
+ should "manually update slugs" do
214
+ result = MuchSlug.update_slugs(@record)
215
+
216
+ assert_true result
217
+ assert_equal 2, subject.slug_db_column_updates.size
218
+
219
+ exp = @exp_default_slug
220
+ assert_equal exp, subject.send(MuchSlug.default_attribute)
221
+ assert_includes [MuchSlug.default_attribute, exp], subject.slug_db_column_updates
222
+
223
+ exp = @exp_custom_slug
224
+ assert_equal exp, subject.send(@slug_attribute)
225
+ assert_includes [@slug_attribute, exp], subject.slug_db_column_updates
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,95 @@
1
+ require "assert"
2
+ require "much-slug/has_slug_registry"
3
+
4
+ class MuchSlug::HasSlugRegistry
5
+ class UnitTests < Assert::Context
6
+ desc "MuchSlug::HasSlugRegistry"
7
+ setup do
8
+ @class = MuchSlug::HasSlugRegistry
9
+ end
10
+ subject{ @class }
11
+
12
+ should "subclass Hash" do
13
+ assert subject < ::Hash
14
+ end
15
+ end
16
+
17
+ class InitTests < UnitTests
18
+ desc "when init"
19
+ setup do
20
+ @attribute = Factory.string.to_sym
21
+ @source = :to_s
22
+ @preprocessor = :downcase
23
+ @separator = "|"
24
+ @allow_underscores = Factory.boolean
25
+
26
+ @registry = @class.new
27
+ end
28
+ subject{ @registry }
29
+
30
+ should have_imeths :register
31
+
32
+ should "default empty entries for unregisterd attributes" do
33
+ entry = subject[Factory.string]
34
+ assert_kind_of @class::Entry, entry
35
+ end
36
+
37
+ should "register new entries" do
38
+ registered_attribute =
39
+ subject.register(
40
+ attribute: @attribute,
41
+ source: @source,
42
+ preprocessor: @preprocessor,
43
+ separator: @separator,
44
+ allow_underscores: @allow_underscores
45
+ )
46
+
47
+ assert_equal @attribute.to_s, registered_attribute
48
+
49
+ entry = subject[registered_attribute]
50
+ assert_kind_of @class::Entry, entry
51
+ assert_equal @source.to_proc, entry.source_proc
52
+ assert_equal @preprocessor.to_proc, entry.preprocessor_proc
53
+ assert_equal @separator, entry.separator
54
+ assert_equal @allow_underscores, entry.allow_underscores
55
+
56
+ assert_same entry, subject[registered_attribute]
57
+ end
58
+
59
+ should "default registered settings if none are provided" do
60
+ registered_attribute =
61
+ subject.register(
62
+ attribute: nil,
63
+ source: @source,
64
+ preprocessor: nil,
65
+ separator: nil,
66
+ allow_underscores: nil
67
+ )
68
+
69
+ assert_equal MuchSlug.default_attribute.to_s, registered_attribute
70
+
71
+ entry = subject[registered_attribute]
72
+ assert_equal MuchSlug.default_preprocessor.to_proc, entry.preprocessor_proc
73
+ assert_equal MuchSlug.default_separator, entry.separator
74
+ assert_equal MuchSlug.default_allow_underscores, entry.allow_underscores
75
+ end
76
+ end
77
+
78
+ class EntryUnitTests < UnitTests
79
+ desc "Entry"
80
+ setup do
81
+ @entry_class = MuchSlug::HasSlugRegistry::Entry
82
+ end
83
+ subject{ @entry_class }
84
+ end
85
+
86
+ class EntryInitTests < EntryUnitTests
87
+ desc "when init"
88
+ setup do
89
+ @entry = @entry_class.new
90
+ end
91
+ subject{ @entry }
92
+
93
+ should have_accessors :source_proc, :preprocessor_proc, :separator, :allow_underscores
94
+ end
95
+ end
@@ -0,0 +1,23 @@
1
+ require "assert"
2
+ require "much-slug"
3
+
4
+ module MuchSlug
5
+ class UnitTests < Assert::Context
6
+ desc "MuchSlug"
7
+ setup do
8
+ @module = MuchSlug
9
+ end
10
+ subject{ @module }
11
+
12
+ should have_imeths :default_attribute, :default_preprocessor
13
+ should have_imeths :default_separator, :default_allow_underscores
14
+ should have_imeths :update_slugs
15
+
16
+ should "know its default settings" do
17
+ assert_equal "slug", subject.default_attribute
18
+ assert_equal :to_s, subject.default_preprocessor
19
+ assert_equal "-", subject.default_separator
20
+ assert_equal false, subject.default_allow_underscores
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,104 @@
1
+ require "assert"
2
+ require "much-slug/slug"
3
+
4
+ module MuchSlug::Slug
5
+ class UnitTests < Assert::Context
6
+ desc "MuchSlug::Slug"
7
+ setup do
8
+ @no_op_pp = proc{ |slug| slug }
9
+ @separator = "-"
10
+ @args = {
11
+ :preprocessor => @no_op_pp,
12
+ :separator => @separator
13
+ }
14
+
15
+ @module = MuchSlug::Slug
16
+ end
17
+ subject{ @module }
18
+
19
+ should have_imeths :new
20
+
21
+ should "always dup the given string" do
22
+ string = Factory.string
23
+ assert_not_same string, subject.new(string, **@args)
24
+ end
25
+
26
+ should "not change strings that are made up of valid chars" do
27
+ string = Factory.string
28
+ assert_equal string, subject.new(string, **@args)
29
+
30
+ string = "#{Factory.string}#{@separator}#{Factory.string.upcase}"
31
+ assert_equal string, subject.new(string, @args)
32
+ end
33
+
34
+ should "turn invalid chars into a separator" do
35
+ string = Factory.integer(3).times.map do
36
+ "#{Factory.string(3)}#{Factory.non_word_chars.sample}#{Factory.string(3)}"
37
+ end.join(Factory.non_word_chars.sample)
38
+ assert_equal string.gsub(/[^\w]+/, @separator), subject.new(string, **@args)
39
+ end
40
+
41
+ should "allow passing a custom preprocessor proc" do
42
+ string = "#{Factory.string}#{@separator}#{Factory.string.upcase}"
43
+ exp = string.downcase
44
+ args = @args.merge(:preprocessor => :downcase.to_proc)
45
+ assert_equal exp, subject.new(string, **args)
46
+
47
+ preprocessor = proc{ |s| s.gsub(/[A-Z]/, "a") }
48
+ exp = preprocessor.call(string)
49
+ args = @args.merge(:preprocessor => preprocessor)
50
+ assert_equal exp, subject.new(string, **args)
51
+ end
52
+
53
+ should "allow passing a custom separator" do
54
+ separator = Factory.non_word_chars.sample
55
+
56
+ invalid_char = (Factory.non_word_chars - [separator]).sample
57
+ string = "#{Factory.string}#{invalid_char}#{Factory.string}"
58
+ exp = string.gsub(/[^\w]+/, separator)
59
+ assert_equal exp, subject.new(string, **@args.merge(:separator => separator))
60
+
61
+ # it won"t change the separator in the strings
62
+ string = "#{Factory.string}#{separator}#{Factory.string}"
63
+ exp = string
64
+ assert_equal string, subject.new(string, **@args.merge(:separator => separator))
65
+
66
+ # it will change the default separator now
67
+ string = "#{Factory.string}#{@separator}#{Factory.string}"
68
+ exp = string.gsub(@separator, separator)
69
+ assert_equal exp, subject.new(string, **@args.merge(:separator => separator))
70
+ end
71
+
72
+ should "change underscores into its separator if not allowed" do
73
+ string = "#{Factory.string}#{@separator}#{Factory.string}"
74
+ assert_equal string, subject.new(string, **@args)
75
+
76
+ exp = string.gsub("_", @separator)
77
+ assert_equal exp, subject.new(string, **@args.merge(:allow_underscores => false))
78
+
79
+ assert_equal string, subject.new(string, **@args.merge(:allow_underscores => true))
80
+ end
81
+
82
+ should "not allow multiple separators in a row" do
83
+ string = "#{Factory.string}#{@separator}#{@separator}#{Factory.string}"
84
+ assert_equal string.gsub(/-{2,}/, @separator), subject.new(string, **@args)
85
+
86
+ # remove separators that were added from changing invalid chars
87
+ invalid_chars =
88
+ (Factory.integer(3) + 1).times.map{ Factory.non_word_chars.sample }.join
89
+ string = "#{Factory.string}#{invalid_chars}#{Factory.string}"
90
+ assert_equal string.gsub(/[^\w]+/, @separator), subject.new(string, **@args)
91
+ end
92
+
93
+ should "remove leading and trailing separators" do
94
+ string = "-#{Factory.string}#{@separator}#{Factory.string}-"
95
+ assert_equal string[1..-2], subject.new(string, **@args)
96
+
97
+ # remove separators that were added from changing invalid chars
98
+ invalid_char = Factory.non_word_chars.sample
99
+ string =
100
+ "#{invalid_char}#{Factory.string}#{@separator}#{Factory.string}#{invalid_char}"
101
+ assert_equal string[1..-2], subject.new(string, **@args)
102
+ end
103
+ end
104
+ end
metadata CHANGED
@@ -1,75 +1,111 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: much-slug
3
- version: !ruby/object:Gem::Version
4
- version: 0.0.1
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
5
  platform: ruby
6
- authors:
6
+ authors:
7
7
  - Kelly Redding
8
8
  - Collin Redding
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2018-10-06 00:00:00 Z
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
12
+ date: 2020-02-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: much-plugin
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 0.2.1
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 0.2.1
28
+ - !ruby/object:Gem::Dependency
16
29
  name: assert
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 2.18.1
35
+ type: :development
17
36
  prerelease: false
18
- requirement: &id001 !ruby/object:Gem::Requirement
19
- requirements:
20
- - - ~>
21
- - !ruby/object:Gem::Version
22
- version: 2.16.5
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 2.18.1
42
+ - !ruby/object:Gem::Dependency
43
+ name: ardb
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: 0.28.3
23
49
  type: :development
24
- version_requirements: *id001
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: 0.28.3
25
56
  description: Friendly, human-readable identifiers for database records.
26
- email:
57
+ email:
27
58
  - kelly@kellyredding.com
28
59
  - collin.redding@me.com
29
60
  executables: []
30
-
31
61
  extensions: []
32
-
33
62
  extra_rdoc_files: []
34
-
35
- files:
36
- - .gitignore
63
+ files:
64
+ - ".gitignore"
37
65
  - Gemfile
38
66
  - LICENSE
39
67
  - README.md
40
68
  - lib/much-slug.rb
69
+ - lib/much-slug/activerecord.rb
70
+ - lib/much-slug/has_slug_registry.rb
71
+ - lib/much-slug/slug.rb
41
72
  - lib/much-slug/version.rb
42
73
  - log/.gitkeep
43
74
  - much-slug.gemspec
44
75
  - test/helper.rb
45
76
  - test/support/factory.rb
77
+ - test/unit/activerecord_tests.rb
78
+ - test/unit/has_slug_registry_tests.rb
79
+ - test/unit/much-slug_tests.rb
80
+ - test/unit/slug_tests.rb
46
81
  - tmp/.gitkeep
47
82
  homepage: https://github.com/redding/much-slug
48
- licenses:
83
+ licenses:
49
84
  - MIT
50
85
  metadata: {}
51
-
52
86
  post_install_message:
53
87
  rdoc_options: []
54
-
55
- require_paths:
88
+ require_paths:
56
89
  - lib
57
- required_ruby_version: !ruby/object:Gem::Requirement
58
- requirements:
59
- - &id002
60
- - ">="
61
- - !ruby/object:Gem::Version
62
- version: "0"
63
- required_rubygems_version: !ruby/object:Gem::Requirement
64
- requirements:
65
- - *id002
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '2.4'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
66
100
  requirements: []
67
-
68
- rubyforge_project:
69
- rubygems_version: 2.7.7
101
+ rubygems_version: 3.0.4
70
102
  signing_key:
71
103
  specification_version: 4
72
104
  summary: Friendly, human-readable identifiers for database records.
73
- test_files:
105
+ test_files:
74
106
  - test/helper.rb
75
107
  - test/support/factory.rb
108
+ - test/unit/activerecord_tests.rb
109
+ - test/unit/has_slug_registry_tests.rb
110
+ - test/unit/much-slug_tests.rb
111
+ - test/unit/slug_tests.rb