sequel-generate-slug 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 901bfe33eea0397cdd68a73aa321a1bb69376989124bf0c66c58d305b241fa02
4
+ data.tar.gz: dd0a5c584970f8482dde914357dedef59b11b971457391f7cbeba2b21f40a809
5
+ SHA512:
6
+ metadata.gz: 559e13aa146f388868f9fffb19e660bb561e70b0b199a4535c8ce2be24b7100e0f7d448f2b0f1589eedecaebbc73362c409f0efa7e3d2008ff648932943a3f0e
7
+ data.tar.gz: eac8f1102296fe5bf5085a3d3e61bc0e4956d8618a939648d185014dce31a73734c717f21370ce2999d7363786f9a4a7fb7e7854484f59570ac2dc85f5b7bce0
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringex'
4
+
5
+ module Sequel
6
+ module Plugins
7
+ # GenerateSlug plugin generates a unique slug based on the specified source
8
+ # and additional columns. The plugin is designed to work with the Sequel
9
+ # ORM.
10
+ #
11
+ # @example
12
+ # class Post < Sequel::Model
13
+ # plugin :generate_slug, column: 'slug', source: 'title', additional: 'identifier'
14
+ # end
15
+ #
16
+ # post = Post.create(title: 'Hello World', identifier: 'abcdef')
17
+ # post.slug # => 'hello-world'
18
+ #
19
+ # post = Post.create(title: 'Hello World', identifier: 'abcdef')
20
+ # post.slug # => 'hello-world-abcdef'
21
+ #
22
+ # post = Post.create(title: 'Hello World', identifier: 'abcdef')
23
+ # post.slug # => 'hello-world-abcdef-1'
24
+ module GenerateSlug
25
+ # Configure the plugin with the given options.
26
+ #
27
+ # @param model [Sequel::Model] the model to configure the plugin for
28
+ # @param opts [Hash] the options to configure the plugin with
29
+ # @option opts [Symbol] :column (:slug) the column to store the generated slug
30
+ # @option opts [Symbol] :source (:title) the source column to generate the slug from
31
+ # @option opts [Symbol] :additional (:identifier) the additional column to use in slug generation if necessary
32
+ def self.configure(model, opts = {})
33
+ model.instance_exec do
34
+ @slug_column = opts[:column] || :slug
35
+ @source_column = opts[:source] || :title
36
+ @additional_column = opts[:additional] || :identifier
37
+ end
38
+ end
39
+
40
+ module ClassMethods
41
+ # @!attribute [r] slug_column
42
+ # @return [Symbol] the column to store the generated slug
43
+ attr_reader :slug_column
44
+
45
+ # @!attribute [r] source_column
46
+ # @return [Symbol] the source column to generate the slug from
47
+ attr_reader :source_column
48
+
49
+ # @!attribute [r] additional_column
50
+ # @return [Symbol] the additional column to use in slug generation if necessary
51
+ attr_reader :additional_column
52
+
53
+ Plugins.inherited_instance_variables(self, :@slug_column => nil, :@source_column => nil, :@additional_column => nil)
54
+ end
55
+
56
+ module InstanceMethods
57
+ # Set the slug before validation if the record is new or the slug needs updating.
58
+ def before_validation
59
+ set_slug if new? || slug_needs_update?
60
+ super
61
+ end
62
+
63
+ private
64
+
65
+ # Generate a unique slug based on the source and additional columns.
66
+ #
67
+ # @return [String] the generated slug
68
+ def generate_slug
69
+ base_slug = get_column_value(model.source_column).to_url
70
+ additional_column = get_column_value(model.additional_column)
71
+
72
+ slug = find_unique_slug(base_slug, additional_column)
73
+ slug ||= generate_slug_with_random_number(base_slug)
74
+
75
+ slug
76
+ end
77
+
78
+ # Find a unique slug based on the given base_slug and additional_column.
79
+ #
80
+ # @param base_slug [String] the base slug to start with
81
+ # @param additional_column [String, nil] the additional column value to use in slug generation if necessary
82
+ # @return [String, nil] the unique slug or nil if not found
83
+ def find_unique_slug(base_slug, additional_column)
84
+ variations = [base_slug]
85
+ variations << "#{base_slug}-#{additional_column}" unless additional_column.nil? || additional_column.empty?
86
+
87
+ variations.each do |variation|
88
+ return variation if self.class.where(model.slug_column.to_sym => variation).none?
89
+ end
90
+
91
+ nil
92
+ end
93
+
94
+ # Generate a unique slug with a random number appended to the base_slug.
95
+ #
96
+ # @param base_slug [String] the base slug to start with
97
+ # @return [String] the generated slug with a random number appended
98
+ def generate_slug_with_random_number(base_slug)
99
+ loop do
100
+ new_slug = "#{base_slug}-#{rand(1000)}"
101
+ break new_slug if self.class.where(model.slug_column.to_sym => new_slug).none?
102
+ end
103
+ end
104
+
105
+ # Check if the slug needs to be updated based on the modified columns.
106
+ #
107
+ # @return [Boolean] true if the slug needs to be updated, false otherwise
108
+ def slug_needs_update?
109
+ modified?(model.source_column.to_sym) || modified?(model.additional_column.to_sym)
110
+ end
111
+
112
+ # Set the slug for the model instance.
113
+ #
114
+ # @param slug [String] the slug to set
115
+ # @return [void]
116
+ def set_slug(slug = generate_slug)
117
+ slug_column = model.slug_column
118
+ method_name = :"#{slug_column}="
119
+
120
+ set_column_value(method_name, slug) if respond_to?(slug_column) && respond_to?(method_name) && !modified?(slug_column)
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module SequelGenerateSlug
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sequel::Plugins::GenerateSlug do
4
+ before do
5
+ DB.create_table :assets do
6
+ primary_key :id
7
+ String :name
8
+ String :identifier
9
+ String :slug
10
+ end
11
+
12
+ class Asset < Sequel::Model(:assets)
13
+ plugin :generate_slug, source: :name, additional: :identifier
14
+ end
15
+ end
16
+
17
+ after do
18
+ DB.drop_table :assets
19
+ end
20
+
21
+ describe 'before_create' do
22
+ context 'when slug is not provided' do
23
+ it 'generates unique slug' do
24
+ asset = Asset.create(name: 'Test Asset', identifier: '123')
25
+ expect(asset.slug).not_to be_empty
26
+ end
27
+ end
28
+
29
+ context 'when slug is provided' do
30
+ it 'does not override provided slug' do
31
+ asset = Asset.create(name: 'Test Asset', identifier: '123', slug: 'test-asset')
32
+ expect(asset.slug).to eq('test-asset')
33
+ end
34
+ end
35
+ end
36
+
37
+ describe 'before_update' do
38
+ context 'when source or additional columns are modified' do
39
+ it 'updates slug' do
40
+ asset = Asset.create(name: 'Test Asset', identifier: '123')
41
+ asset.update(name: 'New Name')
42
+ expect(asset.slug).to eq('new-name')
43
+ end
44
+ end
45
+
46
+ context 'when slug column is modified' do
47
+ it 'does not update slug' do
48
+ asset = Asset.create(name: 'Test Asset', identifier: '123')
49
+ asset.update(slug: 'updated-asset')
50
+ expect(asset.slug).to eq('updated-asset')
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,13 @@
1
+ require 'bundler/setup'
2
+ require 'sequel'
3
+
4
+ require './lib/sequel/plugins/generate_slug'
5
+
6
+ DB = Sequel.connect('sqlite:/')
7
+
8
+ RSpec.configure do |config|
9
+ config.order = :random
10
+ Kernel.srand config.seed
11
+
12
+ config.pattern = 'spec/**/*_spec.rb'
13
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sequel-generate-slug
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Zhuk
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-04-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sequel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: stringex
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Sequel plugin for generating and maintaining slugs in a database using
70
+ the base name and an optional additional column.
71
+ email:
72
+ - zhuk_andriy@hotmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - lib/sequel/plugins/generate_slug.rb
78
+ - lib/version.rb
79
+ - spec/plugins/generate_slug_spec.rb
80
+ - spec/spec_helper.rb
81
+ homepage: https://github.com/andrewzhuk/sequel-generate-slug
82
+ licenses:
83
+ - MIT
84
+ metadata:
85
+ source_code_uri: https://github.com/andrewzhuk/sequel-generate-slug
86
+ bug_tracker_uri: https://github.com/andrewzhuk/sequel-generate-slug/issues
87
+ post_install_message:
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: 2.5.0
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubygems_version: 3.3.26
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: Sequel plugin for generating and maintaining slugs.
106
+ test_files: []