pseudocephalopod 0.1.0 → 0.2.0

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/.gitignore CHANGED
@@ -10,4 +10,6 @@ coverage
10
10
  rdoc
11
11
  pkg
12
12
  *.gem
13
- *.gemspec
13
+ *.gemspec
14
+ coverage
15
+ metrics/*
data/Gemfile CHANGED
@@ -4,6 +4,15 @@ source :gemcutter
4
4
  gem "activerecord", "= 3.0.0.beta2"
5
5
  gem "reversible_data"
6
6
  gem "uuid"
7
- gem "shoulda"
8
7
  gem "sqlite3-ruby", :require => "sqlite3"
9
- gem "redgreen", :require => nil if RUBY_VERSION < "1.9"
8
+
9
+ gem "shoulda", :require => nil
10
+ gem "redgreen", :require => nil if RUBY_VERSION < "1.9"
11
+ gem "rcov", :require => nil
12
+ gem "reek", :require => nil
13
+ gem "roodi", :require => nil
14
+ gem "flay", :require => nil
15
+ gem "flog", :require => nil
16
+ gem "rake", :require => nil
17
+ gem "Saikuro", :require => nil
18
+ gem "jeweler", :require => nil
data/README.md CHANGED
@@ -26,7 +26,7 @@ and built to work on Rails 3 from the start.
26
26
 
27
27
  ## Usage ##
28
28
 
29
- Using Pseudocephalopod is simple. In Rails, simply drop this in your gemfile:
29
+ Using Pseudocephalopod is simple. In Rails, simply drop this in your Gemfile:
30
30
 
31
31
  gem 'pseudocephalopod'
32
32
 
data/Rakefile CHANGED
@@ -1,4 +1,8 @@
1
1
  require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.setup
4
+ Bundler.require
5
+
2
6
  require 'rake'
3
7
 
4
8
  require File.expand_path('../lib/pseudocephalopod/version', __FILE__)
@@ -33,27 +37,49 @@ begin
33
37
  require 'rcov/rcovtask'
34
38
  Rcov::RcovTask.new do |test|
35
39
  test.libs << 'test'
36
- test.pattern = 'test/**/test_*.rb'
40
+ test.pattern = 'test/**/*_test.rb'
37
41
  test.verbose = true
42
+ test.rcov_opts << '--exclude \.bundle-cache --exclude gems-switcher'
43
+ test.output_dir = "metrics/coverage"
38
44
  end
39
45
  rescue LoadError
40
46
  task :rcov do
41
- abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
47
+ abort "Rcov isn't installed, please run via bundle exec after bundle installing"
42
48
  end
43
49
  end
44
50
 
51
+ task :metrics => [:rcov, :saikuro, :reek, :flay, :flog, :roodi]
52
+
45
53
  task :test => :check_dependencies
46
54
 
55
+ task :flog do
56
+ system "flog lib"
57
+ end
58
+
59
+ task :saikuro do
60
+ system "rm -rf metrics/saikuro && mkdir -p metrics/saikuro && saikuro -c -t -i lib/ -y 0 -w 11 -e 16 -o metrics/saikuro/"
61
+ end
62
+
63
+ begin
64
+ require 'flay'
65
+ require 'flay_task'
66
+ FlayTask.new
67
+ rescue LoadError
68
+ task :flay do
69
+ abort "Flay isn't installed, please run via bundle exec after bundle installing"
70
+ end
71
+ end
72
+
47
73
  begin
48
- require 'reek/adapters/rake_task'
49
- Reek::RakeTask.new do |t|
74
+ require 'reek/rake/task'
75
+ Reek::Rake::Task.new do |t|
50
76
  t.fail_on_error = true
51
77
  t.verbose = false
52
78
  t.source_files = 'lib/**/*.rb'
53
79
  end
54
80
  rescue LoadError
55
81
  task :reek do
56
- abort "Reek is not available. In order to run reek, you must: sudo gem install reek"
82
+ abort "Reek isn't installed, please run via bundle exec after bundle installing"
57
83
  end
58
84
  end
59
85
 
@@ -65,7 +91,7 @@ begin
65
91
  end
66
92
  rescue LoadError
67
93
  task :roodi do
68
- abort "Roodi is not available. In order to run roodi, you must: sudo gem install roodi"
94
+ abort "Roodi isn't installed, please run via bundle exec after bundle installing"
69
95
  end
70
96
  end
71
97
 
@@ -16,7 +16,7 @@ module Pseudocephalopod
16
16
  end
17
17
 
18
18
  def create_migration_file
19
- migration_template "migration.rb", "db/migrate/add_cached_slug_to_#{table_name}.rb"
19
+ migration_template "migration.erb", "db/migrate/add_cached_slug_to_#{table_name}.rb"
20
20
  end
21
21
 
22
22
  end
@@ -16,7 +16,7 @@ module Pseudocephalopod
16
16
  end
17
17
 
18
18
  def create_migration_file
19
- migration_template "migration.rb", "db/migrate/create_pseudocephalopod_slugs.rb"
19
+ migration_template "migration.erb", "db/migrate/create_pseudocephalopod_slugs.rb"
20
20
  end
21
21
 
