pseudocephalopod 0.3.1 → 0.3.2
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/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
|