url_keyed_object 0.3.0 → 0.4.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/Rakefile +1 -59
- data/features/support/env.rb +2 -1
- data/features/support/hooks.rb +8 -4
- data/features/support/recording_logger.rb +7 -7
- data/features/with_active_record-basic.feature +10 -11
- data/features/with_active_record-more-advanced.feature +9 -10
- data/lib/url_keyed_object/active_record.rb +47 -52
- data/lib/url_keyed_object/railtie.rb +2 -2
- data/lib/url_keyed_object.rb +10 -4
- data/spec/spec_helper.rb +7 -11
- data/spec/url_keyed_object/active_record_spec.rb +77 -84
- data/spec/url_keyed_object_spec.rb +18 -15
- metadata +86 -73
- data/VERSION +0 -1
- data/spec/spec.opts +0 -2
data/Rakefile
CHANGED
@@ -1,59 +1 @@
|
|
1
|
-
require '
|
2
|
-
require 'rake'
|
3
|
-
|
4
|
-
require 'spec/rake/spectask'
|
5
|
-
Spec::Rake::SpecTask.new(:spec) do |spec|
|
6
|
-
spec.libs << 'lib' << 'spec'
|
7
|
-
spec.spec_files = FileList['spec/**/*_spec.rb']
|
8
|
-
end
|
9
|
-
|
10
|
-
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
11
|
-
spec.libs << 'lib' << 'spec'
|
12
|
-
spec.pattern = 'spec/**/*_spec.rb'
|
13
|
-
spec.rcov = true
|
14
|
-
end
|
15
|
-
|
16
|
-
task :spec => :check_dependencies
|
17
|
-
|
18
|
-
begin
|
19
|
-
require 'cucumber/rake/task'
|
20
|
-
namespace :cucumber do
|
21
|
-
Cucumber::Rake::Task.new(:ok, 'Run features that should pass') do |t|
|
22
|
-
t.fork = true # You may get faster startup if you set this to false
|
23
|
-
t.profile = 'default'
|
24
|
-
end
|
25
|
-
|
26
|
-
Cucumber::Rake::Task.new(:wip, 'Run features that are being worked on') do |t|
|
27
|
-
t.fork = true # You may get faster startup if you set this to false
|
28
|
-
t.profile = 'wip'
|
29
|
-
end
|
30
|
-
|
31
|
-
desc 'Run all features'
|
32
|
-
task :all => [:ok, :wip]
|
33
|
-
end
|
34
|
-
desc 'Alias for cucumber:ok'
|
35
|
-
task :cucumber => 'cucumber:ok'
|
36
|
-
|
37
|
-
task :default => :cucumber
|
38
|
-
|
39
|
-
task :features => :cucumber do
|
40
|
-
STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***"
|
41
|
-
end
|
42
|
-
rescue LoadError
|
43
|
-
desc 'cucumber rake task not available (cucumber not installed)'
|
44
|
-
task :cucumber do
|
45
|
-
abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin'
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
task :default => :spec
|
50
|
-
|
51
|
-
require 'rake/rdoctask'
|
52
|
-
Rake::RDocTask.new do |rdoc|
|
53
|
-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
54
|
-
|
55
|
-
rdoc.rdoc_dir = 'rdoc'
|
56
|
-
rdoc.title = "url_keyed_object #{version}"
|
57
|
-
rdoc.rdoc_files.include('README*')
|
58
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
59
|
-
end
|
1
|
+
require 'bundler/gem_tasks'
|
data/features/support/env.rb
CHANGED
data/features/support/hooks.rb
CHANGED
@@ -1,15 +1,19 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
|
3
3
|
Before("@db") do |scenario|
|
4
|
-
require '
|
4
|
+
require 'active_record'
|
5
|
+
require 'url_keyed_object/active_record'
|
6
|
+
|
5
7
|
@db_dir = ::Dir.mktmpdir
|
6
8
|
begin
|
7
|
-
# Create the SQLite database
|
9
|
+
# Create the SQLite database, pretend the Railtie has run...
|
10
|
+
@recording_logger = RecordingLogger.new($stderr)
|
11
|
+
|
12
|
+
ActiveRecord::Base.extend UrlKeyedObject::ActiveRecord
|
8
13
|
ActiveRecord::Base.establish_connection({'adapter' => 'sqlite3',
|
9
14
|
'database' => "#{@db_dir}/feature.sqlite3", 'pool' => 5, 'timeout' => 5000
|
10
15
|
})
|
11
16
|
ActiveRecord::Base.connection
|
12
|
-
@recording_logger = RecordingLogger.new($stderr)
|
13
17
|
ActiveRecord::Base.logger = @recording_logger
|
14
18
|
rescue
|
15
19
|
$stderr.puts $!, *($!.backtrace)
|
@@ -21,4 +25,4 @@ After("@db") do |scenario|
|
|
21
25
|
ActiveRecord::Base.connection.disconnect!
|
22
26
|
FileUtils.remove_entry_secure @db_dir
|
23
27
|
@recording_logger.reset_recorder!
|
24
|
-
end
|
28
|
+
end
|
@@ -4,25 +4,25 @@ class RecordingLogger < Logger
|
|
4
4
|
def recorded_messages
|
5
5
|
@recorded_messages ||= {}
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
8
|
alias_method :add_original, :add
|
9
9
|
def add(severity, message = nil, progname = nil, &block)
|
10
10
|
record_it(severity, message)
|
11
11
|
super
|
12
12
|
end
|
13
|
-
|
14
|
-
|
13
|
+
|
14
|
+
|
15
15
|
def reset_recorder!
|
16
16
|
@recorded_messages = {}
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
def has_message_for_severity?(severity)
|
20
20
|
(!recorded_messages[severity].nil? && !recorded_messages[severity].empty?)
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
private
|
24
|
-
|
24
|
+
|
25
25
|
def record_it(severity, message)
|
26
26
|
(recorded_messages[severity] ||= []) << message
|
27
27
|
end
|
28
|
-
end
|
28
|
+
end
|
@@ -1,36 +1,36 @@
|
|
1
1
|
@db
|
2
2
|
Feature: Using with ActiveRecord
|
3
|
-
In order to generate and validate URL keys in an ActiveRecord::Base
|
3
|
+
In order to generate and validate URL keys in an ActiveRecord::Base model in a Rails project
|
4
4
|
A Rails developer
|
5
5
|
Wants to include and use UrlKeyedObject
|
6
|
-
|
6
|
+
|
7
7
|
# The most basic use case is for a 5-character ID, stored in the url_key column.
|
8
|
-
#
|
9
|
-
# Simply
|
8
|
+
#
|
9
|
+
# Simply extended the UrlKeyedObject::ActiveRecord module and calling has_url_key is enough.
|
10
10
|
# This makes the url_key attribute protected from mass assignment and read-only.
|
11
11
|
Background:
|
12
12
|
Given a database, with this table defined:
|
13
13
|
"""
|
14
14
|
create_table :things do |t|
|
15
15
|
t.string :url_key, :null => false
|
16
|
-
|
16
|
+
|
17
17
|
t.timestamps
|
18
18
|
end
|
19
19
|
"""
|
20
20
|
And this class:
|
21
21
|
"""
|
22
22
|
class Thing < ActiveRecord::Base
|
23
|
-
|
23
|
+
has_url_key
|
24
24
|
end
|
25
25
|
"""
|
26
|
-
|
26
|
+
|
27
27
|
Scenario: An unsaved model object with UrlKeyedObject included
|
28
28
|
When I make a bare instance:
|
29
29
|
"""
|
30
30
|
@instance = Thing.new
|
31
31
|
"""
|
32
32
|
Then @instance.url_key should be nil
|
33
|
-
|
33
|
+
|
34
34
|
Scenario: A saved model object with UrlKeyedObject included
|
35
35
|
When I make and save an instance:
|
36
36
|
"""
|
@@ -38,15 +38,14 @@ Feature: Using with ActiveRecord
|
|
38
38
|
@instance.save!
|
39
39
|
"""
|
40
40
|
Then @instance.url_key should match /^[a-z0-9]{5}$/
|
41
|
-
|
41
|
+
|
42
42
|
Scenario: Attempting to mass-assign url_key ought to fail
|
43
43
|
When I make an instance using mass-assignment:
|
44
44
|
"""
|
45
45
|
@instance = Thing.new(:url_key => 'abcde')
|
46
46
|
"""
|
47
47
|
Then @instance.url_key should be nil
|
48
|
-
|
49
|
-
|
48
|
+
|
50
49
|
Scenario: Attempting to set url_key ought to fail
|
51
50
|
When I make an instance and set the value of url_key manually:
|
52
51
|
"""
|
@@ -1,36 +1,36 @@
|
|
1
1
|
@db
|
2
2
|
Feature: More advanced usage with ActiveRecord
|
3
|
-
In order to generate and validate non-standard URL keys in an ActiveRecord::Base
|
3
|
+
In order to generate and validate non-standard URL keys in an ActiveRecord::Base model in a Rails project
|
4
4
|
A Rails developer
|
5
5
|
Wants to include and use UrlKeyedObject
|
6
|
-
|
6
|
+
|
7
7
|
# The default case is for a 5-character ID, stored in the url_key column.
|
8
8
|
# Here we have an existing column we want to re-use, and we want a longer URL key
|
9
|
-
#
|
9
|
+
#
|
10
10
|
# calling acts_as_url_keyed and setting a couple of options is all that's needed.
|
11
11
|
Background:
|
12
12
|
Given a database, with this table defined:
|
13
13
|
"""
|
14
14
|
create_table :advanced_things do |t|
|
15
15
|
t.string :opaque_id, :null => false
|
16
|
-
|
16
|
+
|
17
17
|
t.timestamps
|
18
18
|
end
|
19
19
|
"""
|
20
20
|
And this class:
|
21
21
|
"""
|
22
22
|
class AdvancedThing < ActiveRecord::Base
|
23
|
-
|
23
|
+
has_url_key :column => :opaque_id, :length => 8
|
24
24
|
end
|
25
25
|
"""
|
26
|
-
|
26
|
+
|
27
27
|
Scenario: An unsaved model object with UrlKeyedObject included
|
28
28
|
When I make a bare instance:
|
29
29
|
"""
|
30
30
|
@instance = AdvancedThing.new
|
31
31
|
"""
|
32
32
|
Then @instance.opaque_id should be nil
|
33
|
-
|
33
|
+
|
34
34
|
Scenario: A saved model object with an 8-character URL key on #opaque_id
|
35
35
|
When I make and save an instance:
|
36
36
|
"""
|
@@ -38,15 +38,14 @@ Feature: More advanced usage with ActiveRecord
|
|
38
38
|
@instance.save!
|
39
39
|
"""
|
40
40
|
Then @instance.opaque_id should match /^[a-z0-9]{8}$/
|
41
|
-
|
41
|
+
|
42
42
|
Scenario: Attempting to mass-assign opaque_id ought to fail
|
43
43
|
When I make an instance using mass-assignment:
|
44
44
|
"""
|
45
45
|
@instance = AdvancedThing.new(:opaque_id => 'abcde')
|
46
46
|
"""
|
47
47
|
Then @instance.opaque_id should be nil
|
48
|
-
|
49
|
-
|
48
|
+
|
50
49
|
Scenario: Attempting to set opaque_id ought to fail
|
51
50
|
When I make an instance and set the value of opaque_id manually:
|
52
51
|
"""
|
@@ -1,63 +1,58 @@
|
|
1
1
|
require 'url_keyed_object'
|
2
|
-
require '
|
2
|
+
require 'active_support/concern'
|
3
3
|
|
4
4
|
module UrlKeyedObject
|
5
|
-
module
|
6
|
-
def
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
5
|
+
module ActiveRecord
|
6
|
+
def has_url_key(opts = {})
|
7
|
+
url_key_column = opts.has_key?(:column) ? opts[:column] : :url_key
|
8
|
+
url_key_length = opts.has_key?(:length) ? opts[:length] : 5
|
9
|
+
|
10
|
+
include UrlKeyedObject::ActiveRecord::Extensions
|
11
|
+
|
12
|
+
attr_protected url_key_column
|
14
13
|
before_create :generate_valid_url_key
|
15
|
-
|
16
|
-
|
14
|
+
|
15
|
+
@url_key_helper = UrlKeyedObject::ActiveRecord::Helper.new(self, url_key_column, url_key_length)
|
16
|
+
|
17
|
+
define_method("#{url_key_column}=") { |value| logger.warn("Attempt to set ##{url_key_column}!") if self.respond_to?(:logger); nil }
|
17
18
|
end
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
module ClassMethods
|
27
|
-
def url_key_column
|
28
|
-
@url_key_column ||= :url_key
|
19
|
+
|
20
|
+
class Helper
|
21
|
+
attr_reader :klass, :column, :length
|
22
|
+
|
23
|
+
def initialize(klass, column, length)
|
24
|
+
@klass = klass
|
25
|
+
@column = column
|
26
|
+
@length = length
|
29
27
|
end
|
30
|
-
|
31
|
-
def
|
32
|
-
|
28
|
+
|
29
|
+
def generate_valid_url_key(instance)
|
30
|
+
new_url_key = UrlKeyedObject.generate_checked_url_key(length) { |value| valid_url_key?(value) }
|
31
|
+
instance.send(:write_attribute, column, new_url_key)
|
32
|
+
end
|
33
|
+
|
34
|
+
def valid_url_key?(url_key)
|
35
|
+
klass.send("find_by_#{column}", url_key).nil?
|
33
36
|
end
|
34
37
|
end
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
38
|
+
|
39
|
+
module Extensions
|
40
|
+
extend ActiveSupport::Concern
|
41
|
+
|
42
|
+
def to_param
|
43
|
+
send self.class.url_key_helper.column
|
44
|
+
end
|
45
|
+
|
46
|
+
module ClassMethods
|
47
|
+
def url_key_helper
|
48
|
+
@url_key_helper
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
44
52
|
protected
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
write_attribute(url_key_column, new_url_key)
|
49
|
-
end
|
50
|
-
|
51
|
-
def valid_url_key?(url_key)
|
52
|
-
self.class.send("find_by_#{url_key_column}", url_key).nil?
|
53
|
-
end
|
54
|
-
|
55
|
-
def url_key_column
|
56
|
-
self.class.url_key_column
|
57
|
-
end
|
58
|
-
|
59
|
-
def url_key_length
|
60
|
-
self.class.url_key_length
|
53
|
+
def generate_valid_url_key
|
54
|
+
self.class.url_key_helper.generate_valid_url_key(self)
|
55
|
+
end
|
61
56
|
end
|
62
57
|
end
|
63
|
-
end
|
58
|
+
end
|
@@ -4,7 +4,7 @@ require 'url_keyed_object/active_record'
|
|
4
4
|
module UrlKeyedObject
|
5
5
|
class Railtie < Rails::Railtie
|
6
6
|
initializer 'url_keyed_object.active_record_hook', :after => :preload_frameworks do
|
7
|
-
::ActiveRecord::Base.extend UrlKeyedObject::
|
7
|
+
::ActiveRecord::Base.extend UrlKeyedObject::ActiveRecord
|
8
8
|
end
|
9
9
|
end
|
10
|
-
end
|
10
|
+
end
|
data/lib/url_keyed_object.rb
CHANGED
@@ -11,14 +11,14 @@ module UrlKeyedObject
|
|
11
11
|
"m", "n", "p", "q", "r", "s", "v", "w", "x", "y",
|
12
12
|
"z"]
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
# generates a url key from a random 5-digit base 31 number, without checking its uniqueness
|
16
16
|
def generate_unchecked_url_key(length = 5)
|
17
17
|
(1..length).collect do
|
18
18
|
key_encoding[rand(31)]
|
19
19
|
end.join('')
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
# Keeps generating new URL keys until the passed-in block returns true
|
23
23
|
def generate_checked_url_key(length = 5)
|
24
24
|
key = generate_unchecked_url_key(length)
|
@@ -27,16 +27,22 @@ module UrlKeyedObject
|
|
27
27
|
end
|
28
28
|
key
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
# Validate the well-formedness of a URL key
|
32
32
|
def well_formed_url_key?(url_key, length = 5)
|
33
33
|
!url_key.match(Regexp.new("^[0-9abcdefghjkmnpqrsvwxyz]{#{length.to_i}}$")).nil?
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
# decode a moderately dirty URL key
|
37
37
|
def decode_url_key(url_key)
|
38
38
|
url_key.downcase.tr('o', '0').tr('il', '1')
|
39
39
|
end
|
40
|
+
|
41
|
+
# decode and validate a key, returning the decoded key only if it's well-formed
|
42
|
+
def decode_well_formed_url_key(url_key, length = 5)
|
43
|
+
decoded_url_key = decode_url_key(url_key)
|
44
|
+
well_formed_url_key?(decoded_url_key, length) ? decoded_url_key : nil
|
45
|
+
end
|
40
46
|
end
|
41
47
|
end
|
42
48
|
|
data/spec/spec_helper.rb
CHANGED
@@ -1,17 +1,13 @@
|
|
1
|
-
|
2
|
-
$:.unshift(
|
3
|
-
|
4
|
-
gem 'activerecord'
|
1
|
+
lib_path = File.expand_path('../../lib', __FILE__)
|
2
|
+
$:.unshift(lib_path) unless $:.include?(lib_path)
|
3
|
+
|
5
4
|
require 'url_keyed_object'
|
6
|
-
require 'url_keyed_object/active_record'
|
7
|
-
require 'spec'
|
8
|
-
require 'spec/autorun'
|
9
5
|
|
10
|
-
|
6
|
+
RSpec.configure do |config|
|
11
7
|
config.mock_with :mocha
|
12
8
|
end
|
13
9
|
|
14
|
-
|
10
|
+
RSpec::Matchers.define :use_method do |method|
|
15
11
|
chain :for_callback do |callback|
|
16
12
|
@callback = callback
|
17
13
|
end
|
@@ -29,9 +25,9 @@ class Class
|
|
29
25
|
public *saved_private_instance_methods
|
30
26
|
public *saved_protected_instance_methods
|
31
27
|
end
|
32
|
-
|
28
|
+
|
33
29
|
yield
|
34
|
-
|
30
|
+
|
35
31
|
self.class_eval do
|
36
32
|
private *saved_private_instance_methods
|
37
33
|
protected *saved_protected_instance_methods
|
@@ -1,101 +1,94 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
|
5
|
-
require '
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
2
|
+
require 'active_model/callbacks'
|
3
|
+
require 'active_model/mass_assignment_security'
|
4
|
+
|
5
|
+
require 'url_keyed_object/active_record'
|
6
|
+
|
7
|
+
|
8
|
+
class UrlKeyedModel
|
9
|
+
extend ActiveModel::Callbacks
|
10
|
+
include ActiveModel::MassAssignmentSecurity
|
11
|
+
|
12
|
+
define_model_callbacks :create
|
13
|
+
|
14
|
+
def initialize(attrs = {})
|
15
|
+
@attributes = sanitize_for_mass_assignment(attrs)
|
16
|
+
end
|
17
|
+
|
18
|
+
def url_key
|
19
|
+
@attributes[:url_key]
|
20
|
+
end
|
21
|
+
|
22
|
+
def read_attribute(key)
|
23
|
+
@attributes[key]
|
24
|
+
end
|
25
|
+
|
26
|
+
def write_attribute(key, value)
|
27
|
+
@attributes[key] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
extend UrlKeyedObject::ActiveRecord
|
31
|
+
|
32
|
+
has_url_key
|
33
|
+
|
34
|
+
def save
|
35
|
+
run_callbacks :create do
|
36
|
+
#logic
|
13
37
|
end
|
14
38
|
end
|
15
39
|
end
|
16
40
|
|
17
41
|
describe UrlKeyedObject::ActiveRecord do
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
'database' => "#{@db_dir}/spec.sqlite3", 'pool' => 5, 'timeout' => 5000
|
24
|
-
})
|
25
|
-
ActiveRecord::Base.connection
|
26
|
-
rescue
|
27
|
-
$stderr.puts $!, *($!.backtrace)
|
28
|
-
$stderr.puts "Couldn't create database for #{"#{@db_dir}/feature.sqlite3".inspect}"
|
29
|
-
end
|
30
|
-
|
31
|
-
SpecMigration.migrate(:up)
|
32
|
-
|
33
|
-
@url_keyed_class = Class.new(ActiveRecord::Base)
|
34
|
-
@url_keyed_class.class_eval do
|
35
|
-
set_table_name 'things'
|
36
|
-
def self.name
|
37
|
-
'UrlKeyedObjectMock'
|
38
|
-
end
|
39
|
-
|
40
|
-
acts_as_url_keyed
|
41
|
-
end
|
42
|
-
|
43
|
-
ActiveRecord::Base.logger = Logger.new(STDERR)
|
44
|
-
end
|
45
|
-
|
46
|
-
after(:all) do
|
47
|
-
ActiveRecord::Base.connection.disconnect!
|
48
|
-
FileUtils.remove_entry_secure @db_dir
|
49
|
-
end
|
50
|
-
|
51
|
-
it "should protect URL keys from mass-assignment" do
|
52
|
-
url_keyed_object = @url_keyed_class.new(:url_key => 'woo')
|
53
|
-
|
42
|
+
let(:url_keyed_object) { UrlKeyedModel.new }
|
43
|
+
|
44
|
+
it "protects URL keys from mass-assignment" do
|
45
|
+
url_keyed_object = UrlKeyedModel.new(:url_key => 'woo')
|
46
|
+
|
54
47
|
url_keyed_object.url_key.should be_nil
|
55
48
|
end
|
56
|
-
|
57
|
-
it "
|
58
|
-
url_keyed_object = @url_keyed_class.new
|
59
|
-
|
49
|
+
|
50
|
+
it "does not allow URL keys to be set" do
|
60
51
|
url_keyed_object.url_key = 'dfghj'
|
61
|
-
|
52
|
+
|
62
53
|
url_keyed_object.url_key.should be_nil
|
63
54
|
end
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
55
|
+
|
56
|
+
it "returns the contents of the url_key column for to_param" do
|
57
|
+
url_keyed_object.stubs(:url_key).returns('hello')
|
58
|
+
|
59
|
+
url_keyed_object.to_param.should == 'hello'
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "ensuring unique URL keys get created" do
|
63
|
+
let(:ar_helper) { UrlKeyedObject::ActiveRecord::Helper.new(UrlKeyedModel, :url_key, 5) }
|
64
|
+
|
65
|
+
it "can verify that a URL key is valid" do
|
66
|
+
UrlKeyedModel.expects(:find_by_url_key).with('a_url').returns(nil)
|
67
|
+
|
68
|
+
ar_helper.valid_url_key?('a_url').should be_true
|
73
69
|
end
|
74
|
-
|
75
|
-
it "
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
@url_keyed_class.publicize_methods do
|
80
|
-
url_keyed_object.valid_url_key?('a_url').should be_false
|
81
|
-
end
|
70
|
+
|
71
|
+
it "can verify that a URL key is not valid" do
|
72
|
+
UrlKeyedModel.expects(:find_by_url_key).with('a_url').returns(UrlKeyedModel.new)
|
73
|
+
|
74
|
+
ar_helper.valid_url_key?('a_url').should be_false
|
82
75
|
end
|
83
|
-
|
84
|
-
it "
|
85
|
-
url_keyed_object =
|
86
|
-
|
76
|
+
|
77
|
+
it "keeps attempting to create a URL key until one is valid" do
|
78
|
+
url_keyed_object = UrlKeyedModel.new
|
79
|
+
|
87
80
|
UrlKeyedObject.expects(:generate_checked_url_key).yields('a2url').returns('a2url')
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
url_keyed_object.url_key.should == 'a2url'
|
94
|
-
end
|
81
|
+
ar_helper.expects(:valid_url_key?).with('a2url').returns(true)
|
82
|
+
|
83
|
+
ar_helper.generate_valid_url_key(url_keyed_object)
|
84
|
+
|
85
|
+
url_keyed_object.url_key.should == 'a2url'
|
95
86
|
end
|
96
|
-
|
97
|
-
it "
|
98
|
-
|
87
|
+
|
88
|
+
it "uses a before_create callback to ensure that a URL key is created" do
|
89
|
+
UrlKeyedModel.url_key_helper.expects(:generate_valid_url_key).with(url_keyed_object)
|
90
|
+
|
91
|
+
url_keyed_object.save
|
99
92
|
end
|
100
93
|
end
|
101
|
-
end
|
94
|
+
end
|
@@ -4,30 +4,30 @@ describe UrlKeyedObject do
|
|
4
4
|
it "should be able to generate a 5-character URL key" do
|
5
5
|
UrlKeyedObject.generate_unchecked_url_key.should match(/[0-9abcdefghjkmnpqrsvwxyz]{5}/)
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
8
|
it "should be able to generate an arbitrary-length URL key" do
|
9
9
|
UrlKeyedObject.generate_unchecked_url_key(7).should match(/[0-9abcdefghjkmnpqrsvwxyz]{7}/)
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
describe "decoding a moderately dirty URL key (case insensitivity, ambiguous character replacement)" do
|
13
13
|
it "should be able to cope with O o 0 ambiguity" do
|
14
14
|
UrlKeyedObject.decode_url_key('Oo0ab').should == '000ab'
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
it "should be able to cope with 1 I i L l ambiguity" do
|
18
18
|
UrlKeyedObject.decode_url_key('1IiLl').should == '11111'
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
it "should be able to cope with uppercase" do
|
22
22
|
UrlKeyedObject.decode_url_key('ABCDE').should == 'abcde'
|
23
23
|
end
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
describe "well-formedness of URL keys" do
|
27
27
|
it "should be able to confirm that a URL key is well-formed" do
|
28
28
|
UrlKeyedObject.well_formed_url_key?('0ab9d').should be_true
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
it "should report that malformed URL keys are NOT well-formed" do
|
32
32
|
['DfZZZ', # upper case
|
33
33
|
'abcdef', # too long
|
@@ -41,12 +41,12 @@ describe UrlKeyedObject do
|
|
41
41
|
UrlKeyedObject.well_formed_url_key?(malformed_url_key).should be_false
|
42
42
|
end
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
45
|
it "should be able to cope with a different specified length" do
|
46
46
|
UrlKeyedObject.well_formed_url_key?('abcdef', 6).should be_true
|
47
47
|
end
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
describe "validity of URL keys" do
|
51
51
|
it "should be able to evaluate a block to check key validity, repeating until it generates one" do
|
52
52
|
valid_key_sequence = sequence('valid key sequence')
|
@@ -54,14 +54,17 @@ describe UrlKeyedObject do
|
|
54
54
|
in_sequence(valid_key_sequence)
|
55
55
|
UrlKeyedObject.expects(:generate_unchecked_url_key).with(5).returns('a2url').
|
56
56
|
in_sequence(valid_key_sequence)
|
57
|
-
|
57
|
+
|
58
58
|
key = UrlKeyedObject.generate_checked_url_key { |value| value != 'a1url' }
|
59
|
-
|
59
|
+
|
60
60
|
key.should == 'a2url'
|
61
61
|
end
|
62
|
-
|
63
|
-
it "should description" do
|
64
|
-
|
65
|
-
end
|
66
62
|
end
|
67
|
-
|
63
|
+
|
64
|
+
it "can return a decoded key that has been checked for well-formedness" do
|
65
|
+
UrlKeyedObject.expects(:decode_url_key).with('ABCDE').returns('abcde')
|
66
|
+
UrlKeyedObject.expects(:well_formed_url_key?).with('abcde', 5).returns('abcde')
|
67
|
+
|
68
|
+
UrlKeyedObject.decode_well_formed_url_key('ABCDE').should == 'abcde'
|
69
|
+
end
|
70
|
+
end
|
metadata
CHANGED
@@ -1,73 +1,80 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: url_keyed_object
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 3
|
8
|
-
- 0
|
9
|
-
version: 0.3.0
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
prerelease:
|
10
6
|
platform: ruby
|
11
|
-
authors:
|
7
|
+
authors:
|
12
8
|
- Matt Patterson
|
13
9
|
autorequire:
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
dependencies:
|
20
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2010-06-11 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
21
15
|
name: rspec
|
16
|
+
requirement: &2164422500 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.8'
|
22
|
+
type: :development
|
22
23
|
prerelease: false
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
version:
|
24
|
+
version_requirements: *2164422500
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: mocha
|
27
|
+
requirement: &2164422000 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
32
33
|
type: :development
|
33
|
-
version_requirements: *id001
|
34
|
-
- !ruby/object:Gem::Dependency
|
35
|
-
name: cucumber
|
36
34
|
prerelease: false
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
35
|
+
version_requirements: *2164422000
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: cucumber
|
38
|
+
requirement: &2164421340 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0.5'
|
45
44
|
type: :development
|
46
|
-
|
47
|
-
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2164421340
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
48
|
name: activerecord
|
49
|
+
requirement: &2164420800 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.2'
|
55
|
+
type: :development
|
49
56
|
prerelease: false
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
57
|
+
version_requirements: *2164420800
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: sqlite3
|
60
|
+
requirement: &2164420300 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
58
66
|
type: :development
|
59
|
-
|
60
|
-
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *2164420300
|
69
|
+
description: Making it easy to work with Rails objects which use a URL key in their
|
70
|
+
URL instead of their database ID.
|
61
71
|
email: matt@reprocessed.org
|
62
72
|
executables: []
|
63
|
-
|
64
73
|
extensions: []
|
65
|
-
|
66
|
-
extra_rdoc_files:
|
74
|
+
extra_rdoc_files:
|
67
75
|
- LICENSE
|
68
76
|
- README.rdoc
|
69
|
-
files:
|
70
|
-
- VERSION
|
77
|
+
files:
|
71
78
|
- README.rdoc
|
72
79
|
- LICENSE
|
73
80
|
- Rakefile
|
@@ -76,38 +83,44 @@ files:
|
|
76
83
|
- lib/url_keyed_object/railtie.rb
|
77
84
|
- lib/url_keyed_object.rb
|
78
85
|
- rails/init.rb
|
79
|
-
|
86
|
+
- spec/spec_helper.rb
|
87
|
+
- spec/url_keyed_object/active_record_spec.rb
|
88
|
+
- spec/url_keyed_object_spec.rb
|
89
|
+
- features/step_definitions/database_steps.rb
|
90
|
+
- features/step_definitions/eval_steps.rb
|
91
|
+
- features/step_definitions/feedback_steps.rb
|
92
|
+
- features/support/env.rb
|
93
|
+
- features/support/hooks.rb
|
94
|
+
- features/support/recording_logger.rb
|
95
|
+
- features/for_url_key-like_uses.feature
|
96
|
+
- features/with_active_record-basic.feature
|
97
|
+
- features/with_active_record-more-advanced.feature
|
80
98
|
homepage: http://github.com/fidothe/url_keyed_object
|
81
99
|
licenses: []
|
82
|
-
|
83
100
|
post_install_message:
|
84
|
-
rdoc_options:
|
101
|
+
rdoc_options:
|
85
102
|
- --charset=UTF-8
|
86
|
-
require_paths:
|
103
|
+
require_paths:
|
87
104
|
- lib
|
88
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
requirements:
|
97
|
-
- -
|
98
|
-
- !ruby/object:Gem::Version
|
99
|
-
|
100
|
-
- 0
|
101
|
-
version: "0"
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ! '>='
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
102
117
|
requirements: []
|
103
|
-
|
104
118
|
rubyforge_project:
|
105
|
-
rubygems_version: 1.
|
119
|
+
rubygems_version: 1.8.10
|
106
120
|
signing_key:
|
107
121
|
specification_version: 3
|
108
122
|
summary: Making it easy to work with objects with URL keys
|
109
|
-
test_files:
|
110
|
-
- spec/spec.opts
|
123
|
+
test_files:
|
111
124
|
- spec/spec_helper.rb
|
112
125
|
- spec/url_keyed_object/active_record_spec.rb
|
113
126
|
- spec/url_keyed_object_spec.rb
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
0.3.0
|
data/spec/spec.opts
DELETED