22
22
  end
@@ -1,27 +1,21 @@
1
1
  module Pseudocephalopod
2
2
  module ActiveRecordMethods
3
+ AR_CLASS_ATTRIBUTE_NAMES = %w(cached_slug_column slug_source slug_convertor_proc default_uuid_slug use_slug_history sync_slugs slug_scope use_slug_cache use_slug_to_param).map(&:to_sym)
3
4
 
4
5
  def is_sluggable(source = :name, options = {})
6
+ options.symbolize_keys!
7
+ class_attribute *AR_CLASS_ATTRIBUTE_NAMES
8
+ attr_accessor :found_via_slug
9
+ # Load extensions
5
10
  extend ClassMethods
6
11
  include InstanceMethods
7
12
  extend Pseudocephalopod::Scopes
8
13
  extend Pseudocephalopod::Finders
9
- options.symbolize_keys!
10
- # Define the attributes
11
- class_attribute :cached_slug_column, :slug_source, :slug_source_convertor,
12
- :default_uuid_slug, :store_slug_history, :sync_slugs, :slug_scope
13
- attr_accessor :found_via_slug
14
- # Set attribute values
15
- set_slug_convertor options[:convertor]
16
- self.slug_source = source.to_sym
17
- self.cached_slug_column = (options[:slug_column] || :cached_slug).to_sym
18
- self.default_uuid_slug = !!options.fetch(:uuid, true)
19
- self.store_slug_history = !!options.fetch(:history, true)
20
- self.sync_slugs = !!options.fetch(:sync, true)
21
- self.slug_scope = options[:slug_scope]
22
- include Pseudocephalopod::Caching if !!options.fetch(:use_cache, true)
23
- alias_method :to_param, :to_slug if !!options.fetch(:to_param, true)
24
- include Pseudocephalopod::SlugHistory if self.store_slug_history
14
+ self.slug_source = source.to_sym
15
+ set_slug_options options
16
+ alias_method :to_param, :to_slug if use_slug_to_param
17
+ include Pseudocephalopod::SlugHistory if use_slug_history
18
+ include Pseudocephalopod::Caching if use_slug_cache
25
19
  before_validation :autogenerate_slug
26
20
  end
27
21
 
@@ -33,7 +27,7 @@ module Pseudocephalopod
33
27
 
34
28
  def generate_slug
35
29
  slug_value = send(self.slug_source)
36
- slug_value = self.slug_source_convertor.call(slug_value) if slug_value.present?
30
+ slug_value = self.slug_convertor_proc.call(slug_value) if slug_value.present?
37
31
  if slug_value.present?
38
32
  scope = self.class.other_than(self).slug_scope_relation(self)
39
33
  slug_value = Pseudocephalopod.next_value(scope, slug_value)
@@ -88,18 +82,27 @@ module Pseudocephalopod
88
82
  self.slug_scope.present?
89
83
  end
90
84
 
85
+ def set_slug_options(options)
86
+ set_slug_convertor options[:convertor]
87
+ self.cached_slug_column = (options[:slug_column] || :cached_slug).to_sym
88
+ self.slug_scope = options[:scope]
89
+ self.default_uuid_slug = !!options.fetch(:uuid, true)
90
+ self.sync_slugs = !!options.fetch(:sync, true)
91
+ self.use_slug_cache = !!options.fetch(:use_cache, true)
92
+ self.use_slug_to_param = !!options.fetch(:to_param, true)
93
+ self.use_slug_history = !!options.fetch(:history, Pseudocephalopod::Slug.usable?)
94
+ end
95
+
91
96
  def set_slug_convertor(convertor)
92
97
  if convertor.present?
93
- if convertor.is_a?(Symbol)
94
- self.slug_source_convertor = proc { |r| r.try(convertor) }
95
- elsif convertor.respond_to?(:call)
96
- self.slug_source_convertor = convertor
98
+ unless convertor.respond_to?(:call)
99
+ convertor_key = convertor.to_sym
100
+ convertor = proc { |r| r.try(convertor_key) }
97
101
  end
102
+ self.slug_convertor_proc = convertor
98
103
  else
99
- if "".respond_to?(:to_url)
100
- self.slug_source_convertor = proc { |r| r.to_s.to_url }
101
- else
102
- self.slug_source_convertor = proc { |r| ActiveSupport::Multibyte::Chars.new(r.to_s).parameterize }
104
+ self.slug_convertor_proc = proc do |slug|
105
+ slug.respond_to?(:to_url) ? slug.to_url : ActiveSupport::Multibyte::Chars.new(slug.to_s).parameterize
103
106
  end
104
107
  end
105
108
  end
@@ -1,7 +1,17 @@
1
1
  require 'digest/sha2'
2
2
 
3
3
  module Pseudocephalopod
4
+ # Mixin for adding simple caching support to models using pseudocephalod.
5
+ # Usually included by passing the :cache option as true (by default it is
6
+ # true, you can disable by passing :cache as false or nil).
4
7
  module Caching
8
+
9
+ class << self
10
+ attr_accessor :cache_expires_in
11
+ end
12
+
13
+ # Cache for 10 minutes by default.
14
+ self.cache_expires_in = 600
5
15
 
