has_unique_slug 0.1.3 → 0.1.4

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.
data/Gemfile CHANGED
@@ -2,4 +2,3 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in has_unique_slug.gemspec
4
4
  gemspec
5
- gem 'rspec'
data/README.md CHANGED
@@ -9,42 +9,30 @@ Tested and working on Rails 3.1.x
9
9
 
10
10
  ## Usage
11
11
 
12
- Assume you have a Post model that has a title and slug column, you can use the following to uniquely parameterize title:
13
-
14
12
  class Post < ActiveRecord::Base
15
13
  has_unique_slug
16
14
  end
17
-
18
15
 
19
- A unique slug will be generated automatically on creation by calling parameterize on title.
20
- If the generated slug is not unique, a number is added onto the end to ensure uniqueness. The series starts at 2 and increments up by one until a unique slug is found.
21
- If a slug is already specified, this slug will be used however the above rules still apply for incrementing the slug until a unique one is found.
22
- Ex. Post 1 has title "Sample Post" which would then generate slug "sample-post"
23
- Post 2 has also has title "Sample Post" which then would generate slug "sample-post-2"
16
+ - by default, the column `title` is assumed as the identifier, and `slug` is used to store the unique slug
17
+ - `title.paramterize` is called to generate the slug unless a slug has already been assigned, in which case parameterize is called on the provided slug
18
+ - if a slug is not unique in the database, a suffix is appended on the end in the format "-n" where n starts at 2 and progresses ad infinitum until a unique slug is found.
24
19
 
25
- You can specify which column to use to generate the slug and which column to use to store the slug. Below is the default:
20
+ You can specify which column to use to generate the slug and which column to use to store the slug. For example:
26
21
 
27
22
  class Post < ActiveRecord::Base
28
- # the column slug will store the slug, title.parameterize will be called to build the slug
29
- has_unique_slug :slug, :title
23
+ has_unique_slug :column => :permalink, :subject => :name
24
+ # will store the unique slug in the column `permalink` created from `name`
30
25
  end
31
26
 
32
- The entire argument list is `has_unique_slug(slug_column, title_column, options, &block)` however there are no options you can pass in at this time.
33
-
34
- If only 1 argument is given, use that column to store the slug:
35
-
36
- class Post < ActiveRecord::Base
37
- has_unique_slug :permalink # Uses the permalink column to store the slug
38
- end
39
-
40
- Optionally, a block can be provided to generate the slug:
27
+ Optionally, a Proc can be used instead of a column name to create the slug:
41
28
 
42
29
  class Car < ActiveRecord::Base
43
- has_unique_slug {|car| "#{car.year} #{car.name}"}
30
+ has_unique_slug :subject => Proc.new {|car| "#{car.year}-#{car.name}"}
44
31
  end
45
- Note the space: parameterize will be called on the result of the block to ensure the slug is url friendly.
32
+ You do not have to call parameterize on name, this will be done automatically. (You don't even need to add dash, a space will work fine)
33
+
46
34
 
47
- You do not have to modify your controller to get this to work:
35
+ You do not have to modify your controller to find records:
48
36
 
49
37
  class PostsController < ApplicationController
50
38
 
@@ -56,11 +44,14 @@ You do not have to modify your controller to get this to work:
56
44
  end
57
45
  end
58
46
 
59
- Then you may use all your standard url helpers as normal.
60
- Ex. If a `post` has a title "Sample Post" and a slug "sample-post", the helper `post_path(post)` will create /posts/sample-post
47
+ All the standard url helper methods will still work since `to_param` is overridden to output the slug
48
+
49
+ # Ex.
50
+ post = Post.create! :title => "Sample Post"
51
+ post_path(post) # /posts/sample-post
61
52
 
62
53
  ## TODO:
63
54
 
64
- - Would like to write some tests.
65
- - Would like to be able to specify scope for uniqueness
66
- - Possibly consider optimizing the method to ensure uniqueness
55
+ - Add support for scopes
56
+ - Add support for database versioning
57
+ - Consider optimizing the method to ensure a unique slug
@@ -19,6 +19,8 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ["lib"]
20
20
 
21
21
  # specify any dependencies here; for example:
22
- # s.add_development_dependency "rspec"
22
+ s.add_development_dependency "rspec"
23
+ s.add_development_dependency "activerecord"
24
+ s.add_development_dependency "sqlite3"
23
25
  # s.add_runtime_dependency "rest-client"
24
26
  end
@@ -7,25 +7,26 @@ module HasUniqueSlug
7
7
 
8
8
  # Builds a slug from the subject_column unless a block is specified.
9
9
  # If a block is specified, the result of the block is returned.
10
- def build_slug(record, subject_column, &block)
11
- ( block_given? ? yield(record) : record[subject_column] ).parameterize
10
+ def build_slug(record, subject_column)
11
+ ( subject_column.is_a?(Proc) ? subject_column.call(record) : record[subject_column] ).parameterize
12
12
  end
13
13
 
14
14
  module ClassMethods
15
15
 
16
- def has_unique_slug(*args, &block)
16
+ def has_unique_slug args = {}
17
17
 
18
- options = { :scope => nil }
19
- options.merge! args.pop if args.last.is_a? Hash
20
- slug_column, subject_column = args
21
- slug_column ||= :slug
22
- subject_column ||= :title
18
+ # Setup default options
19
+ options = { :column => :slug, :subject => :title }
20
+ options.merge! args
21
+ slug_column, subject_column = options[:column], options[:subject]
23
22
 
24
- # Add ActiveRecord Callback
25
- before_save do |record|
23
+ # Use before_validates otherwise ActiveRecord uniqueness validations on the model will fail. Uniqueness is already guarneteed.
24
+ # It is not recommend to use a 'validates :slug, :uniqueness => true' validation because that will add unndeeded stress on the
25
+ # database.
26
+ before_validation do |record|
26
27
 
27
- # Add a slug if slug doesn't exist
28
- slug_prefix = record[slug_column].blank? ? build_slug(record, subject_column, &block) : record[slug_column].parameterize
28
+ # Create base slug
29
+ slug_prefix = record[slug_column].blank? ? build_slug(record, subject_column) : record[slug_column].parameterize
29
30
 
30
31
  # Ensure the current slug is unique in the database, if not, make it unqiue
31
32
  test_slug, i = slug_prefix, 1
@@ -34,7 +35,7 @@ module HasUniqueSlug
34
35
  test_slug = "#{slug_prefix}-#{(i += 1)}"
35
36
  end
36
37
 
37
- # Set the slug
38
+ # Set the slug in the record
38
39
  record[slug_column] = test_slug
39
40
  end
40
41
 
@@ -50,14 +51,11 @@ module HasUniqueSlug
50
51
  end
51
52
  EOV
52
53
 
53
- # Add find method to override ActiveRecord::Base.find
54
+ # Add find method to override ActiveRecord::Base.find.
55
+ # Note: find_by_id will still work to search for record by their database id.
54
56
  instance_eval do
55
57
  def find(*args)
56
- if args.length == 1
57
- where("#{slug_column} = ?", args.first).first
58
- else
59
- where("#{slug_column} IN (?)", args)
60
- end
58
+ args.length == 1 ? where(slug_column => args.first).first : where(slug_column => args)
61
59
  end
62
60
  end
63
61
  end
@@ -65,7 +63,8 @@ module HasUniqueSlug
65
63
  end
66
64
 
67
65
  module InstanceMethods
68
-
66
+
67
+ # Override to param method. Outputs the specified column (or slug by default) instead of the id
69
68
  def to_param
70
69
  self.send(self.class.slug_column)
71
70
  end
@@ -1,3 +1,3 @@
1
1
  module HasUniqueSlug
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
@@ -1,5 +1,104 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe HasUniqueSlug
4
- pending "Write test simulating active record"
3
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
4
+
5
+ def setup_db
6
+ ActiveRecord::Schema.define(:version => 1) do
7
+ create_table :standards do |t|
8
+ t.column :title, :string
9
+ t.column :slug, :string
10
+ end
11
+
12
+ create_table :customs do |t|
13
+ t.column :name, :string
14
+ t.column :permalink, :string
15
+ end
16
+ end
17
+ end
18
+
19
+ def teardown_db
20
+ ActiveRecord::Base.connection.tables.each do |t|
21
+ ActiveRecord::Base.connection.drop_table t
22
+ end
23
+ end
24
+
25
+ class Standard < ActiveRecord::Base
26
+ has_unique_slug
27
+
28
+ def self.table_name
29
+ "standards"
30
+ end
31
+ end
32
+
33
+ class Custom < ActiveRecord::Base
34
+ has_unique_slug :column => :permalink, :subject => :name
35
+
36
+ def self.table_name
37
+ "customs"
38
+ end
39
+ end
40
+
41
+ class Custom2 < ActiveRecord::Base
42
+ has_unique_slug :column => :permalink, :subject => Proc.new {|record| "zcvf #{record.name} zxvf"}
43
+
44
+ def self.table_name
45
+ "customs"
46
+ end
47
+ end
48
+
49
+ describe HasUniqueSlug do
50
+
51
+ before(:all) do
52
+ setup_db
53
+ end
54
+
55
+ after(:all) do
56
+ teardown_db
57
+ end
58
+
59
+ it "creates a unique slug" do
60
+ r = Standard.create! :title => "Sample Record"
61
+ r.slug.should == "sample-record"
62
+ end
63
+
64
+ it "should add incremental column if not unique" do
65
+ 2.upto 5 do |i|
66
+ r = Standard.create! :title => "Sample Record"
67
+ r.slug.should == "sample-record-#{i}"
68
+ end
69
+ end
70
+
71
+ it "should not increment the slug if the duplicate is itself" do
72
+ r = Standard.last
73
+ slug = r.slug
74
+ r.save.should be_true
75
+ r.slug.should == slug
76
+ end
77
+
78
+ it "should update slugs for non-standard implementation" do
79
+ r = Custom.create! :name => "Sample Record"
80
+ r.permalink.should == "sample-record"
81
+ 2.upto 5 do |i|
82
+ r = Custom.create! :name => "Sample Record"
83
+ r.permalink.should == "sample-record-#{i}"
84
+ end
85
+ r = Custom.last
86
+ slug = r.permalink
87
+ r.save.should be_true
88
+ r.permalink.should == slug
89
+ end
90
+
91
+ it "should update slugs based on the block if a block is provided" do
92
+ r = Custom2.create! :name => "Sample Record"
93
+ r.permalink.should == "zcvf-sample-record-zxvf"
94
+ 2.upto 5 do |i|
95
+ r = Custom2.create! :name => "Sample Record"
96
+ r.permalink.should == "zcvf-sample-record-zxvf-#{i}"
97
+ end
98
+ r = Custom2.last
99
+ slug = r.permalink
100
+ r.save.should be_true
101
+ r.permalink.should == slug
102
+ end
103
+
5
104
  end
data/spec/spec_helper.rb CHANGED
@@ -1,8 +1,33 @@
1
1
  require 'rubygems'
2
2
  require 'bundler/setup'
3
+ gem 'activerecord'
4
+ require 'active_record'
5
+ gem 'sqlite3'
6
+ require 'sqlite3'
3
7
 
4
8
  require 'has_unique_slug' # and any other gems you need
5
9
 
10
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
11
+
12
+ def setup_db
13
+ ActiveRecord::Schema.define(:version => 1) do
14
+ create_table :standard_setup do |t|
15
+ t.column :title, :string
16
+ t.column :slug, :string
17
+ end
18
+ create_table :customized_setup do |t|
19
+ t.column :name, :string
20
+ t.column :permalink, :string
21
+ end
22
+ end
23
+ end
24
+
25
+ def teardown_db
26
+ ActiveRecord::Base.connection.tables.each do |t|
27
+ ActiveRecord::Base.connection.drop_table t
28
+ end
29
+ end
30
+
6
31
  RSpec.configure do |config|
7
- # some (optional) config here
32
+
8
33
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: has_unique_slug
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 3
10
- version: 0.1.3
9
+ - 4
10
+ version: 0.1.4
11
11
  platform: ruby
12
12
  authors:
13
13
  - Brendan Stennett
@@ -15,9 +15,50 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-10-13 00:00:00 Z
19
- dependencies: []
20
-
18
+ date: 2011-10-14 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :development
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: activerecord
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :development
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: sqlite3
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ type: :development
61
+ version_requirements: *id003
21
62
  description: Generates a unique slug for use as a drop-in replacement for ids.
22
63
  email:
23
64
  - brendan@unknowncollective.com