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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 144b6900260d2c6d5d9f293cee58c0ceb25ec663ff00c77bf1d2d72f9d79fc78
4
- data.tar.gz: 6332f95d24fcc612fd1be9147f2e816c55a9484c4ac2582e9be21691902272e9
3
+ metadata.gz: fc2bd819b79ff41a6fe04c425c2dd21243bf4250f12d75dfe2fa4d2fea926ae1
4
+ data.tar.gz: 21b1aafaa43ca42f633072d722f9d35e6dbc21a1594195228a22df884c7f65b3
5
5
  SHA512:
6
- metadata.gz: 906f07f820ffe12443424f5c0e1427a5b942537911a6026f6b9644b3acdd40a817227642f86b7d01123bac0beeaed31eebf522b6e7442206cf3b16c6e4b0444e
7
- data.tar.gz: 61f35e4e5322dd7a7fc967c093697150e56d1d2c17b8f15890bf262ffdeec9306ebb7790748e06691b082ec9ac006bd163a427b4e963363fa0affbbdb8ff2e9b
6
+ metadata.gz: fd9094457874a4c7a629e007747c6931367a67655d2c343f9aab91ee3c160340250c08119267e4dd3a576a043bd8b8ba5e22413f13deb6602640715a5e927316
7
+ data.tar.gz: 776c13331c63092e759103488cd55b4feabd36697909c90a9aabdc108d437c0b32475a572f9e5fdb262b1bcac42ce1ede6e9a279bc9d8d4be431116ceae3b386
data/.rubocop.yml CHANGED
@@ -44,6 +44,8 @@ Metrics/MethodLength:
44
44
 
45
45
  Metrics/ModuleLength:
46
46
  Max: 100
47
+ Exclude:
48
+ - 'lib/schnecke/schnecke.rb'
47
49
 
48
50
  Minitest/MultipleAssertions:
49
51
  Max: 10
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- schnecke (0.2.0)
4
+ schnecke (0.4.0)
5
5
  activerecord (> 4.2.0)
6
6
  activesupport (> 4.2.0)
7
7
 
data/README.md CHANGED
@@ -22,10 +22,10 @@ Or install it yourself as:
22
22
 
23
23
  ## Usage
24
24
 
25
- Given a class `A` which has the attribute `name` and has a `slug` column defined, we can do the following:
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 A
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 A
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 A
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 A
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 A
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, `A` and `B` as defined as below, then the slugs will be unique for all slugs for all type `A` objcets and all type `B` 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 A
137
+ class SomeObject
81
138
  include Schnecke
82
139
  slug :name
83
140
  end
84
141
 
85
- class B
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 `A` and once for any object of type `B`. 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.
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 `A`, 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.
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 A
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 A
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 B
230
+ class SomeOtherObject
153
231
  include Schnecke
154
232
  slug :name, require_format: /\A[a-z]+\z/
155
233
 
@@ -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
- validate_source
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 = truncate(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!(/[\p{Pc}\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]/, '')
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 validate_source
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 truncate(slug)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Schnecke
4
- VERSION = '0.2.0'
4
+ VERSION = '0.4.0'
5
5
  end
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.2.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-01 00:00:00.000000000 Z
11
+ date: 2022-10-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler