schnecke 0.2.0 → 0.4.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 +4 -4
- data/.rubocop.yml +2 -0
- data/Gemfile.lock +1 -1
- data/README.md +92 -14
- data/lib/schnecke/schnecke.rb +50 -20
- data/lib/schnecke/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fc2bd819b79ff41a6fe04c425c2dd21243bf4250f12d75dfe2fa4d2fea926ae1
|
|
4
|
+
data.tar.gz: 21b1aafaa43ca42f633072d722f9d35e6dbc21a1594195228a22df884c7f65b3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fd9094457874a4c7a629e007747c6931367a67655d2c343f9aab91ee3c160340250c08119267e4dd3a576a043bd8b8ba5e22413f13deb6602640715a5e927316
|
|
7
|
+
data.tar.gz: 776c13331c63092e759103488cd55b4feabd36697909c90a9aabdc108d437c0b32475a572f9e5fdb262b1bcac42ce1ede6e9a279bc9d8d4be431116ceae3b386
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -22,10 +22,10 @@ Or install it yourself as:
|
|
|
22
22
|
|
|
23
23
|
## Usage
|
|
24
24
|
|
|
25
|
-
Given a class `
|
|
25
|
+
Given a class `SomeObject` which has the attribute `name` and has a `slug` column defined, we can do the following:
|
|
26
26
|
|
|
27
27
|
```ruby
|
|
28
|
-
class
|
|
28
|
+
class SomeObject
|
|
29
29
|
include Schnecke
|
|
30
30
|
slug :name
|
|
31
31
|
end
|
|
@@ -34,18 +34,75 @@ end
|
|
|
34
34
|
This will take the value in `name` and automatically set the slug based on it. If the slug needs to be based on multiple attributes of a model, simply pass in the array of attributes as follows:
|
|
35
35
|
|
|
36
36
|
```ruby
|
|
37
|
-
class
|
|
37
|
+
class SomeObject
|
|
38
38
|
include Schnecke
|
|
39
39
|
slug [:first_name, :last_name]
|
|
40
40
|
end
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
Under the hood, this library adds a `before_validate` callback that automatically runs a method called `assign_slug`. You are welcome to call this method explicity if you so desire.
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
obj = SomeObject.new(name: 'Hello World!')
|
|
47
|
+
obj.assign_slug
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
It is important to note that if the attribute used to hold the slug (`slug` by default, see next section) already contains a value, the slug assignment **WILL NOT HAPPEN**. This means, if you manually assign the slug by explicitly setting the slug value yourself, it will not be modified. If you would like the slug to be overwrriten you can explicitly call the `reassign_slug` method.
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
obj = SomeObject.new(name: 'Hello World!', slug: 'hi')
|
|
54
|
+
|
|
55
|
+
# This will do nothing as the slug was already set
|
|
56
|
+
obj.assign_slug
|
|
57
|
+
|
|
58
|
+
# This will cause the slug to be assigned
|
|
59
|
+
obj.reassign_slug
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Using a method to define a slug source
|
|
63
|
+
|
|
64
|
+
There are times when the source of the slug needs to be set based on some other values (e.g. a parent object, random number, etc). In this case, simply define a method that is to be used to set the soure of the slug. This method can be public, protected, or private.
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
class SomeObject
|
|
68
|
+
include Schnecke
|
|
69
|
+
slug :parent_slug
|
|
70
|
+
|
|
71
|
+
belongs_to :parent_object
|
|
72
|
+
|
|
73
|
+
protected
|
|
74
|
+
|
|
75
|
+
def parent_slug
|
|
76
|
+
parent_object.slug
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Just like if one is using a plain old attributes, one can mix and match methods and attributes if the slug is to be derived from multiple sources
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
class SomeObject
|
|
85
|
+
include Schnecke
|
|
86
|
+
slug [:name, :parent_slug]
|
|
87
|
+
|
|
88
|
+
belongs_to :parent_object
|
|
89
|
+
|
|
90
|
+
protected
|
|
91
|
+
|
|
92
|
+
def parent_slug
|
|
93
|
+
parent_object.slug
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
And this will take the `:name` attribute and the result from `parent_slug` and create a slug from the two things combined.
|
|
99
|
+
|
|
43
100
|
### Slug column
|
|
44
101
|
|
|
45
102
|
By default it is assumed that the generated slug will be assigned to the `slug` attribute of the model. If one needs to place the slug in a different columm, this can be done by defining the `column` attribute:
|
|
46
103
|
|
|
47
104
|
```ruby
|
|
48
|
-
class
|
|
105
|
+
class SomeObject
|
|
49
106
|
include Schnecke
|
|
50
107
|
slug :name, column: :some_other_column
|
|
51
108
|
end
|
|
@@ -59,14 +116,14 @@ By default the maxium length of a slug is 32 characters *NOT INCLUDING* any pote
|
|
|
59
116
|
|
|
60
117
|
|
|
61
118
|
```ruby
|
|
62
|
-
class
|
|
119
|
+
class SomeObject
|
|
63
120
|
include Schnecke
|
|
64
121
|
slug :name, limit_length: 15
|
|
65
122
|
end
|
|
66
123
|
```
|
|
67
124
|
|
|
68
125
|
```ruby
|
|
69
|
-
class
|
|
126
|
+
class SomeObject
|
|
70
127
|
include Schnecke
|
|
71
128
|
slug :name, limit_length: false
|
|
72
129
|
end
|
|
@@ -74,21 +131,21 @@ end
|
|
|
74
131
|
|
|
75
132
|
### Slug Uniquness
|
|
76
133
|
|
|
77
|
-
By default slugs are unique to the object that defines the slug. For example if we have the 2 objects, `
|
|
134
|
+
By default slugs are unique to the object that defines the slug. For example if we have the 2 objects, `SomeObject` and `SomeOtherObject` as defined as below, then the slugs will be unique for all slugs for all type `SomeObject` objcets and all type `SomeOtherObject` objects.
|
|
78
135
|
|
|
79
136
|
```ruby
|
|
80
|
-
class
|
|
137
|
+
class SomeObject
|
|
81
138
|
include Schnecke
|
|
82
139
|
slug :name
|
|
83
140
|
end
|
|
84
141
|
|
|
85
|
-
class
|
|
142
|
+
class SomeOtherObject
|
|
86
143
|
include Schnecke
|
|
87
144
|
slug :name
|
|
88
145
|
end
|
|
89
146
|
```
|
|
90
147
|
|
|
91
|
-
This means that the slug `foo` can exists 2 times; once for any object of type `
|
|
148
|
+
This means that the slug `foo` can exists 2 times; once for any object of type `SomeObject` and once for any object of type `SomeOtherObject`. Currently there is no way to create globally unique slugs. If this is something that is required, then something like [`friendly_id`](https://github.com/norman/friendly_id) might be more appropriate for your use case.
|
|
92
149
|
|
|
93
150
|
### Handling non-unique slugs
|
|
94
151
|
|
|
@@ -98,7 +155,7 @@ It is important to note that the maximum length of a slug does not include the a
|
|
|
98
155
|
|
|
99
156
|
### Defining a custom uniqueness scope
|
|
100
157
|
|
|
101
|
-
There are times when we want slugs not be unique for all objects of type `
|
|
158
|
+
There are times when we want slugs not be unique for all objects of type `SomeObject`, but rather for a smaller scope. For example, let's say we have a system with multiple `Accounts`, each containing `Record`s. If we want the slug for the `Record` to be unique only within the scope of an `account` we can do by providing the uniqueness scope when setting up the slug.
|
|
102
159
|
|
|
103
160
|
```ruby
|
|
104
161
|
class Record
|
|
@@ -121,12 +178,33 @@ class Tag
|
|
|
121
178
|
end
|
|
122
179
|
```
|
|
123
180
|
|
|
181
|
+
### Callbacks
|
|
182
|
+
|
|
183
|
+
Two callbacks, `before_assign_slug` and `after_assign_slug`, are provided so that you can run arbitrary code before and after the slug assignment process. Both of these callbacks will always run regardless of whether or not a slug is to be assigned. The only time `after_assign_slug` is not run is if there is an exception raised during the assignment process.
|
|
184
|
+
|
|
185
|
+
Note, since `reassign_slug` is just a forced assignment of a slug, both callbacks will run as well.
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
class SomeObject
|
|
189
|
+
include Schnecke
|
|
190
|
+
slug :name
|
|
191
|
+
|
|
192
|
+
def before_assign_slug(opts={})
|
|
193
|
+
puts 'Hello world! I get run before the slug assignment process'
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def after_assign_slug(opts={})
|
|
197
|
+
puts 'Goodbye world! I get run after the slug assignment process'
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
```
|
|
201
|
+
|
|
124
202
|
### Advanced Usage
|
|
125
203
|
|
|
126
204
|
If you need to change how the slug is generated, how duplicates are handled, etc., you can overwrite the methods in your class. For example to change how slugs are generated you can overwrite the `slugify` method.
|
|
127
205
|
|
|
128
206
|
```ruby
|
|
129
|
-
class
|
|
207
|
+
class SomeObject
|
|
130
208
|
include Schnecke
|
|
131
209
|
slug :name
|
|
132
210
|
|
|
@@ -140,7 +218,7 @@ end
|
|
|
140
218
|
Note, by default the library will validate to ensure that the slug only contains lowercase alpphanumeric letters and '-' or '_'. If your new method changes the alloweable set of characters you can either disable this validation, or pass in your own validation pattern.
|
|
141
219
|
|
|
142
220
|
```ruby
|
|
143
|
-
class
|
|
221
|
+
class SomeObject
|
|
144
222
|
include Schnecke
|
|
145
223
|
slug :name, require_format: false
|
|
146
224
|
|
|
@@ -149,7 +227,7 @@ class A
|
|
|
149
227
|
end
|
|
150
228
|
end
|
|
151
229
|
|
|
152
|
-
class
|
|
230
|
+
class SomeOtherObject
|
|
153
231
|
include Schnecke
|
|
154
232
|
slug :name, require_format: /\A[a-z]+\z/
|
|
155
233
|
|
data/lib/schnecke/schnecke.rb
CHANGED
|
@@ -64,7 +64,47 @@ module Schnecke
|
|
|
64
64
|
# Note, a slug will not be assigned if one already exists. If one needs to
|
|
65
65
|
# force the assignment of a slug, pass `force: true`
|
|
66
66
|
def assign_slug(opts = {})
|
|
67
|
-
|
|
67
|
+
before_assign_slug(opts)
|
|
68
|
+
perform_slug_assign(opts)
|
|
69
|
+
after_assign_slug(opts)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Reassign the slug
|
|
73
|
+
#
|
|
74
|
+
# Unlike assign_slug, this will cause a slug to be created even if one
|
|
75
|
+
# already exists.
|
|
76
|
+
def reassign_slug(opts = {})
|
|
77
|
+
opts[:force] = true
|
|
78
|
+
assign_slug(opts)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Callback that is handled before the slug assignment process.
|
|
82
|
+
#
|
|
83
|
+
# When this is called, no validations, or decisions about whether or not
|
|
84
|
+
# a slug should be created have been made. As such, this will always run
|
|
85
|
+
# regardless of whether or not the slug assignment process proceeds or not.
|
|
86
|
+
def before_assign_slug(opts = {})
|
|
87
|
+
# Left blank, but can be implemented by user
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Callback that is handled after the slug is assigned
|
|
91
|
+
#
|
|
92
|
+
# Unless an error is raised during the slug assignment process, this method
|
|
93
|
+
# will always be called regardless of whether or not the slug was assigned
|
|
94
|
+
def after_assign_slug(opts = {})
|
|
95
|
+
# Left blank, but can be implemented by user
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
protected
|
|
99
|
+
|
|
100
|
+
# Assign the slug
|
|
101
|
+
#
|
|
102
|
+
# This is automatically called before model validation.
|
|
103
|
+
#
|
|
104
|
+
# Note, a slug will not be assigned if one already exists. If one needs to
|
|
105
|
+
# force the assignment of a slug, pass `force: true`
|
|
106
|
+
def perform_slug_assign(opts = {})
|
|
107
|
+
validate_slug_source
|
|
68
108
|
validate_slug_column
|
|
69
109
|
|
|
70
110
|
return if !should_create_slug? && !opts[:force]
|
|
@@ -79,7 +119,7 @@ module Schnecke
|
|
|
79
119
|
end
|
|
80
120
|
|
|
81
121
|
# Make sure it is not too long
|
|
82
|
-
candidate_slug =
|
|
122
|
+
candidate_slug = truncate_slug(candidate_slug)
|
|
83
123
|
|
|
84
124
|
# If there is a duplicate, create a unique one
|
|
85
125
|
if slug_exists?(candidate_slug)
|
|
@@ -89,16 +129,6 @@ module Schnecke
|
|
|
89
129
|
self[schnecke_config[:slug_column]] = candidate_slug
|
|
90
130
|
end
|
|
91
131
|
|
|
92
|
-
# Reassign the slug
|
|
93
|
-
#
|
|
94
|
-
# Unlike assign_slug, this will cause a slug to be created even if one
|
|
95
|
-
# already exists.
|
|
96
|
-
def reassign_slug
|
|
97
|
-
assign_slug(force: true)
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
protected
|
|
101
|
-
|
|
102
132
|
# Slugify a string
|
|
103
133
|
#
|
|
104
134
|
# This will take a string and convert it to a slug by removing punctuation
|
|
@@ -106,9 +136,9 @@ module Schnecke
|
|
|
106
136
|
#
|
|
107
137
|
# This can be overriden if a different slug generation method is needed
|
|
108
138
|
def slugify(str)
|
|
109
|
-
return if str.blank?
|
|
139
|
+
return str if str.blank?
|
|
110
140
|
|
|
111
|
-
str.gsub
|
|
141
|
+
str = str.gsub(/[\p{Pc}\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]/, '')
|
|
112
142
|
str.parameterize
|
|
113
143
|
end
|
|
114
144
|
|
|
@@ -131,7 +161,7 @@ module Schnecke
|
|
|
131
161
|
#
|
|
132
162
|
# This can be overriden if a different behavior is desired
|
|
133
163
|
def slugify_duplicate(slug)
|
|
134
|
-
return if slug.blank?
|
|
164
|
+
return slug if slug.blank?
|
|
135
165
|
|
|
136
166
|
seq = 2
|
|
137
167
|
new_slug = slug_concat([slug, seq])
|
|
@@ -156,10 +186,10 @@ module Schnecke
|
|
|
156
186
|
|
|
157
187
|
private
|
|
158
188
|
|
|
159
|
-
def
|
|
189
|
+
def validate_slug_source
|
|
160
190
|
source = arrayify(schnecke_config[:slug_source])
|
|
161
191
|
source.each do |attr|
|
|
162
|
-
unless respond_to?(attr)
|
|
192
|
+
unless respond_to?(attr, true)
|
|
163
193
|
raise ArgumentError,
|
|
164
194
|
"Source '#{attr}' does not exist."
|
|
165
195
|
end
|
|
@@ -185,9 +215,9 @@ module Schnecke
|
|
|
185
215
|
parts.join(schnecke_config[:slug_separator])
|
|
186
216
|
end
|
|
187
217
|
|
|
188
|
-
def
|
|
189
|
-
return if slug.blank?
|
|
190
|
-
return if schnecke_config[:limit_length].blank?
|
|
218
|
+
def truncate_slug(slug)
|
|
219
|
+
return slug if slug.blank?
|
|
220
|
+
return slug if schnecke_config[:limit_length].blank?
|
|
191
221
|
|
|
192
222
|
slug[0, schnecke_config[:limit_length]]
|
|
193
223
|
end
|
data/lib/schnecke/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: schnecke
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrick R. Schmid
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-10-
|
|
11
|
+
date: 2022-10-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|