much-slug 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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