6
16
  def self.included(parent)
7
17
  parent.extend ClassMethods
@@ -14,19 +24,30 @@ module Pseudocephalopod
14
24
  end
15
25
 
16
26
  module InstanceMethods
27
+ # Automatically called in after_save, will cache this records id
28
+ # with to match the current records slug / scope
17
29
  def globally_cache_slug
18
30
  return unless send(:"#{self.cached_slug_column}_changed?")
19
31
  value = self.to_slug
20
32
  self.class.cache_slug_lookup!(value, self) if value.present?
21
- unless store_slug_history
33
+ unless use_slug_history
22
34
  value = send(:"#{self.cached_slug_column}_was")
23
35
  self.class.cache_slug_lookup!(value, nil)
24
36
  end
25
37
  end
38
+
39
+ # Wraps remove_slug_history! to remove each of the slugs
40
+ # recording in this models slug history.
41
+ def remove_slug_history!
42
+ previous_slugs.each { |s| self.class.cache_slug_lookup!(s, nil) }
43
+ super
44
+ end
45
+
26
46
  end
27
47
 
28
48
  module ClassMethods
29
49
 
50
+ # Wraps find_using_slug to look in the cache.
30
51
  def find_using_slug(slug, options = {})
31
52
  # First, attempt to load an id and then record from the cache.
32
53
  if (cached_id = lookup_cached_id_from_slug(slug)).present?
@@ -38,6 +59,7 @@ module Pseudocephalopod
38
59
  end
39
60
  end
40
61
 
62
+ # Returns a slug cache key for a given slug.
41
63
  def slug_cache_key(slug)
42
64
  [Pseudocephalopod.cache_key_prefix, slug_scope_key(Digest::SHA256.hexdigest(slug.to_s.strip))].compact.join("/")
43
65
  end
@@ -46,8 +68,19 @@ module Pseudocephalopod
46
68
  lookup_cached_id_from_slug(slug).present?
47
69
  end
48
70
 
71
+ # Modify the cache for a given slug. If record is nil, it will
72
+ # delete the item from the slug cache, otherwise it will store
73
+ # the records id.
49
74
  def cache_slug_lookup!(slug, record)
50
- Pseudocephalopod.cache && Pseudocephalopod.cache.write(slug_cache_key(slug), record && record.id)
75
+ return if Pseudocephalopod.cache.blank?
76
+ cache = Pseudocephalopod.cache
77
+ key = slug_cache_key(slug)
78
+ # Set an expires in option for caching.
79
+ caching_options = Hash.new.tap do |hash|
80
+ expiry = Pseudocephalopod::Caching.cache_expires_in
81
+ hash[:expires_in] = expiry.to_i if expiry.present?
82
+ end
83
+ record.nil? ? cache.delete(key) : cache.write(key, record.id, hash)
51
84
  end
52
85
 
53
86
  protected
@@ -6,7 +6,7 @@ module Pseudocephalopod
6
6
  value = nil
7
7
  value ||= find_by_id(slug.to_i, options) if slug =~ /\A\d+\Z/
8
8
  value ||= with_cached_slug(slug).first(options)
9
- value ||= find_using_slug_history(slug, options) if store_slug_history
9
+ value ||= find_using_slug_history(slug, options) if use_slug_history
10
10
  value.found_via_slug = slug if value.present?
11
11
  value
12
12
  end
@@ -1,18 +1,24 @@
1
1
  module Pseudocephalopod
2
2
  class MemoryCache
3
3
 
4
- @@cache = {}
5
-
6
- def self.write(key, value)
7
- @@cache[key.to_s] = value
4
+ def self.write(key, value, options = {})
5
+ cache[key.to_s] = value
8
6
  end
9
7
 
10
8
  def self.read(key)
11
- @@cache[key.to_s]
9
+ cache[key.to_s]
10
+ end
11
+
12
+ def self.delete(key)
13
+ cache.delete key.to_s
12
14
  end
13
15
 
14
16
  def self.reset!
15
- @@cache = {}
17
+ @cache = nil
18
+ end
19
+
20
+ def self.cache
21
+ @cache ||= {}
16
22
  end
17
23
 
18
24
  end
@@ -28,5 +28,9 @@ module Pseudocephalopod
28
28
  for_record(record).delete_all
29
29
  end
30
30
 
31
+ def self.usable?
32
+ table_exists? rescue false
33
+ end
34
+
31
35
  end
32
36
  end
@@ -16,18 +16,19 @@ module Pseudocephalopod
16
16
  Pseudocephalopod.previous_slugs_for(self)
17
17
  end
18
18
 
19
+ def remove_slug_history!
20
+ Pseudocephalopod.remove_slug_history_for(self)
21
+ end
22
+
19
23
  protected
20
24
 
21
25
  def record_slug_changes
22
- return unless send(:"#{self.cached_slug_column}_changed?")
23
- value = send(:"#{self.cached_slug_column}_was")
26
+ slug_column = self.cached_slug_column
27
+ return unless send(:"#{slug_column}_changed?")
28
+ value = send(:"#{slug_column}_was")
24
29
  Pseudocephalopod.record_slug(self, value) if value.present?
