has_unique_slug 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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