pseudocephalopod 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +1 -121
- data/Rakefile +5 -97
- data/lib/pseudocephalopod.rb +3 -78
- metadata +13 -101
- data/Gemfile +0 -20
- data/Gemfile.lock +0 -82
- data/lib/generators/pseudocephalopod/slug_migration/slug_migration_generator.rb +0 -24
- data/lib/generators/pseudocephalopod/slug_migration/templates/migration.erb +0 -12
- data/lib/generators/pseudocephalopod/slugs/slugs_generator.rb +0 -24
- data/lib/generators/pseudocephalopod/slugs/templates/migration.erb +0 -20
- data/lib/pseudocephalopod/active_record_methods.rb +0 -112
- data/lib/pseudocephalopod/caching.rb +0 -87
- data/lib/pseudocephalopod/finders.rb +0 -19
- data/lib/pseudocephalopod/memory_cache.rb +0 -29
- data/lib/pseudocephalopod/railtie.rb +0 -9
- data/lib/pseudocephalopod/scopes.rb +0 -13
- data/lib/pseudocephalopod/slug.rb +0 -36
- data/lib/pseudocephalopod/slug_history.rb +0 -41
- data/lib/pseudocephalopod/version.rb +0 -8
- data/pseudocephalopod.gemspec +0 -90
- data/test/caching_test.rb +0 -77
- data/test/helper.rb +0 -44
- data/test/is_sluggable_test.rb +0 -155
- data/test/model_definitions.rb +0 -19
- data/test/pseudocephalopod_test.rb +0 -27
- data/test/slug_history_test.rb +0 -86
data/Gemfile
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
source :gemcutter
|
2
|
-
|
3
|
-
# Setup gems
|
4
|
-
gem "activerecord", "= 3.0.0"
|
5
|
-
gem "reversible_data"
|
6
|
-
gem "uuid"
|
7
|
-
gem "sqlite3-ruby", :require => "sqlite3"
|
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
|
19
|
-
|
20
|
-
gem 'ruby-debug', :require => nil
|
data/Gemfile.lock
DELETED
@@ -1,82 +0,0 @@
|
|
1
|
-
GEM
|
2
|
-
remote: http://rubygems.org/
|
3
|
-
specs:
|
4
|
-
Saikuro (1.1.0)
|
5
|
-
activemodel (3.0.0)
|
6
|
-
activesupport (= 3.0.0)
|
7
|
-
builder (~> 2.1.2)
|
8
|
-
i18n (~> 0.4.1)
|
9
|
-
activerecord (3.0.0)
|
10
|
-
activemodel (= 3.0.0)
|
11
|
-
activesupport (= 3.0.0)
|
12
|
-
arel (~> 1.0.0)
|
13
|
-
tzinfo (~> 0.3.23)
|
14
|
-
activesupport (3.0.0)
|
15
|
-
arel (1.0.1)
|
16
|
-
activesupport (~> 3.0.0)
|
17
|
-
builder (2.1.2)
|
18
|
-
columnize (0.3.1)
|
19
|
-
flay (1.4.1)
|
20
|
-
ruby_parser (~> 2.0)
|
21
|
-
sexp_processor (~> 3.0)
|
22
|
-
flog (2.5.0)
|
23
|
-
ruby_parser (~> 2.0)
|
24
|
-
sexp_processor (~> 3.0)
|
25
|
-
gemcutter (0.6.1)
|
26
|
-
git (1.2.5)
|
27
|
-
i18n (0.4.1)
|
28
|
-
jeweler (1.4.0)
|
29
|
-
gemcutter (>= 0.1.0)
|
30
|
-
git (>= 1.2.5)
|
31
|
-
rubyforge (>= 2.0.0)
|
32
|
-
json_pure (1.4.6)
|
33
|
-
linecache (0.43)
|
34
|
-
macaddr (1.0.0)
|
35
|
-
rake (0.8.7)
|
36
|
-
rcov (0.9.9)
|
37
|
-
redgreen (1.2.2)
|
38
|
-
reek (1.2.8)
|
39
|
-
ruby2ruby (~> 1.2)
|
40
|
-
ruby_parser (~> 2.0)
|
41
|
-
sexp_processor (~> 3.0)
|
42
|
-
reversible_data (0.1.0)
|
43
|
-
roodi (2.1.0)
|
44
|
-
ruby_parser
|
45
|
-
ruby-debug (0.10.3)
|
46
|
-
columnize (>= 0.1)
|
47
|
-
ruby-debug-base (~> 0.10.3.0)
|
48
|
-
ruby-debug-base (0.10.3)
|
49
|
-
linecache (>= 0.3)
|
50
|
-
ruby2ruby (1.2.5)
|
51
|
-
ruby_parser (~> 2.0)
|
52
|
-
sexp_processor (~> 3.0)
|
53
|
-
ruby_parser (2.0.5)
|
54
|
-
sexp_processor (~> 3.0)
|
55
|
-
rubyforge (2.0.4)
|
56
|
-
json_pure (>= 1.1.7)
|
57
|
-
sexp_processor (3.0.5)
|
58
|
-
shoulda (2.11.3)
|
59
|
-
sqlite3-ruby (1.3.1)
|
60
|
-
tzinfo (0.3.23)
|
61
|
-
uuid (2.3.1)
|
62
|
-
macaddr (~> 1.0)
|
63
|
-
|
64
|
-
PLATFORMS
|
65
|
-
ruby
|
66
|
-
|
67
|
-
DEPENDENCIES
|
68
|
-
Saikuro
|
69
|
-
activerecord (= 3.0.0)
|
70
|
-
flay
|
71
|
-
flog
|
72
|
-
jeweler
|
73
|
-
rake
|
74
|
-
rcov
|
75
|
-
redgreen
|
76
|
-
reek
|
77
|
-
reversible_data
|
78
|
-
roodi
|
79
|
-
ruby-debug
|
80
|
-
shoulda
|
81
|
-
sqlite3-ruby
|
82
|
-
uuid
|
@@ -1,24 +0,0 @@
|
|
1
|
-
module Pseudocephalopod
|
2
|
-
module Generators
|
3
|
-
class SlugMigrationGenerator < Rails::Generators::NamedBase
|
4
|
-
include Rails::Generators::Migration
|
5
|
-
|
6
|
-
def self.source_root
|
7
|
-
@_ps_source_root ||= File.expand_path("templates", File.dirname(__FILE__))
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.next_migration_number(dirname) #:nodoc:
|
11
|
-
if ActiveRecord::Base.timestamped_migrations
|
12
|
-
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
13
|
-
else
|
14
|
-
"%.3d" % (current_migration_number(dirname) + 1)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def create_migration_file
|
19
|
-
migration_template "migration.erb", "db/migrate/add_cached_slug_to_#{table_name}.rb"
|
20
|
-
end
|
21
|
-
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
@@ -1,12 +0,0 @@
|
|
1
|
-
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
|
-
|
3
|
-
def self.up
|
4
|
-
add_column <%= table_name.to_sym.inspect %>, :cached_slug, :string
|
5
|
-
add_index <%= table_name.to_sym.inspect %>, :cached_slug
|
6
|
-
end
|
7
|
-
|
8
|
-
def self.down
|
9
|
-
remove_column <%= table_name.to_sym.inspect %>, :cached_slug
|
10
|
-
end
|
11
|
-
|
12
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
module Pseudocephalopod
|
2
|
-
module Generators
|
3
|
-
class SlugsGenerator < Rails::Generators::Base
|
4
|
-
include Rails::Generators::Migration
|
5
|
-
|
6
|
-
def self.source_root
|
7
|
-
@_ps_source_root ||= File.expand_path("templates", File.dirname(__FILE__))
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.next_migration_number(dirname) #:nodoc:
|
11
|
-
if ActiveRecord::Base.timestamped_migrations
|
12
|
-
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
13
|
-
else
|
14
|
-
"%.3d" % (current_migration_number(dirname) + 1)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def create_migration_file
|
19
|
-
migration_template "migration.erb", "db/migrate/create_pseudocephalopod_slugs.rb"
|
20
|
-
end
|
21
|
-
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
class CreatePseudocephalopodSlugs < ActiveRecord::Migration
|
2
|
-
|
3
|
-
def self.up
|
4
|
-
create_table :slugs do |t|
|
5
|
-
t.string :scope
|
6
|
-
t.string :slug
|
7
|
-
t.integer :record_id
|
8
|
-
t.datetime :created_at
|
9
|
-
end
|
10
|
-
add_index :slugs, [:scope, :slug]
|
11
|
-
add_index :slugs, [:scope, :record_id]
|
12
|
-
add_index :slugs, [:scope, :slug, :created_at]
|
13
|
-
add_index :slugs, [:scope, :record_id, :created_at]
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.down
|
17
|
-
drop_table :slugs
|
18
|
-
end
|
19
|
-
|
20
|
-
end
|
@@ -1,112 +0,0 @@
|
|
1
|
-
module Pseudocephalopod
|
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)
|
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
|
10
|
-
extend ClassMethods
|
11
|
-
include InstanceMethods
|
12
|
-
extend Pseudocephalopod::Scopes
|
13
|
-
extend Pseudocephalopod::Finders
|
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
|
19
|
-
before_save :autogenerate_slug
|
20
|
-
end
|
21
|
-
|
22
|
-
module InstanceMethods
|
23
|
-
|
24
|
-
def to_slug
|
25
|
-
cached_slug.present? ? cached_slug : id.to_s
|
26
|
-
end
|
27
|
-
|
28
|
-
def generate_slug
|
29
|
-
slug_value = send(self.slug_source)
|
30
|
-
slug_value = self.slug_convertor_proc.call(slug_value) if slug_value.present?
|
31
|
-
if slug_value.present?
|
32
|
-
scope = self.class.other_than(self).slug_scope_relation(self)
|
33
|
-
slug_value = Pseudocephalopod.next_value(scope, slug_value)
|
34
|
-
write_attribute self.cached_slug_column, slug_value
|
35
|
-
elsif self.default_uuid_slug
|
36
|
-
write_attribute self.cached_slug_column, Pseudocephalopod.generate_uuid_slug
|
37
|
-
else
|
38
|
-
write_attribute self.cached_slug_column, nil
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def generate_slug!
|
43
|
-
generate_slug
|
44
|
-
save :validate => false
|
45
|
-
end
|
46
|
-
|
47
|
-
def autogenerate_slug
|
48
|
-
generate_slug if should_generate_slug?
|
49
|
-
end
|
50
|
-
|
51
|
-
def should_generate_slug?
|
52
|
-
send(self.cached_slug_column).blank? || (self.sync_slugs && send(:"#{self.slug_source}_changed?"))
|
53
|
-
end
|
54
|
-
|
55
|
-
def has_better_slug?
|
56
|
-
found_via_slug.present? && found_via_slug != to_slug
|
57
|
-
end
|
58
|
-
|
59
|
-
def slug_scope_key(nested_scope = [])
|
60
|
-
self.class.slug_scope_key(nested_scope)
|
61
|
-
end
|
62
|
-
|
63
|
-
end
|
64
|
-
|
65
|
-
module ClassMethods
|
66
|
-
|
67
|
-
def update_all_slugs!
|
68
|
-
find_each { |r| r.generate_slug! }
|
69
|
-
end
|
70
|
-
|
71
|
-
def slug_scope_key(nested_scope = [])
|
72
|
-
([table_name, slug_scope] + Array(nested_scope)).flatten.compact.join("|")
|
73
|
-
end
|
74
|
-
|
75
|
-
def slug_scope_relation(record)
|
76
|
-
has_slug_scope? ? where(slug_scope => record.send(slug_scope)) : scoped
|
77
|
-
end
|
78
|
-
|
79
|
-
protected
|
80
|
-
|
81
|
-
def has_slug_scope?
|
82
|
-
self.slug_scope.present?
|
83
|
-
end
|
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
|
-
|
96
|
-
def set_slug_convertor(convertor)
|
97
|
-
if convertor.present?
|
98
|
-
unless convertor.respond_to?(:call)
|
99
|
-
convertor_key = convertor.to_sym
|
100
|
-
convertor = proc { |r| r.try(convertor_key) }
|
101
|
-
end
|
102
|
-
self.slug_convertor_proc = convertor
|
103
|
-
else
|
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
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
@@ -1,87 +0,0 @@
|
|
1
|
-
require 'digest/sha2'
|
2
|
-
|
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).
|
7
|
-
module Caching
|
8
|
-
extend ActiveSupport::Concern
|
9
|
-
|
10
|
-
mattr_accessor :cache_expires_in
|
11
|
-
# Cache for 10 minutes by default.
|
12
|
-
self.cache_expires_in = 600
|
13
|
-
|
14
|
-
included do
|
15
|
-
after_save :globally_cache_slug
|
16
|
-
end
|
17
|
-
|
18
|
-
module InstanceMethods
|
19
|
-
# Automatically called in after_save, will cache this records id
|
20
|
-
# with to match the current records slug / scope
|
21
|
-
def globally_cache_slug
|
22
|
-
return unless send(:"#{self.cached_slug_column}_changed?")
|
23
|
-
value = self.to_slug
|
24
|
-
self.class.cache_slug_lookup!(value, self) if value.present?
|
25
|
-
unless use_slug_history
|
26
|
-
value = send(:"#{self.cached_slug_column}_was")
|
27
|
-
self.class.cache_slug_lookup!(value, nil)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# Wraps remove_slug_history! to remove each of the slugs
|
32
|
-
# recording in this models slug history.
|
33
|
-
def remove_slug_history!
|
34
|
-
previous_slugs.each { |s| self.class.cache_slug_lookup!(s, nil) }
|
35
|
-
super
|
36
|
-
end
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
|
-
module ClassMethods
|
41
|
-
|
42
|
-
# Wraps find_using_slug to look in the cache.
|
43
|
-
def find_using_slug(slug, options = {})
|
44
|
-
# First, attempt to load an id and then record from the cache.
|
45
|
-
if (cached_id = lookup_cached_id_from_slug(slug)).present?
|
46
|
-
return find(cached_id, options).tap { |r| r.found_via_slug = slug }
|
47
|
-
end
|
48
|
-
# Otherwise, fallback to the normal approach.
|
49
|
-
super.tap do |record|
|
50
|
-
cache_slug_lookup!(slug, record) if record.present?
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# Returns a slug cache key for a given slug.
|
55
|
-
def slug_cache_key(slug)
|
56
|
-
[Pseudocephalopod.cache_key_prefix, slug_scope_key(Digest::SHA256.hexdigest(slug.to_s.strip))].compact.join("/")
|
57
|
-
end
|
58
|
-
|
59
|
-
def has_cache_for_slug?(slug)
|
60
|
-
lookup_cached_id_from_slug(slug).present?
|
61
|
-
end
|
62
|
-
|
63
|
-
# Modify the cache for a given slug. If record is nil, it will
|
64
|
-
# delete the item from the slug cache, otherwise it will store
|
65
|
-
# the records id.
|
66
|
-
def cache_slug_lookup!(slug, record)
|
67
|
-
return if Pseudocephalopod.cache.blank?
|
68
|
-
cache = Pseudocephalopod.cache
|
69
|
-
key = slug_cache_key(slug)
|
70
|
-
# Set an expires in option for caching.
|
71
|
-
caching_options = Hash.new.tap do |hash|
|
72
|
-
expiry = Pseudocephalopod::Caching.cache_expires_in
|
73
|
-
hash[:expires_in] = expiry.to_i if expiry.present?
|
74
|
-
end
|
75
|
-
record.nil? ? cache.delete(key) : cache.write(key, record.id, caching_options)
|
76
|
-
end
|
77
|
-
|
78
|
-
protected
|
79
|
-
|
80
|
-
def lookup_cached_id_from_slug(slug)
|
81
|
-
Pseudocephalopod.cache && Pseudocephalopod.cache.read(slug_cache_key(slug))
|
82
|
-
end
|
83
|
-
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
87
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
module Pseudocephalopod
|
2
|
-
module Finders
|
3
|
-
|
4
|
-
def find_using_slug(slug, options = {})
|
5
|
-
slug = slug.to_s
|
6
|
-
value = nil
|
7
|
-
value ||= find_by_id(slug.to_i, options) if slug =~ /\A\d+\Z/
|
8
|
-
value ||= with_cached_slug(slug).first(options)
|
9
|
-
value ||= find_using_slug_history(slug, options) if use_slug_history
|
10
|
-
value.found_via_slug = slug if value.present?
|
11
|
-
value
|
12
|
-
end
|
13
|
-
|
14
|
-
def find_using_slug!(slug, options = {})
|
15
|
-
find_using_slug(slug, options) or raise ActiveRecord::RecordNotFound
|
16
|
-
end
|
17
|
-
|
18
|
-
end
|
19
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
module Pseudocephalopod
|
2
|
-
# Implements a simple cache store that uses the
|
3
|
-
# current processes memory. This makes is primarily
|
4
|
-
# used for testing purposes in the situations where
|
5
|
-
# caching is used.
|
6
|
-
class MemoryCache
|
7
|
-
|
8
|
-
def self.write(key, value, options = {})
|
9
|
-
cache[key.to_s] = value
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.read(key)
|
13
|
-
cache[key.to_s]
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.delete(key)
|
17
|
-
cache.delete key.to_s
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.reset!
|
21
|
-
@cache = nil
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.cache
|
25
|
-
@cache ||= {}
|
26
|
-
end
|
27
|
-
|
28
|
-
end
|
29
|
-
end
|