25
30
  end
26
31
 
27
- def remove_slug_history
28
- Pseudocephalopod.remove_slug_history_for(self)
29
- end
30
-
31
32
  end
32
33
 
33
34
  module ClassMethods
@@ -1,7 +1,7 @@
1
1
  module Pseudocephalopod
2
2
  module Version
3
3
  MAJOR = 0
4
- MINOR = 1
4
+ MINOR = 2
5
5
  PATCH = 0
6
6
  STRING = [MAJOR, MINOR, PATCH].join(".")
7
7
  end
@@ -20,8 +20,12 @@ module Pseudocephalopod
20
20
  slug
21
21
  end
22
22
 
23
+ def uuid
24
+ @uuid ||= UUID.new
25
+ end
26
+
23
27
  def generate_uuid_slug
24
- UUID.new.generate
28
+ uuid.generate
25
29
  end
26
30
 
27
31
  def last_known_slug_id(scope, slug)
File without changes
data/test/caching_test.rb CHANGED
@@ -1,60 +1,77 @@
1
1
  require 'helper'
2
2
 
3
3
  class CachingTest < Test::Unit::TestCase
4
- with_tables :slugs, :users, :unslugged_users do
4
+ with_tables :slugs, :users do
5
5
 
6
6
  setup { Pseudocephalopod::MemoryCache.reset! }
7
7
 
8
- should 'store a cache automatically after finding it' do
9
- assert !User.has_cache_for_slug?("bob")
10
- u = User.create :name => "Bob"
11
- assert User.has_cache_for_slug?("bob")
12
- Pseudocephalopod::MemoryCache.reset!
13
- assert !User.has_cache_for_slug?("bob")
14
- assert_equal u, User.find_using_slug("bob")
15
- assert User.has_cache_for_slug?("bob")
16
- # Second find from cache
17
- assert_equal u, User.find_using_slug("bob")
8
+ context 'with the default slug setup' do
9
+
10
+ setup { setup_slugs! }
11
+
12
+ should 'store a cache automatically after finding it' do
13
+ assert_has_no_cache_for "bob"
14
+ u = User.create :name => "Bob"
15
+ assert_has_cache_for "bob"
16
+ Pseudocephalopod::MemoryCache.reset!
17
+ assert_has_no_cache_for "bob"
18
+ assert_same_as_slug u, "bob"
19
+ assert_has_cache_for "bob"
20
+ assert_same_as_slug u, "bob"
21
+ end
22
+
23
+ should 'automatically keep cache entries for history by default' do
24
+ setup_slugs!
25
+ assert_has_no_cache_for "bob"
26
+ assert_has_no_cache_for "red"
27
+ assert_has_no_cache_for "sam"
28
+ user = User.create :name => "bob"
29
+ assert_has_cache_for "bob"
30
+ assert_has_no_cache_for "red"
31
+ assert_has_no_cache_for "sam"
32
+ user.update_attributes :name => "Red"
33
+ assert_has_cache_for "bob"
34
+ assert_has_cache_for "red"
35
+ assert_has_no_cache_for "sam"
36
+ user.update_attributes :name => "Sam"
37
+ assert_has_cache_for "bob"
38
+ assert_has_cache_for "red"
39
+ assert_has_cache_for "sam"
40
+ end
41
+
18
42
  end
19
43
 
20
44
  should 'remove cache on models without history' do
21
- UnsluggedUser.is_sluggable :name, :history => false
22
- u = UnsluggedUser.create :name => "Bob"
23
- assert UnsluggedUser.has_cache_for_slug?("bob")
24
- assert !UnsluggedUser.has_cache_for_slug?("red")
25
- assert !UnsluggedUser.has_cache_for_slug?("sam")
45
+ setup_slugs! :history => false
46
+ u = User.create :name => "Bob"
47
+ assert_has_cache_for "bob"
48
+ assert_has_no_cache_for "red"
49
+ assert_has_no_cache_for "sam"
26
50
  u.update_attributes :name => "Red"
27
51
  u.update_attributes :name => "Sam"
28
- assert !UnsluggedUser.has_cache_for_slug?("bob")
29
- assert !UnsluggedUser.has_cache_for_slug?("red")
30
- assert UnsluggedUser.has_cache_for_slug?("sam")
31
- end
32
-
33
- should 'automatically keep cache entries for history by default' do
34
- assert !User.has_cache_for_slug?("bob")
35
- assert !User.has_cache_for_slug?("red")
36
- assert !User.has_cache_for_slug?("sam")
37
- user = User.create :name => "bob"
38
- assert User.has_cache_for_slug?("bob")
39
- assert !User.has_cache_for_slug?("red")
40
- assert !User.has_cache_for_slug?("sam")
41
- user.update_attributes :name => "Red"
42
- assert User.has_cache_for_slug?("bob")
43
- assert User.has_cache_for_slug?("red")
44
- assert !User.has_cache_for_slug?("sam")
45
- user.update_attributes :name => "Sam"
46
- assert User.has_cache_for_slug?("bob")
47
- assert User.has_cache_for_slug?("red")
48
- assert User.has_cache_for_slug?("sam")
52
+ assert_has_no_cache_for "bob"
53
+ assert_has_no_cache_for "red"
54
+ assert_has_cache_for "sam"
49
55
  end
