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
@@ -0,0 +1,87 @@
|
|
1
|
+
#
|
2
|
+
# The collision extension handles collisions as part of the save process. It uses a CollisionResolver
|
3
|
+
# object to handle the heavy lifting.
|
4
|
+
#
|
5
|
+
# Provide `collision: true`, or `collision: :name_of_collision_resolver` to your slug() invocation.
|
6
|
+
# No options are used.
|
7
|
+
#
|
8
|
+
|
9
|
+
module Louisville
|
10
|
+
module Extensions
|
11
|
+
module Collision
|
12
|
+
|
13
|
+
|
14
|
+
def self.configure_default_options(options)
|
15
|
+
options[:collision] = :string_sequence if options[:collision] == true
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def self.included(base)
|
20
|
+
base.class_eval do
|
21
|
+
alias_method_chain :louisville_slug, :resolver
|
22
|
+
alias_method_chain :louisville_slug=, :resolver
|
23
|
+
alias_method_chain :louisville_slug_changed?, :resolver
|
24
|
+
alias_method_chain :validate_louisville_slug, :resolver
|
25
|
+
|
26
|
+
before_validation :make_louisville_slug_unique, :if => :should_uniquify_louisville_slug?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def louisville_slug_with_resolver
|
32
|
+
louisville_collision_resolver.read_slug
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
|
40
|
+
|
41
|
+
def louisville_slug_with_resolver=(val)
|
42
|
+
louisville_collision_resolver.assign_slug(val)
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def louisville_slug_changed_with_resolver?
|
47
|
+
louisville_collision_resolver.slug_changed?
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def louisville_collision_resolver
|
52
|
+
@louisville_collision_resolver ||= begin
|
53
|
+
class_name = louisville_config.options_for(:collision)[:resolver] || louisville_config[:collision]
|
54
|
+
klass = Louisville::CollisionResolvers.const_get(:"#{class_name.to_s.classify}")
|
55
|
+
klass.new(self, louisville_config.options_for(:collision))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def make_louisville_slug_unique
|
61
|
+
return if louisville_collision_resolver.unique?
|
62
|
+
|
63
|
+
self.louisville_slug = louisville_collision_resolver.next_valid_slug
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def should_uniquify_louisville_slug?
|
68
|
+
return false if louisville_config.option?(:setter) && desired_louisville_slug
|
69
|
+
|
70
|
+
louisville_collision_resolver.provides_collision_solution? && louisville_slug_changed?
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def validate_louisville_slug_with_resolver
|
75
|
+
return false unless validate_louisville_slug_without_resolver
|
76
|
+
|
77
|
+
unless louisville_collision_resolver.unique?
|
78
|
+
self.errors.add(louisville_config[:column], :taken)
|
79
|
+
return false
|
80
|
+
end
|
81
|
+
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
#
|
2
|
+
# The finder extension allows your class to use find('slug') to query for your record.
|
3
|
+
# If the history extension is enabled it will also query the history table to see if
|
4
|
+
# there are any previous slugs that match.
|
5
|
+
#
|
6
|
+
# The finder option is enabled by default, to disable provide `finder: false` to your slug() invocation.
|
7
|
+
# No options are used.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Louisville
|
11
|
+
module Extensions
|
12
|
+
module Finder
|
13
|
+
|
14
|
+
|
15
|
+
def self.included(base)
|
16
|
+
base.extend ClassMethods
|
17
|
+
base.class_eval do
|
18
|
+
class << self
|
19
|
+
alias_method_chain :relation, :louisville_finder
|
20
|
+
|
21
|
+
if ActiveRecord::VERSION::MAJOR >= 4 && ActiveRecord::VERSION::MINOR >= 2
|
22
|
+
alias_method_chain :find, :louisville_finder
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
|
33
|
+
def find_with_louisville_finder(*args)
|
34
|
+
return find_without_lousville_finder(*args) if args.length != 1
|
35
|
+
|
36
|
+
id = args[0]
|
37
|
+
id = id.id if ActiveRecord::Base === id
|
38
|
+
return find_without_louisville_finder(*args) if Louisville::Util.numeric?(id)
|
39
|
+
|
40
|
+
relation_with_louisville_finder.find_one(id)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def relation_with_louisville_finder
|
46
|
+
rel = relation_without_louisville_finder
|
47
|
+
rel.extend RelationMethods unless rel.respond_to?(:find_one_with_louisville)
|
48
|
+
rel
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
module RelationMethods
|
55
|
+
|
56
|
+
|
57
|
+
def find_one(id)
|
58
|
+
id = id.id if ActiveRecord::Base === id
|
59
|
+
|
60
|
+
return super(id) if Louisville::Util.numeric?(id)
|
61
|
+
|
62
|
+
seq_column = "#{louisville_config[:column]}_sequence"
|
63
|
+
|
64
|
+
if self.column_names.include?(seq_column)
|
65
|
+
base, seq = Louisville::Util.slug_parts(id)
|
66
|
+
record = self.where(louisville_config[:column] => base, seq_column => seq).first
|
67
|
+
else
|
68
|
+
record = self.where(louisville_config[:column] => id).first
|
69
|
+
end
|
70
|
+
|
71
|
+
return record if record
|
72
|
+
|
73
|
+
if louisville_config.option?(:history)
|
74
|
+
|
75
|
+
base, seq = Louisville::Util.slug_parts(id)
|
76
|
+
|
77
|
+
record = Louisville::Slug.where(:slug_base => base, :slug_sequence => seq, :sluggable_type => ::Louisville::Util.polymorphic_name(self)).select(:sluggable_id).first
|
78
|
+
super(record.try(:sluggable_id) || id)
|
79
|
+
else
|
80
|
+
return super(id)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
def exists?(id = :none)
|
86
|
+
id = id.id if ActiveRecord::Base === id
|
87
|
+
|
88
|
+
return super(id) if Louisville::Util.numeric?(id)
|
89
|
+
|
90
|
+
if String === id
|
91
|
+
return true if super(louisville_config[:column] => id)
|
92
|
+
return super(id) unless louisville_config.option?(:history)
|
93
|
+
|
94
|
+
base, seq = Louisville::Util.slug_parts(id)
|
95
|
+
|
96
|
+
Louisville::Slug.where(:slug_base => base, :slug_sequence => seq, :sluggable_type => ::Louisville::Util.polymorphic_name(name)).exists?
|
97
|
+
|
98
|
+
elsif ActiveRecord::VERSION::MAJOR == 3
|
99
|
+
return super(id == :none ? false : id)
|
100
|
+
else
|
101
|
+
return super(id)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
#
|
2
|
+
# The history extension stores previous slug values in the `slugs` table.
|
3
|
+
# It provides instances with a `historical_slugs` association that return Louisville::Slug records.
|
4
|
+
# Whenever a slug is changed the previous value is added to the table, the current value is never
|
5
|
+
# present in the history table.
|
6
|
+
#
|
7
|
+
# Provide `history: true` to your slug() invocation.
|
8
|
+
# No options are used.
|
9
|
+
#
|
10
|
+
|
11
|
+
module Louisville
|
12
|
+
module Extensions
|
13
|
+
module History
|
14
|
+
|
15
|
+
|
16
|
+
def self.included(base)
|
17
|
+
base.class_eval do
|
18
|
+
|
19
|
+
# provide an association for easy lookup, joining, etc.
|
20
|
+
has_many :historical_slugs, :class_name => 'Louisville::Slug', :dependent => :destroy, :as => :sluggable
|
21
|
+
|
22
|
+
# If our slug has changed we should manage the history.
|
23
|
+
after_save :delete_matching_historical_slug, :if => :louisville_slug_changed?
|
24
|
+
after_save :generate_historical_slug, :if => :louisville_slug_changed?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
# First, we delete any previous slugs that this record owned that match the current slug.
|
35
|
+
# This allows a record to return to a previous slug without duplication in the history table.
|
36
|
+
def delete_matching_historical_slug
|
37
|
+
current_value = self.louisville_slug
|
38
|
+
|
39
|
+
return unless current_value
|
40
|
+
|
41
|
+
base, seq = Louisville::Util.slug_parts(current_value)
|
42
|
+
|
43
|
+
self.historical_slugs.where(:slug_base => base, :slug_sequence => seq).delete_all
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
# Then we generate a new historical slug for the previous value (if there is one).
|
48
|
+
def generate_historical_slug
|
49
|
+
previous_value = self.send("#{louisville_config[:column]}_was")
|
50
|
+
|
51
|
+
return unless previous_value
|
52
|
+
|
53
|
+
base, seq = Louisville::Util.slug_parts(previous_value)
|
54
|
+
|
55
|
+
self.historical_slugs.create(:slug_base => base, :slug_sequence => seq)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#
|
2
|
+
# Enables the slug to be dictated by a setter on the instance. If a setter is provided the collision
|
3
|
+
# extension will not uniquify but rather add a validation error.
|
4
|
+
#
|
5
|
+
# Provide `setter: true` or `setter: :name_of_accessor` to your slug() invocation.
|
6
|
+
# No options are used.
|
7
|
+
#
|
8
|
+
|
9
|
+
module Louisville
|
10
|
+
module Extensions
|
11
|
+
module Setter
|
12
|
+
|
13
|
+
|
14
|
+
def self.configure_default_options(options)
|
15
|
+
options[:setter] = "desired_#{options[:column]}" if options[:setter] == true
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def self.included(base)
|
20
|
+
base.class_eval do
|
21
|
+
attr_accessor :desired_louisville_slug
|
22
|
+
alias_method :"#{louisville_config[:setter]}=", :desired_louisville_slug=
|
23
|
+
|
24
|
+
if respond_to?(:accessible_attributes) && accessible_attributes.any?
|
25
|
+
attr_accessible :desired_louisville_slug, louisville_config[:setter].to_sym
|
26
|
+
end
|
27
|
+
|
28
|
+
alias_method_chain :extract_louisville_slug_value_from_field, :setter
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
def extract_louisville_slug_value_from_field_with_setter
|
39
|
+
self.desired_louisville_slug || extract_louisville_slug_value_from_field_without_setter
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
module Louisville
|
2
|
+
class Slug < ActiveRecord::Base
|
3
|
+
self.table_name = :slugs
|
4
|
+
|
5
|
+
validates :sluggable_type, :sluggable_id, :slug_base, :slug_sequence, :presence => true
|
6
|
+
validates :slug_base, :uniqueness => {:scope => [:sluggable_id, :sluggable_type, :slug_sequence]}
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Louisville
|
2
|
+
module Slugger
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
base.class_eval do
|
7
|
+
|
8
|
+
before_validation :apply_louisville_slug
|
9
|
+
|
10
|
+
validate :validate_louisville_slug, :if => :needs_to_validate_louisville_slug?
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
|
18
|
+
def slug(field, options = {})
|
19
|
+
@louisville_slugger = ::Louisville::Config.new(field, options)
|
20
|
+
@louisville_slugger.hook!(self)
|
21
|
+
@louisville_slugger
|
22
|
+
end
|
23
|
+
|
24
|
+
def louisville_config
|
25
|
+
@louisville_slugger || (superclass.respond_to?(:louisville_config) ? superclass.louisville_config : nil)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
def louisville_slug
|
33
|
+
self.send(louisville_config[:column])
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def louisville_config
|
38
|
+
self.class.louisville_config
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
def louisville_slug=(val)
|
48
|
+
self.send("#{louisville_config[:column]}=", val)
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def louisville_slug_changed?
|
53
|
+
self.send("#{louisville_config[:column]}_changed?")
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def apply_louisville_slug
|
58
|
+
value = extract_louisville_slug_value_from_field
|
59
|
+
value = sanitize_louisville_slug(value) if value
|
60
|
+
|
61
|
+
# the value may have changed but the parameterized value may be the same
|
62
|
+
# charlie vs Charlie.
|
63
|
+
if self.louisville_slug
|
64
|
+
base = Louisville::Util.slug_base(self.louisville_slug)
|
65
|
+
|
66
|
+
# if the base hasn't changed let's not set the value since doing so may incur extra cost.
|
67
|
+
# namely, the numeric_sequence resolver would have to determine and apply the sequence.
|
68
|
+
if base != value
|
69
|
+
self.louisville_slug = value
|
70
|
+
end
|
71
|
+
|
72
|
+
else
|
73
|
+
self.louisville_slug = value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
def sanitize_louisville_slug(value)
|
79
|
+
value.parameterize
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def extract_louisville_slug_value_from_field
|
84
|
+
self.send(louisville_config[:field])
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
def validate_louisville_slug
|
89
|
+
|
90
|
+
if louisville_slug.blank?
|
91
|
+
errors.add(louisville_config[:column], :blank)
|
92
|
+
return false
|
93
|
+
end
|
94
|
+
|
95
|
+
true
|
96
|
+
end
|
97
|
+
|
98
|
+
def needs_to_validate_louisville_slug?
|
99
|
+
new_record? || louisville_slug_changed?
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Louisville
|
2
|
+
class Util
|
3
|
+
|
4
|
+
|
5
|
+
SLUG_MATCHER = /^(.+)--([\d]+)$/
|
6
|
+
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def numeric?(id)
|
11
|
+
Integer === id || !!(id.to_s =~ /^[\d]+$/)
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def slug_base(compare)
|
16
|
+
compare =~ SLUG_MATCHER
|
17
|
+
$1 || compare
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def slug_sequence(compare)
|
22
|
+
compare =~ SLUG_MATCHER
|
23
|
+
[$2.to_i, 1].max
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def slug_parts(compare)
|
28
|
+
[slug_base(compare), slug_sequence(compare)]
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def polymorphic_name(klass)
|
33
|
+
klass.base_class.sti_name
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
data/lib/louisville.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "louisville/version"
|
2
|
+
|
3
|
+
module Louisville
|
4
|
+
|
5
|
+
autoload :Config, 'louisville/config'
|
6
|
+
autoload :Slug, 'louisville/slug'
|
7
|
+
autoload :Slugger, 'louisville/slugger'
|
8
|
+
autoload :Util, 'louisville/util'
|
9
|
+
|
10
|
+
module Extensions
|
11
|
+
|
12
|
+
autoload :Collision, 'louisville/extensions/collision'
|
13
|
+
autoload :Finder, 'louisville/extensions/finder'
|
14
|
+
autoload :History, 'louisville/extensions/history'
|
15
|
+
autoload :Setter, 'louisville/extensions/setter'
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
module CollisionResolvers
|
20
|
+
|
21
|
+
autoload :Abstract, 'louisville/collision_resolvers/abstract'
|
22
|
+
autoload :None, 'louisville/collision_resolvers/none'
|
23
|
+
autoload :NumericSequence, 'louisville/collision_resolvers/numeric_sequence'
|
24
|
+
autoload :StringSequence, 'louisville/collision_resolvers/string_sequence'
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/louisville.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'louisville/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "louisville"
|
8
|
+
gem.version = Louisville::VERSION
|
9
|
+
gem.authors = ["Mike Nelson"]
|
10
|
+
gem.email = ["mike@mnelson.io"]
|
11
|
+
gem.description = %q{A simple and extensible slugging library for ActiveRecord.}
|
12
|
+
gem.summary = %q{Simple and Extensible Slugging}
|
13
|
+
gem.homepage = "http://github.com/mnelson/louisville"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency 'activerecord', '>= 3.0'
|
21
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Louisville::Extensions::Collision do
|
4
|
+
|
5
|
+
class MinimalCollisionUser < ActiveRecord::Base
|
6
|
+
self.table_name = :users
|
7
|
+
|
8
|
+
include Louisville::Slugger
|
9
|
+
|
10
|
+
slug :name, :collision => :none
|
11
|
+
end
|
12
|
+
|
13
|
+
class MinimalCollisionSequenceUser < ActiveRecord::Base
|
14
|
+
self.table_name = :users
|
15
|
+
|
16
|
+
include Louisville::Slugger
|
17
|
+
|
18
|
+
slug :name, :collision => :string_sequence, :setter => true
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:mcu) { MinimalCollisionUser.new }
|
23
|
+
let(:mcsu) { MinimalCollisionSequenceUser.new }
|
24
|
+
let(:resolver) {
|
25
|
+
mcsu.send(:louisville_collision_resolver)
|
26
|
+
}
|
27
|
+
|
28
|
+
it 'should ensure the slug is unique' do
|
29
|
+
mcu.name = 'pete'
|
30
|
+
expect(mcu.save).to eq(true)
|
31
|
+
|
32
|
+
u2 = MinimalCollisionUser.new
|
33
|
+
u2.name = 'pete'
|
34
|
+
expect(u2.save).to eq(false)
|
35
|
+
|
36
|
+
expect(u2.errors[:slug].to_s).to match(/has already been taken/)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should choose a collision resolver based on the config' do
|
40
|
+
expect(resolver).to be_a(Louisville::CollisionResolvers::StringSequence)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should override the slug reader to read from the resolver' do
|
44
|
+
expect(resolver).to receive(:read_slug).once
|
45
|
+
mcsu.louisville_slug
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should override the slug writer to apply via the resolver' do
|
49
|
+
expect(resolver).to receive(:assign_slug).with('test').once
|
50
|
+
mcsu.send(:louisville_slug=, 'test')
|
51
|
+
end
|
52
|
+
|
53
|
+
context "#should_uniquify_louisville_slug?" do
|
54
|
+
|
55
|
+
it 'should not uniquify if the resolver does not provide a solution' do
|
56
|
+
resolver = double(:provides_collision_solution? => false)
|
57
|
+
allow(mcu).to receive(:louisville_collision_resolver){ resolver }
|
58
|
+
allow(mcu).to receive(:louisville_slug_changed?){ true }
|
59
|
+
expect(mcu.send(:should_uniquify_louisville_slug?)).to eq(false)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should uniquify if the resolver provides a solution' do
|
63
|
+
resolver = double(:provides_collision_solution? => true)
|
64
|
+
allow(mcu).to receive(:louisville_collision_resolver){ resolver }
|
65
|
+
allow(mcu).to receive(:louisville_slug_changed?){ true }
|
66
|
+
expect(mcu.send(:should_uniquify_louisville_slug?)).to eq(true)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should not uniquify if the setter extension is used and present' do
|
70
|
+
allow(mcsu).to receive(:louisville_slug_changed?){ true }
|
71
|
+
expect(mcsu.send(:should_uniquify_louisville_slug?)).to eq(true)
|
72
|
+
mcsu.desired_slug = 'test'
|
73
|
+
expect(mcsu.send(:should_uniquify_louisville_slug?)).to eq(false)
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
context "collision resolver utilities" do
|
79
|
+
it "should choose the correct 'latest' slug" do
|
80
|
+
expect(resolver.send(:provide_latest_slug, 'dog-b--4', 'dog-b--10')).to eq('dog-b--10')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
data/spec/column_spec.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Louisville::Slugger column variations' do
|
4
|
+
|
5
|
+
class ColumnVariationUser < ActiveRecord::Base
|
6
|
+
self.table_name = :users
|
7
|
+
|
8
|
+
include Louisville::Slugger
|
9
|
+
|
10
|
+
slug :name, :column => :other_slug, :history => true
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should use the provided column as the storage location' do
|
14
|
+
u = ColumnVariationUser.new
|
15
|
+
u.name = 'bob'
|
16
|
+
expect(u.save).to eq(true)
|
17
|
+
|
18
|
+
expect(u.slug).to eq(nil)
|
19
|
+
expect(u.other_slug).to eq('bob')
|
20
|
+
expect(u.other_slug_sequence).to eq(1)
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
it 'should not impact the history columns' do
|
25
|
+
u = ColumnVariationUser.new
|
26
|
+
u.name = 'bill'
|
27
|
+
expect(u.save).to eq(true)
|
28
|
+
|
29
|
+
u.name = 'billy'
|
30
|
+
expect(u.save).to eq(true)
|
31
|
+
|
32
|
+
history = Louisville::Slug.last
|
33
|
+
|
34
|
+
expect(history.slug_base).to eq('bill')
|
35
|
+
expect(history.slug_sequence).to eq(1)
|
36
|
+
|
37
|
+
expect(ColumnVariationUser.find('billy')).to eq(u)
|
38
|
+
expect(ColumnVariationUser.find('bill')).to eq(u)
|
39
|
+
end
|
40
|
+
end
|