pseudocephalopod 0.1.0 → 0.2.0

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