50
56
 
51
57
  should 'allow you to disable caching' do
52
- UnsluggedUser.is_sluggable :name, :use_cache => false
53
- assert !UnsluggedUser.respond_to?(:has_cache_for_slug?)
58
+ setup_slugs! :use_cache => false
59
+ assert !User.respond_to?(:has_cache_for_slug?)
54
60
  end
55
61
 
56
62
  # Ensure we destroy the contents of the cache after each test.
57
63
  teardown { Pseudocephalopod::MemoryCache.reset! }
58
64
 
59
65
  end
66
+
67
+ protected
68
+
69
+ def assert_has_cache_for(key)
70
+ assert User.has_cache_for_slug?(key), "User should have a cache entry for #{key.inspect}"
71
+ end
72
+
73
+ def assert_has_no_cache_for(key)
74
+ assert !User.has_cache_for_slug?(key), "User should not have a cache entry for #{key.inspect}"
75
+ end
76
+
60
77
  end
data/test/helper.rb CHANGED
@@ -19,4 +19,25 @@ Pseudocephalopod.cache = Pseudocephalopod::MemoryCache
19
19
 
20
20
  class Test::Unit::TestCase
21
21
  extend ReversibleData::ShouldaMacros
22
+
23
+ def setup_slugs!(*args)
24
+ options = args.extract_options!
25
+ field = args.pop || :name
26
+ User.is_sluggable field, options
27
+ end
28
+
29
+ def assert_same_as_slug(user, slug, options = {})
30
+ found_user = User.find_using_slug(slug, options)
31
+ assert_equal user, found_user, "#{slug.inspect} should return #{user.inspect}, got #{found_user.inspect}"
32
+ end
33
+
34
+ def assert_different_to_slug(user, slug, options = {})
35
+ found_user = User.find_using_slug(slug, options)
36
+ assert_not_equal user, found_user, "#{slug.inspect} should not return #{user.inspect}, got same record."
37
+ end
38
+
39
+ def assert_none_for_slug(slug)
40
+ assert User.find_using_slug(slug).blank?, "slug #{slug.inspect} should not return any records."
41
+ end
42
+
22
43
  end
@@ -2,87 +2,145 @@ require 'helper'
2
2
  require 'digest/md5'
3
3
 
4
4
  class IsSluggableTest < Test::Unit::TestCase
5
- with_tables :slugs, :users, :unslugged_users do
5
+ with_tables :slugs, :users do
6
6
 
7
- should 'correctly sluggify a value' do
8
- user = User.create(:name => "Bob")
9
- assert_equal "bob", user.to_param
10
- assert_equal "bob", user.cached_slug
7
+ class StringWrapper < String
8
+ def to_url; "my-demo-slug"; end
11
9
  end
12
10
 
