louisville 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.ruby-vesion +0 -0
- data/.travis.yml +42 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +124 -0
- data/Rakefile +9 -0
- data/gemfiles/ar30.gemfile +13 -0
- data/gemfiles/ar31.gemfile +12 -0
- data/gemfiles/ar32.gemfile +12 -0
- data/gemfiles/ar40.gemfile +12 -0
- data/gemfiles/ar41.gemfile +12 -0
- data/gemfiles/ar42.gemfile +12 -0
- data/lib/louisville/collision_resolvers/abstract.rb +101 -0
- data/lib/louisville/collision_resolvers/none.rb +11 -0
- data/lib/louisville/collision_resolvers/numeric_sequence.rb +78 -0
- data/lib/louisville/collision_resolvers/string_sequence.rb +48 -0
- data/lib/louisville/config.rb +60 -0
- data/lib/louisville/extensions/collision.rb +87 -0
- data/lib/louisville/extensions/finder.rb +109 -0
- data/lib/louisville/extensions/history.rb +59 -0
- data/lib/louisville/extensions/setter.rb +44 -0
- data/lib/louisville/slug.rb +8 -0
- data/lib/louisville/slugger.rb +103 -0
- data/lib/louisville/util.rb +39 -0
- data/lib/louisville/version.rb +13 -0
- data/lib/louisville.rb +28 -0
- data/louisville.gemspec +21 -0
- data/spec/collision_spec.rb +84 -0
- data/spec/column_spec.rb +40 -0
- data/spec/finder_spec.rb +70 -0
- data/spec/history_spec.rb +96 -0
- data/spec/numeric_sequence_spec.rb +36 -0
- data/spec/setter_spec.rb +42 -0
- data/spec/slugger_spec.rb +89 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/string_sequence_spec.rb +41 -0
- data/spec/support/database.example.yml +6 -0
- data/spec/support/database.yml +6 -0
- data/spec/support/schema.rb +20 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fdc680eb9ca340621494d36950a855275314f80a
|
4
|
+
data.tar.gz: cdcd1db4119ae3ac44992ad07a9568f8ce1d1e6b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 93640afde3565c61129849b03f5bae891c486f82b948335329f1dd1c4fa7a779cba16f4804dafcb6c659047e567eeb14181d49133870a87b635d73ae3825ae5b
|
7
|
+
data.tar.gz: 0a5050f87c8db5e269acd3067b9f3741ac9470168b37c2c2c05051fa13266d81326c65bbffe278053f4d40ac98a688418d33b222a30bed9b6095bf1b4ceaf538
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
louisville
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.1.5
|
data/.ruby-vesion
ADDED
File without changes
|
data/.travis.yml
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
language: ruby
|
2
|
+
|
3
|
+
services: mysql
|
4
|
+
|
5
|
+
before_script:
|
6
|
+
- mysql -e 'create database louisville_test;'
|
7
|
+
- cp spec/support/database.example.yml spec/support/database.yml
|
8
|
+
|
9
|
+
before_install:
|
10
|
+
- gem update --system 2.1.11
|
11
|
+
- gem --version
|
12
|
+
|
13
|
+
rvm:
|
14
|
+
- 1.8.7
|
15
|
+
- 1.9.2
|
16
|
+
- 1.9.3
|
17
|
+
- 2.0.0
|
18
|
+
- 2.1.5
|
19
|
+
- jruby
|
20
|
+
|
21
|
+
gemfile:
|
22
|
+
- gemfiles/ar30.gemfile
|
23
|
+
- gemfiles/ar31.gemfile
|
24
|
+
- gemfiles/ar32.gemfile
|
25
|
+
- gemfiles/ar40.gemfile
|
26
|
+
- gemfiles/ar41.gemfile
|
27
|
+
- gemfiles/ar42.gemfile
|
28
|
+
|
29
|
+
matrix:
|
30
|
+
exclude:
|
31
|
+
- rvm: 1.8.7
|
32
|
+
gemfile: gemfiles/ar40.gemfile
|
33
|
+
- rvm: 1.9.2
|
34
|
+
gemfile: gemfiles/ar40.gemfile
|
35
|
+
- rvm: 1.8.7
|
36
|
+
gemfile: gemfiles/ar41.gemfile
|
37
|
+
- rvm: 1.9.2
|
38
|
+
gemfile: gemfiles/ar41.gemfile
|
39
|
+
- rvm: 1.8.7
|
40
|
+
gemfile: gemfiles/ar42.gemfile
|
41
|
+
- rvm: 1.9.2
|
42
|
+
gemfile: gemfiles/ar42.gemfile
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Mike Nelson
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
# Louisville
|
2
|
+
|
3
|
+
This is not a Swiss Army Bulldozer. This is not a Pseudocephalopod. Contrary to popular belief, this was not written in Kentucky. This is a moderately simple, moderately extensible, moderately opinionated slugging library.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
Just need the most basic of slugging?
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
add_column :players, :slug, :string
|
11
|
+
add_index :players, :slug, unique: true
|
12
|
+
```
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
class Player < ActiveRecord::Base
|
16
|
+
include Louisville::Slugger
|
17
|
+
|
18
|
+
slug :name
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
|
23
|
+
Need a litte more? The `slug` class method accepts an options hash.
|
24
|
+
|
25
|
+
| Option Key | Option Value | Default Value | What it does |
|
26
|
+
| ---------- | ------------ | ------------- | ------------ |
|
27
|
+
| :column | Any String | "slug" | Configures the slug column. "slug" is the default, provide to override. |
|
28
|
+
| :finder | true | true | Adds the finder extension. The finder extension allows `class.find('slug')` to work. |
|
29
|
+
| :finder | false | true | Removes the finder option, disabling the `class.find` override. |
|
30
|
+
| :collision | :string_sequence | :none | Handles collisions by appending a sequence to the slug. A generated slug which collides with an existing slug will gain a "--number". So if there was a record with "foobar" as it's slug and another record generated the slug "foobar", the second record would save as "foobar--2". |
|
31
|
+
| :collision | :numeric_sequence | :none | Handles collisions my incrementing a numeric column named `"#{slug_column}_sequence"`. With this configuration, the slug column may not be unique but the `[slug, slug_sequence]` combination would be. |
|
32
|
+
| :setter | Any Valid Ruby Method String | false | Allows the slug generation to be short circuited by providing a setter. Think about a user choosing their username or a page having an seo title. Collisions with the provided value will not be resolved, meaning a validation error will occur if an existing slug is provided. |
|
33
|
+
| :history | true | false | When a record's slug changes this will create a record in the slugs table. The finder and collision resolver extensions respect the existence of the history table if this option is enabled.
|
34
|
+
|
35
|
+
### Collision Resolvers
|
36
|
+
|
37
|
+
Two collision resolvers are included in Louisville. You can decide which to use based on the profile of your app. If you're app is read heavy and/or rarely colliding on write, :string\_sequence is fine for you. If your app is write heavy or deals with collisions often, :numeric\_sequence is a better choice.
|
38
|
+
|
39
|
+
**collision: :string_sequence**
|
40
|
+
|
41
|
+
To use the string sequence collision resolver, configure your schema like so:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
add_column :players, :slug, :string
|
45
|
+
add_index :players, :slug, unique: true
|
46
|
+
```
|
47
|
+
|
48
|
+
**collision: :numeric_sequence**
|
49
|
+
|
50
|
+
To use this collision resolver configure your schema like so:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
add_column :players, :slug, :string
|
54
|
+
add_column :players, :slug_sequence, :integer, default: 1
|
55
|
+
add_index :players, [:slug, :slug_sequence], unique: true
|
56
|
+
```
|
57
|
+
|
58
|
+
### Setter
|
59
|
+
|
60
|
+
I found this to be a shortcoming of other libraries and intended to make it dead simple to implement. Many times you want you users to be able to choose their slugs, skipping any kind of collision resolution. In Louisville it's simple:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
class Player
|
64
|
+
slug :name, setter: :desired_slug
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
Now, you can simply do `player.desired_slug = params[:username]` and if available the record's slug will be set, otherwise the record will have a validation error.
|
69
|
+
|
70
|
+
### History
|
71
|
+
|
72
|
+
You'll need to create a slug table (no model) for this to work:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
create_table :slugs do |t|
|
76
|
+
t.string :sluggable_type
|
77
|
+
t.integer :sluggable_id
|
78
|
+
t.string :slug_base
|
79
|
+
t.integer :slug_sequence, :default => 1
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
Now with the table created, you can change a records slug and the previous value(s) will be stored in the slugs table. Note that the current slug is not stored in the table.
|
84
|
+
|
85
|
+
|
86
|
+
## Creating your own extension
|
87
|
+
|
88
|
+
You can provide your own extension to Louisville by creating a module within the Louisville::Extensions namespace. For instance.
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
module Louisville
|
92
|
+
module Extensions
|
93
|
+
module Upcase
|
94
|
+
self.included(base)
|
95
|
+
base.class_eval do
|
96
|
+
alias_method_chain :sanitize_louisville_slug, :upcase
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
|
102
|
+
def sanitize_louisville_slug_with_upcase(value)
|
103
|
+
value = sanitize_louisville_slug_without_upcase(value).upcase
|
104
|
+
value = value.gsub(/[\d]+/, '') if louisville_config.options_for(:upcase)[:remove_numbers]
|
105
|
+
value
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
Then, in your class you would do:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
class Player
|
116
|
+
include Louisville::Slugger
|
117
|
+
|
118
|
+
slug :name, upcase: true
|
119
|
+
# or if you wanted to provide options for the module...
|
120
|
+
slug :name, upcase: {remove_numbers: true}
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
Classes with the Louisville::Slugger module have access to the `louisville_config` both at the class and instance level. The louisville config provides you with a way to grab the options for your (or any) extension as well as determine what extensions are enabled. For instance, if you wanted to know if the history extension was being used you would do `louisville_config.option?(:history)`.
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in makara.gemspec
|
4
|
+
gemspec :path => '../'
|
5
|
+
|
6
|
+
|
7
|
+
gem 'rake'
|
8
|
+
gem 'activerecord', '3.0.20'
|
9
|
+
gem 'rspec'
|
10
|
+
gem 'i18n', '~> 0.5.0'
|
11
|
+
|
12
|
+
gem 'mysql2', '0.2.11', :platform => :ruby
|
13
|
+
gem 'activerecord-jdbcmysql-adapter', :platform => :jruby
|
@@ -0,0 +1,12 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in makara.gemspec
|
4
|
+
gemspec :path => '../'
|
5
|
+
|
6
|
+
|
7
|
+
gem 'rake'
|
8
|
+
gem 'activerecord', '3.1.12'
|
9
|
+
gem 'rspec'
|
10
|
+
gem 'i18n', '0.6.11'
|
11
|
+
gem 'mysql2', :platform => :ruby
|
12
|
+
gem 'activerecord-jdbcmysql-adapter', :platform => :jruby
|
@@ -0,0 +1,12 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in makara.gemspec
|
4
|
+
gemspec :path => '../'
|
5
|
+
|
6
|
+
|
7
|
+
gem 'rake'
|
8
|
+
gem 'activerecord', '3.2.19'
|
9
|
+
gem 'rspec'
|
10
|
+
gem 'i18n', '0.6.11'
|
11
|
+
gem 'mysql2', :platform => :ruby
|
12
|
+
gem 'activerecord-jdbcmysql-adapter', :platform => :jruby
|
@@ -0,0 +1,12 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in makara.gemspec
|
4
|
+
gemspec :path => '../'
|
5
|
+
|
6
|
+
|
7
|
+
gem 'rake'
|
8
|
+
gem 'activerecord', '4.0.10'
|
9
|
+
gem 'rspec'
|
10
|
+
gem 'rack'
|
11
|
+
gem 'mysql2', :platform => :ruby
|
12
|
+
gem 'activerecord-jdbcmysql-adapter', :platform => :jruby
|
@@ -0,0 +1,12 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in makara.gemspec
|
4
|
+
gemspec :path => '../'
|
5
|
+
|
6
|
+
|
7
|
+
gem 'rake'
|
8
|
+
gem 'activerecord', '4.1.6'
|
9
|
+
gem 'rspec'
|
10
|
+
gem 'rack'
|
11
|
+
gem 'mysql2', :platform => :ruby
|
12
|
+
gem 'activerecord-jdbcmysql-adapter', :platform => :jruby
|
@@ -0,0 +1,12 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in makara.gemspec
|
4
|
+
gemspec :path => '../'
|
5
|
+
|
6
|
+
|
7
|
+
gem 'rake'
|
8
|
+
gem 'activerecord', '~> 4.2.0'
|
9
|
+
gem 'rspec'
|
10
|
+
gem 'rack'
|
11
|
+
gem 'mysql2', :platform => :ruby
|
12
|
+
gem 'activerecord-jdbcmysql-adapter', :platform => :jruby
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Louisville
|
2
|
+
module CollisionResolvers
|
3
|
+
class Abstract
|
4
|
+
|
5
|
+
def initialize(instance, options = {})
|
6
|
+
@instance = instance
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def unique?
|
12
|
+
unique_in_table? && unique_in_history?
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def next_valid_slug
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def provides_collision_solution?
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def assign_slug(val)
|
27
|
+
@instance.send("#{config[:column]}=", val)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def read_slug
|
32
|
+
@instance.send(config[:column])
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def slug_changed?
|
37
|
+
@instance.send("#{config[:column]}_changed?")
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
def slug_base(compare = nil)
|
47
|
+
Louisville::Util.slug_base(compare || @instance.louisville_slug.to_s)
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def slug_sequence(compare = nil)
|
52
|
+
Louisville::Util.slug_sequence(compare || @instance.louisville_slug.to_s)
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
def unique_in_history?
|
58
|
+
return true unless config.option?(:history)
|
59
|
+
|
60
|
+
scope = ::Louisville::Slug.where(:sluggable_type => ::Louisville::Util.polymorphic_name(klass))
|
61
|
+
scope = scope.where(:slug_base => slug_base)
|
62
|
+
scope = scope.where(:slug_sequence => slug_sequence)
|
63
|
+
scope = scope.where("#{Louisville::Slug.quoted_table_name}.sluggable_id <> ?", @instance.id) if @instance.persisted?
|
64
|
+
|
65
|
+
!scope.exists?
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def unique_in_table?
|
70
|
+
scope = klass.where(config[:column] => @instance.louisville_slug)
|
71
|
+
scope = scope.where("#{klass.quoted_table_name}.#{klass.primary_key} <> ?", @instance.id) if @instance.persisted?
|
72
|
+
|
73
|
+
!scope.exists?
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def config
|
78
|
+
@instance.louisville_config
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
def klass
|
83
|
+
@instance.class
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def next_slug(slug)
|
88
|
+
base = slug_base(slug)
|
89
|
+
sequence = slug_sequence(slug)
|
90
|
+
|
91
|
+
"#{base}--#{sequence + 1}"
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
def provide_latest_slug(*slugs)
|
96
|
+
slugs.compact.sort{|a,b| a.length == b.length ? a <=> b : a.length - b.length }.last
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Louisville
|
2
|
+
module CollisionResolvers
|
3
|
+
class NumericSequence < Abstract
|
4
|
+
|
5
|
+
|
6
|
+
def provides_collision_solution?
|
7
|
+
true
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def next_valid_slug
|
12
|
+
provide_latest_slug(next_valid_slug_from_table, next_valid_slug_from_history)
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def assign_slug(val)
|
17
|
+
base, seq = Louisville::Util.slug_parts(val)
|
18
|
+
|
19
|
+
@instance.send("#{config[:column]}=", base)
|
20
|
+
@instance.send("#{config[:column]}_sequence=", seq)
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def read_slug
|
25
|
+
col = config[:column]
|
26
|
+
seq = "#{col}_sequence"
|
27
|
+
|
28
|
+
"#{@instance.send(col)}--#{@instance.send(seq)}"
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def slug_changed?
|
33
|
+
@instance.send("#{config[:column]}_changed?") || @instance.send("#{@config[:column]}_sequence_changed?")
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
|
41
|
+
|
42
|
+
def unique_in_table?
|
43
|
+
|
44
|
+
base_method = config[:column]
|
45
|
+
sequence_method = "#{config[:column]}_sequence"
|
46
|
+
|
47
|
+
scope = klass.where(config[:column] => @instance.send(base_method), sequence_method => @instance.send(sequence_method))
|
48
|
+
scope = scope.where("#{klass.quoted_table_name}.#{klass.primary_key} <> ?", @instance.id) if @instance.persisted?
|
49
|
+
|
50
|
+
!scope.exists?
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def next_valid_slug_from_table
|
55
|
+
base_field = config[:column]
|
56
|
+
sequ_field = "#{config[:column]}_sequence"
|
57
|
+
|
58
|
+
scope = klass.where(base_field => slug_base)
|
59
|
+
scope = scope.where("#{klass.quoted_table_name}.#{klass.primary_key} <> ?", @instance.id) if @instance.persisted?
|
60
|
+
|
61
|
+
"#{slug_base}--#{scope.maximum(sequ_field).to_i + 1}"
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def next_valid_slug_from_history
|
66
|
+
return nil unless config.option?(:history)
|
67
|
+
|
68
|
+
scope = ::Louisville::Slug.where(:sluggable_type => ::Louisville::Util.polymorphic_name(klass))
|
69
|
+
scope = scope.where(:slug_base => slug_base)
|
70
|
+
scope = scope.where("#{Louisville::Slug.quoted_table_name}.sluggable_id <> ?", @instance.id) if @instance.persisted?
|
71
|
+
|
72
|
+
"#{slug_base}--#{scope.maximum(:slug_sequence).to_i + 1}"
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Louisville
|
2
|
+
module CollisionResolvers
|
3
|
+
class StringSequence < Abstract
|
4
|
+
|
5
|
+
|
6
|
+
def provides_collision_solution?
|
7
|
+
true
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def next_valid_slug
|
12
|
+
provide_latest_slug(next_valid_slug_from_table, next_valid_slug_from_history)
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
def next_valid_slug_from_table
|
22
|
+
length_command = klass.connection.adapter_name =~ /sqlserver/i ? 'LEN' : 'LENGTH'
|
23
|
+
pk_col = "#{klass.quoted_table_name}.#{klass.primary_key}"
|
24
|
+
col = "#{klass.quoted_table_name}.#{config[:column]}"
|
25
|
+
|
26
|
+
scope = klass.select("#{col}")
|
27
|
+
scope = scope.where("#{col} = ? OR #{col} LIKE ?", slug_base, "#{slug_base}--%")
|
28
|
+
scope = scope.order("#{length_command}(#{col}) DESC, #{col} DESC")
|
29
|
+
|
30
|
+
return nil unless record = scope.first
|
31
|
+
|
32
|
+
next_slug(record.louisville_slug)
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def next_valid_slug_from_history
|
37
|
+
return nil unless config.option?(:history)
|
38
|
+
|
39
|
+
scope = ::Louisville::Slug.where(:sluggable_type => ::Louisville::Util.polymorphic_name(klass))
|
40
|
+
scope = scope.where(:slug_base => slug_base)
|
41
|
+
scope = scope.where("#{Louisville::Slug.quoted_table_name}.sluggable_id <> ?", @instance.id) if @instance.persisted?
|
42
|
+
|
43
|
+
"#{slug_base}--#{scope.maximum(:slug_sequence).to_i + 1}"
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Louisville
|
2
|
+
class Config
|
3
|
+
|
4
|
+
DEFAULTS = {
|
5
|
+
:column => :slug,
|
6
|
+
:finder => true,
|
7
|
+
:collision => :none,
|
8
|
+
:setter => false,
|
9
|
+
:history => false
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
|
13
|
+
def initialize(field, options = {})
|
14
|
+
@options = DEFAULTS.merge(options).merge(:field => field)
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def hook!(klass)
|
19
|
+
modules.each do |modul|
|
20
|
+
modul.configure_default_options(@options) if modul.respond_to?(:configure_default_options)
|
21
|
+
klass.send(:include, modul)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def option?(key)
|
27
|
+
!!option(key)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def option(key)
|
32
|
+
@options[key]
|
33
|
+
end
|
34
|
+
alias_method :[], :option
|
35
|
+
|
36
|
+
|
37
|
+
def options_for(key)
|
38
|
+
return self[key] if self[key] === Hash
|
39
|
+
{}
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def extension_keys
|
44
|
+
(@options.keys - [:column, :field])
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
def modules
|
54
|
+
extension_keys.map do |option|
|
55
|
+
::Louisville::Extensions.const_get(option.to_s.classify) if self.option?(option)
|
56
|
+
end.compact
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|