13
- should 'generate a uuid in place of a slug' do
14
- user = User.create(:name => '')
15
- assert user.cached_slug.present?
16
- end
17
-
18
- should 'return need to generate a slug when the cahced slug is blank' do
19
- user = User.new(:name => "Ninja Stuff")
20
- assert user.cached_slug.blank?
21
- assert user.should_generate_slug?
22
- user.save
23
- assert user.cached_slug.present?
24
- assert !user.should_generate_slug?
25
- user.name = 'Awesome'
26
- assert user.should_generate_slug?
27
- end
28
-
29
- should 'let you disable syncing a slug' do
30
- UnsluggedUser.is_sluggable :name, :sync => false
31
- user = UnsluggedUser.create(:name => "Ninja User")
32
- assert !user.should_generate_slug?
33
- user.name = "Another User Name"
34
- assert !user.should_generate_slug?
35
- end
36
-
37
- should 'by default record slug history' do
38
- user = User.create :name => "Bob"
39
- assert_equal [], user.previous_slugs
40
- user.update_attributes! :name => "Sal"
41
- user.update_attributes! :name => "Red"
42
- user.update_attributes! :name => "Jim"
43
- assert_equal ["red", "sal", "bob"], user.previous_slugs
44
- assert_equal user, User.find_using_slug("red")
45
- assert_equal user, User.find_using_slug("sal")
46
- assert_equal user, User.find_using_slug("bob")
47
- assert_equal user, User.find_using_slug("jim")
48
- end
49
-
50
- should 'let you disable recording of slug history' do
51
- UnsluggedUser.is_sluggable :name, :history => false
52
- user = UnsluggedUser.create(:name => "Bob")
53
- assert !user.respond_to?(:previous_slugs)
54
- user.update_attributes! :name => "Red"
55
- assert_equal user, UnsluggedUser.find_using_slug("red")
56
- assert_not_equal user, UnsluggedUser.find_using_slug("bob")
57
- assert UnsluggedUser.find_using_slug("bob").blank?
58
- end
59
-
60
- should "let you find a record by it's id as needed" do
61
- user = User.create :name => "Bob"
62
- assert_equal user, User.find_using_slug(user.id)
63
- assert_equal user, User.find_using_slug(user.id.to_i)
11
+ context 'with the default slug options' do
12
+
13
+ setup { setup_slugs! }
14
+
15
+ should 'correctly sluggify a value' do
16
+ user = User.create(:name => "Bob")
17
+ assert_equal "bob", user.to_param
18
+ assert_equal "bob", user.cached_slug
19
+ end
20
+
21
+ should 'generate a uuid in place of a slug' do
22
+ user = User.create(:name => '')
23
+ assert user.cached_slug.present?
24
+ end
25
+
26
+ should 'return need to generate a slug when the cahced slug is blank' do
27
+ user = User.new(:name => "Ninja Stuff")
28
+ assert user.cached_slug.blank?
29
+ assert user.should_generate_slug?
30
+ user.save
31
+ assert user.cached_slug.present?
32
+ assert !user.should_generate_slug?
33
+ user.name = 'Awesome'
34
+ assert user.should_generate_slug?
35
+ end
36
+
37
+ should "let you find a record by it's id as needed" do
38
+ user = User.create :name => "Bob"
39
+ assert_equal user, User.find_using_slug(user.id)
40
+ assert_equal user, User.find_using_slug(user.id.to_i)
41
+ end
42
+
43
+ should 'return nil for unfound slugs by default' do
44
+ assert_nil User.find_using_slug("awesome")
45
+ end
46
+
47
+ should 'let you find slugs and raise an exception' do
48
+ assert_raises(ActiveRecord::RecordNotFound) do
49
+ User.find_using_slug!("awesome")
50
+ end
51
+ end
52
+
53
+ should 'default to generate a uuid' do
54
+ user = User.create :name => ""
55
+ assert_match /\A[a-zA-Z0-9]{32}\Z/, user.cached_slug.gsub("-", "")
56
+ user = User.create
57
+ assert_match /\A[a-zA-Z0-9]{32}\Z/, user.cached_slug.gsub("-", "")
58
+ end
59
+
60
+ should 'automatically append a sequence to the end of conflicting slugs' do
61
+ u1 = User.create :name => "ninjas Are awesome"
62
+ u2 = User.create :name => "Ninjas are awesome"
63
+ assert_equal "ninjas-are-awesome", u1.to_slug
64
+ assert_equal "ninjas-are-awesome--1", u2.to_slug
65
+ end
66
+
67
+ should 'let you find out if there is a better way of finding a slug' do
68
+ user = User.create :name => "Bob"
69
+ user.update_attributes! :name => "Ralph"
70
+ assert !User.find_using_slug("ralph").has_better_slug?
71
+ assert User.find_using_slug("bob").has_better_slug?
72
+ assert User.find_using_slug(user.id).has_better_slug?
73
+ end
74
+
64
75
  end
65
76
 
66
- should 'default to generate a uuid' do
67
- user = User.create :name => ""
68
- assert_match /\A[a-zA-Z0-9]{32}\Z/, user.cached_slug.gsub("-", "")
69
- user = User.create
70
- assert_match /\A[a-zA-Z0-9]{32}\Z/, user.cached_slug.gsub("-", "")
77
+ context 'with slug syncing disabled' do
78
+ setup { setup_slugs! :sync => false }
79
+
80
+ should 'let you disable syncing a slug' do
81
+ user = User.create(:name => "Ninja User")
82
+ assert !user.should_generate_slug?
83
+ user.name = "Another User Name"
84
+ assert !user.should_generate_slug?
85
+ end
86
+
87
+ should 'let you force slug generation' do
88
+ user = User.create(:name => "Ninja User")
89
+ assert_equal "ninja-user", user.to_slug
90
+ user.update_attributes :name => "Test User"
91
+ assert_equal "ninja-user", user.to_slug
92
+ user.generate_slug!
93
+ assert_equal "test-user", user.to_slug
94
+ user.reload
95
+ assert_equal "test-user", user.to_slug
96
+ end
97
+
98
+ should 'let you force the update of all slugs' do
99
+ user_a = User.create(:name => "User A")
100
+ user_b = User.create(:name => "User B")
101
+ user_c = User.create(:name => "User C")
102
+ user_a.update_attributes :name => "User A-1"
103
+ user_b.update_attributes :name => "User B-1"
104
+ user_c.update_attributes :name => "User C-1"
105
+ assert_equal "user-a", user_a.to_slug
106
+ assert_equal "user-b", user_b.to_slug
107
+ assert_equal "user-c", user_c.to_slug
108
+ User.update_all_slugs!
109
+ user_a.reload
110
+ user_b.reload
111
+ user_c.reload
112
+ assert_equal "user-a-1", user_a.to_slug
113
+ assert_equal "user-b-1", user_b.to_slug
114
+ assert_equal "user-c-1", user_c.to_slug
115
+ end
116
+
71
117
  end
72
118
 
73
- should 'automatically append a sequence to the end of conflicting slugs' do
74
- u1 = User.create :name => "ninjas Are awesome"
75
- u2 = User.create :name => "Ninjas are awesome"
76
- assert_equal "ninjas-are-awesome", u1.to_slug
77
- assert_equal "ninjas-are-awesome--1", u2.to_slug
119
+ context 'setting slug convertors' do
120
+
121
+ should 'let you specify a symbol' do
122
+ setup_slugs! :convertor => :upcase
123
+ assert_equal "AWESOME USER", User.create(:name => "Awesome User").to_slug
124
+ end
125
+
126
+ should 'let you specify a proc' do
127
+ setup_slugs! :convertor => proc { |r| r.reverse.upcase }
128
+ assert_equal "RESU EMOSEWA", User.create(:name => "Awesome User").to_slug
129
+ end
130
+
131
+ should 'call to_url if available by default' do
132
+ setup_slugs!
133
+ original_value = StringWrapper.new("Awesome User")
134
+ assert_equal "my-demo-slug", User.create(:name => original_value).to_slug
135
+ end
136
+
78
137
  end
79
138
 
80
- should 'let you find out if there is a better way of finding a slug' do
81
- user = User.create :name => "Bob"
82
- user.update_attributes! :name => "Ralph"
83
- assert !User.find_using_slug("ralph").has_better_slug?
84
- assert User.find_using_slug("bob").has_better_slug?
85
- assert User.find_using_slug(user.id).has_better_slug?
139
+ should 'set the cached slug to nil if uuid is nil and the source value is blank' do
140
+ setup_slugs! :uuid => nil
141
+ record = User.create(:name => "")
142
+ assert_nil record.cached_slug
143
+ assert_equal record.id.to_s, record.to_slug
86
144
  end
87
145
 
88
146
  end
@@ -11,18 +11,9 @@ ReversibleData.add :slugs do |t|
11
11
  t.datetime :created_at
12
12
  end
13
13
 
14
- user = ReversibleData.add :users do |u|
15
- u.string :name
16
- u.string :cached_slug
17
- u.timestamps
18
- end
19
-
20
- user.define_model do
21
- is_sluggable :name
22
- end
23
-
24
- ReversibleData.add :unslugged_users do |u|
14
+ ReversibleData.add :users do |u|
25
15
  u.string :name
16
+ u.string :address
26
17
  u.string :cached_slug
27
18
  u.timestamps
28
19
  end
@@ -2,6 +2,11 @@ require 'helper'
2
2
 
3
3
  class PseudocephalopodTest < Test::Unit::TestCase
4
4
 
5
+ class SlugScopeTest
6
+ cattr_accessor :slug_scope_key
7
+ self.slug_scope_key = "my-test-scope"
8
+ end
9
+
5
10
  should 'return the correct counter versions' do
6
11
  assert_equal 'awesome', Pseudocephalopod.with_counter('awesome')
7
12
  assert_equal 'awesome', Pseudocephalopod.with_counter('awesome', 0)
@@ -9,5 +14,14 @@ class PseudocephalopodTest < Test::Unit::TestCase
9
14
  assert_equal 'awesome--2', Pseudocephalopod.with_counter('awesome', 2)
10
15
  assert_equal 'awesome--100', Pseudocephalopod.with_counter('awesome', 100)
11
16
  end
12
-
17
+
18
+ should 'correct allow you to slug scope keys' do
19
+ assert_equal "my-test-scope", Pseudocephalopod.key_for_scope(SlugScopeTest)
20
+ assert_equal "my-test-scope", Pseudocephalopod.key_for_scope(SlugScopeTest.new)
21
+ assert_equal "my-test-scope", Pseudocephalopod.key_for_scope("my-test-scope")
22
+ assert_equal "", Pseudocephalopod.key_for_scope(nil)
23
+ assert_equal "1", Pseudocephalopod.key_for_scope(1)
24
+ assert_equal "awesome", Pseudocephalopod.key_for_scope(:awesome)
25
+ end
26
+
13
27
  end
@@ -7,22 +7,69 @@ class FakedModel
7
7
  end
8
8
 
9
9
  class SlugHistoryTest < Test::Unit::TestCase
10
- with_tables :slugs do
10
+ with_tables :slugs, :users do
11
+
12
+ context 'for arbitrary models' do
13
+
14
+ setup do
15
+ @record_a = FakedModel.new(12)
16
+ @record_b = FakedModel.new(4)
17
+ Pseudocephalopod.record_slug(@record_a, "awesome")
18
+ Pseudocephalopod.record_slug(@record_b, "awesome-1")
19
+ Pseudocephalopod.record_slug(@record_a, "ninjas")
20
+ end
21
+
22
+ should 'let you lookup a given record id easily' do
23
+ assert Pseudocephalopod.last_known_slug_id(FakedModel, "felafel").blank?
24
+ assert Pseudocephalopod.last_known_slug_id(FakedModel, "ninjas-2").blank?
25
+ assert_equal 12, Pseudocephalopod.last_known_slug_id(FakedModel, "awesome")
26
+ assert_equal 4, Pseudocephalopod.last_known_slug_id(FakedModel, "awesome-1")
27
+ assert_equal 12, Pseudocephalopod.last_known_slug_id(FakedModel, "ninjas")
28
+ end
29
+
30
+ should 'let you return slug history for a given record'
11
31
 
12
- setup do
13
- @record_a = FakedModel.new(12)
14
- @record_b = FakedModel.new(4)
15
- Pseudocephalopod.record_slug(@record_a, "awesome")
16
- Pseudocephalopod.record_slug(@record_b, "awesome-1")
17
- Pseudocephalopod.record_slug(@record_a, "ninjas")
18
32
  end
19
33
 
20
- should 'let you lookup a given record id easily' do
21
- assert Pseudocephalopod.last_known_slug_id(FakedModel, "felafel").blank?
22
- assert Pseudocephalopod.last_known_slug_id(FakedModel, "ninjas-2").blank?
23
- assert_equal 12, Pseudocephalopod.last_known_slug_id(FakedModel, "awesome")
24
- assert_equal 4, Pseudocephalopod.last_known_slug_id(FakedModel, "awesome-1")
25
- assert_equal 12, Pseudocephalopod.last_known_slug_id(FakedModel, "ninjas")
34
+ context 'on a specific record' do
35
+
36
+ should 'by default record slug history' do
37
+ setup_slugs!
38
+ user = User.create :name => "Bob"
39
+ assert_equal [], user.previous_slugs
40
+ user.update_attributes! :name => "Sal"
41
+ user.update_attributes! :name => "Red"
42
+ user.update_attributes! :name => "Jim"
43
+ assert_same_as_slug user, "red"
44
+ assert_same_as_slug user, "sal"
45
+ assert_same_as_slug user, "bob"
46
+ assert_same_as_slug user, "jim"
47
+ end
48
+
49
+ should 'let you reset history for a slug' do
50
+ setup_slugs!
51
+ user = User.create :name => "Bob"
52
+ user.update_attributes! :name => "Sal"
53
+ user.update_attributes! :name => "Red"
54
+ user.update_attributes! :name => "Jim"
55
+ assert_equal ["red", "sal", "bob"], user.previous_slugs
56
+ user.remove_slug_history!
57
+ assert_equal [], user.previous_slugs
58
+ assert_none_for_slug "red"
59
+ assert_none_for_slug "sal"
60
+ assert_none_for_slug "bob"
61
+ end
62
+
63
+ should 'let you disable recording of slug history' do
64
+ setup_slugs! :history => false
65
+ user = User.create(:name => "Bob")
66
+ assert !user.respond_to?(:previous_slugs)
67
+ user.update_attributes! :name => "Red"
68
+ assert_same_as_slug user, "red"
69
+ assert_different_to_slug user, "bob"
70
+ assert_none_for_slug "bob"
71
+ end
72
+
26
73
  end
27
74
 
28
75
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 1
7
+ - 2
8
8
  - 0
9
- version: 0.1.0
9
+ version: 0.2.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Darcy Laycock
@@ -14,13 +14,13 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-04-23 00:00:00 +08:00
17
+ date: 2010-04-24 00:00:00 +08:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: activerecord
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
22
+ type: :runtime
23
+ version_requirements: &id001 !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
@@ -30,32 +30,32 @@ dependencies:
30
30
  - 0
31
31
  - beta2
32
32
  version: 3.0.0.beta2
33
- type: :runtime
34
- version_requirements: *id001
33
+ prerelease: false
34
+ requirement: *id001
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: shoulda
37
- prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
37
+ type: :development
38
+ version_requirements: &id002 !ruby/object:Gem::Requirement
39
39
  requirements:
40
40
  - - ">="
41
41
  - !ruby/object:Gem::Version
42
42
  segments:
43
43
  - 0
44
44
  version: "0"
45
- type: :development
46
- version_requirements: *id002
45
+ prerelease: false
46
+ requirement: *id002
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: reversible_data
49
- prerelease: false
50
- requirement: &id003 !ruby/object:Gem::Requirement
49
+ type: :development
50
+ version_requirements: &id003 !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  segments:
55
55
  - 0
56
56
  version: "0"
57
- type: :development
58
- version_requirements: *id003
57
+ prerelease: false
58
+ requirement: *id003
59
59
  description: Super simple slugs for ActiveRecord 3.0 and higher, with support for slug history
60
60
  email: sutto@sutto.net
61
61
  executables: []
@@ -73,9 +73,9 @@ files:
73
73
  - README.md
74
74
  - Rakefile
75
75
  - lib/generators/pseudocephalopod/slug_migration/slug_migration_generator.rb
76
- - lib/generators/pseudocephalopod/slug_migration/templates/migration.rb
76
+ - lib/generators/pseudocephalopod/slug_migration/templates/migration.erb
77
77
  - lib/generators/pseudocephalopod/slugs/slugs_generator.rb
78
- - lib/generators/pseudocephalopod/slugs/templates/migration.rb
78
+ - lib/generators/pseudocephalopod/slugs/templates/migration.erb
79
79
  - lib/pseudocephalopod.rb
80
80
  - lib/pseudocephalopod/active_record_methods.rb
81
81
  - lib/pseudocephalopod/caching.rb
@@ -86,6 +86,7 @@ files:
86
86
  - lib/pseudocephalopod/slug.rb
87
87
  - lib/pseudocephalopod/slug_history.rb
88
88
  - lib/pseudocephalopod/version.rb
89
+ - metrics/.gitignore
89
90
  - test/caching_test.rb
90
91
  - test/helper.rb
91
92
  - test/is_sluggable_test.rb