url_keyed_object 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Matt Patterson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,29 @@
1
+ = Url-keyed Object
2
+
3
+ Makes it easy to work with ActiveRecord (and other) objects in Rails (and anything else) which want to use an opaque alphanumeric ID in URLs to identify themselves, rather than their database ID.
4
+
5
+ /model/1
6
+
7
+ versus
8
+
9
+ /model/a1c5t
10
+
11
+ One exposes your database IDs in your URLs, and the other doesn't. There are cases when that's genuinely useful not to expose IDs directly (changing your Database from MySQL to Something Else, say, or some radical horiztonal partitioning scheme, or easier data migration), but mostly it's a matter of personal taste.
12
+
13
+ == Documentation
14
+
15
+ I'm trying to make executable documentation, so all the docs are in the form of Cucumber features, under the +features+ dir.
16
+
17
+ == Note on Patches/Pull Requests
18
+
19
+ * Fork the project.
20
+ * Make your feature addition or bug fix.
21
+ * Add tests for it. This is important so I don't break it in a
22
+ future version unintentionally.
23
+ * Commit, do not mess with rakefile, version, or history.
24
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
25
+ * Send me a pull request. Bonus points for topic branches.
26
+
27
+ == Copyright
28
+
29
+ Copyright (c) 2010 Matt Patterson. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,78 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "url_keyed_object"
8
+ gem.summary = %Q{Making it easy to work with objects with URL keys}
9
+ gem.description = %Q{Making it easy to work with Rails objects which use a URL key in their URL instead of their database ID.}
10
+ gem.email = "matt@reprocessed.org"
11
+ gem.homepage = "http://github.com/fidothe/url_keyed_object"
12
+ gem.authors = ["Matt Patterson"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ gem.add_development_dependency "cucumber", ">= 0.5"
15
+ gem.add_development_dependency "activerecord", ">= 2.3"
16
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ require 'spec/rake/spectask'
24
+ Spec::Rake::SpecTask.new(:spec) do |spec|
25
+ spec.libs << 'lib' << 'spec'
26
+ spec.spec_files = FileList['spec/**/*_spec.rb']
27
+ end
28
+
29
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
30
+ spec.libs << 'lib' << 'spec'
31
+ spec.pattern = 'spec/**/*_spec.rb'
32
+ spec.rcov = true
33
+ end
34
+
35
+ task :spec => :check_dependencies
36
+
37
+ begin
38
+ require 'cucumber/rake/task'
39
+ namespace :cucumber do
40
+ Cucumber::Rake::Task.new(:ok, 'Run features that should pass') do |t|
41
+ t.fork = true # You may get faster startup if you set this to false
42
+ t.profile = 'default'
43
+ end
44
+
45
+ Cucumber::Rake::Task.new(:wip, 'Run features that are being worked on') do |t|
46
+ t.fork = true # You may get faster startup if you set this to false
47
+ t.profile = 'wip'
48
+ end
49
+
50
+ desc 'Run all features'
51
+ task :all => [:ok, :wip]
52
+ end
53
+ desc 'Alias for cucumber:ok'
54
+ task :cucumber => 'cucumber:ok'
55
+
56
+ task :default => :cucumber
57
+
58
+ task :features => :cucumber do
59
+ STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***"
60
+ end
61
+ rescue LoadError
62
+ desc 'cucumber rake task not available (cucumber not installed)'
63
+ task :cucumber do
64
+ abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin'
65
+ end
66
+ end
67
+
68
+ task :default => :spec
69
+
70
+ require 'rake/rdoctask'
71
+ Rake::RDocTask.new do |rdoc|
72
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
73
+
74
+ rdoc.rdoc_dir = 'rdoc'
75
+ rdoc.title = "url_keyed_object #{version}"
76
+ rdoc.rdoc_files.include('README*')
77
+ rdoc.rdoc_files.include('lib/**/*.rb')
78
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/cucumber.yml ADDED
@@ -0,0 +1,2 @@
1
+ default: --format pretty --tags ~@wip
2
+ wip: --format pretty --tags @wip
@@ -0,0 +1 @@
1
+ *.sqlite3
@@ -0,0 +1,7 @@
1
+ require 'tmpdir'
2
+
3
+ Given /^a database, with this table defined:$/ do |db_string|
4
+ eval(["class FeatureMigration < ActiveRecord::Migration; def self.up",
5
+ db_string, "end; end"].join("\n"))
6
+ FeatureMigration.migrate(:up)
7
+ end
@@ -0,0 +1,15 @@
1
+ Given /^this class:$/ do |string|
2
+ eval string
3
+ end
4
+
5
+ When /^I (?:make|do) .+:$/ do |string|
6
+ eval string
7
+ end
8
+
9
+ Then /^(@[a-zA-Z_0-9]+)\.([a-zA-Z_0-9?!]+) should match \/(.+)\/$/ do |ivar, method, regex|
10
+ instance_variable_get(ivar).send(method).should match(regex)
11
+ end
12
+
13
+ Then /^(@[a-zA-Z_0-9]+)\.([a-zA-Z_0-9?!]+) should be (.+)$/ do |ivar, method, value|
14
+ instance_variable_get(ivar).send(method).should send("be_#{value}")
15
+ end
@@ -0,0 +1,8 @@
1
+ Then /^an? (warning|error|debug message|fatal error|info message|unknown) should have been logged$/ do |message_kind|
2
+ severity = {
3
+ 'warning' => Logger::WARN, 'error' => Logger::ERROR,
4
+ 'debug message' => Logger::DEBUG, 'info message' => Logger::INFO,
5
+ 'fatal error' => Logger::FATAL, 'unknown' => Logger::UNKNOWN
6
+ }[message_kind]
7
+ @recording_logger.has_message_for_severity?(severity).should be_true
8
+ end
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+ require 'url_keyed_object'
3
+
4
+ require 'spec/expectations'
@@ -0,0 +1,24 @@
1
+ require 'fileutils'
2
+
3
+ Before("@db") do |scenario|
4
+ require 'activerecord'
5
+ @db_dir = ::Dir.mktmpdir
6
+ begin
7
+ # Create the SQLite database
8
+ ActiveRecord::Base.establish_connection({'adapter' => 'sqlite3',
9
+ 'database' => "#{@db_dir}/feature.sqlite3", 'pool' => 5, 'timeout' => 5000
10
+ })
11
+ ActiveRecord::Base.connection
12
+ @recording_logger = RecordingLogger.new($stderr)
13
+ ActiveRecord::Base.logger = @recording_logger
14
+ rescue
15
+ $stderr.puts $!, *($!.backtrace)
16
+ $stderr.puts "Couldn't create database for #{"#{@db_dir}/feature.sqlite3".inspect}"
17
+ end
18
+ end
19
+
20
+ After("@db") do |scenario|
21
+ ActiveRecord::Base.connection.disconnect!
22
+ FileUtils.remove_entry_secure @db_dir
23
+ @recording_logger.reset_recorder!
24
+ end
@@ -0,0 +1,28 @@
1
+ require 'logger'
2
+
3
+ class RecordingLogger < Logger
4
+ def recorded_messages
5
+ @recorded_messages ||= {}
6
+ end
7
+
8
+ alias_method :add_original, :add
9
+ def add(severity, message = nil, progname = nil, &block)
10
+ record_it(severity, message)
11
+ super
12
+ end
13
+
14
+
15
+ def reset_recorder!
16
+ @recorded_messages = {}
17
+ end
18
+
19
+ def has_message_for_severity?(severity)
20
+ (!recorded_messages[severity].nil? && !recorded_messages[severity].empty?)
21
+ end
22
+
23
+ private
24
+
25
+ def record_it(severity, message)
26
+ (recorded_messages[severity] ||= []) << message
27
+ end
28
+ end
@@ -0,0 +1,58 @@
1
+ @db
2
+ Feature: Using with ActiveRecord
3
+ In order to generate and validate URL keys in an ActiveRecord::Base object
4
+ A Rails developer
5
+ Wants to include and use UrlKeyedObject
6
+
7
+ # The most basic use case is for a 5-character ID, stored in the url_key column.
8
+ #
9
+ # Simply including the UrlKeyedObject::ActiveRecord module is enough.
10
+ # This makes the url_key attribute protected from mass assignment and read-only.
11
+ Background:
12
+ Given a database, with this table defined:
13
+ """
14
+ create_table :things do |t|
15
+ t.string :url_key, :null => false
16
+
17
+ t.timestamps
18
+ end
19
+ """
20
+ And this class:
21
+ """
22
+ class Thing < ActiveRecord::Base
23
+ include UrlKeyedObject::ActiveRecord
24
+ end
25
+ """
26
+
27
+ Scenario: An unsaved model object with UrlKeyedObject included
28
+ When I make a bare instance:
29
+ """
30
+ @instance = Thing.new
31
+ """
32
+ Then @instance.url_key should be nil
33
+
34
+ Scenario: A saved model object with UrlKeyedObject included
35
+ When I make and save an instance:
36
+ """
37
+ @instance = Thing.new
38
+ @instance.save!
39
+ """
40
+ Then @instance.url_key should match /[a-z0-9]{5}/
41
+
42
+ Scenario: Attempting to mass-assign url_key ought to fail
43
+ When I make an instance using mass-assignment:
44
+ """
45
+ @instance = Thing.new(:url_key => 'abcde')
46
+ """
47
+ Then @instance.url_key should be nil
48
+ And a warning should have been logged
49
+
50
+ Scenario: Attempting to set url_key ought to fail
51
+ When I make an instance using mass-assignment:
52
+ """
53
+ @instance = Thing.new
54
+ @instance.url_key = 'abcde'
55
+ """
56
+ Then @instance.url_key should be nil
57
+ And a warning should have been logged
58
+
@@ -0,0 +1,41 @@
1
+ module UrlKeyedObject
2
+ module ActiveRecord
3
+ # --
4
+ # the stuff that actually gets included.
5
+ # comment included for my benefit, in case I forget (quite likely)
6
+ # ++
7
+
8
+ def to_param
9
+ url_key
10
+ end
11
+
12
+ def url_key=(value)
13
+ logger.warn('Attempt to set #url_key!')
14
+ nil
15
+ end
16
+
17
+ # --
18
+ # Validations
19
+ # ++
20
+
21
+ def self.included(model)
22
+ model.class_eval do
23
+ before_create :generate_valid_url_key
24
+ end
25
+ end
26
+
27
+ protected
28
+
29
+ def generate_valid_url_key
30
+ new_url_key = UrlKeyedObject.generate_unchecked_url_key
31
+ while !self.valid_url_key?(new_url_key) do
32
+ new_url_key = UrlKeyedObject.generate_unchecked_url_key
33
+ end
34
+ write_attribute(:url_key, new_url_key)
35
+ end
36
+
37
+ def valid_url_key?(url_key)
38
+ self.class.find_by_url_key(url_key).nil?
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,33 @@
1
+ module UrlKeyedObject
2
+ # --
3
+ # module-only methods
4
+ # ++
5
+ class << self
6
+ # holds an array so a base-31 column (units / tens / hundreds) equiv can be
7
+ # encoded as an ASCII character
8
+ def key_encoding
9
+ @key_encoding ||= ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0",
10
+ "a", "b", "c", "d", "e", "f", "g", "h", "j", "k", "m",
11
+ "n", "p", "q", "r", "s", "v", "w", "x", "y", "z"]
12
+ end
13
+
14
+ # generates a url key from a random 5-digit base 31 number, without checking its uniqueness
15
+ def generate_unchecked_url_key
16
+ (1..5).collect do
17
+ key_encoding[rand(31)]
18
+ end.join('')
19
+ end
20
+
21
+ # Validate the well-formedness of a URL key
22
+ def well_formed_url_key?(url_key)
23
+ !url_key.match(/^[0-9abcdefghjkmnpqrsvwxyz]{5}$/).nil?
24
+ end
25
+
26
+ # decode a moderately dirty URL key
27
+ def decode_url_key(url_key)
28
+ url_key.downcase.tr('o', '0').tr('il', '1')
29
+ end
30
+ end
31
+ end
32
+
33
+ require 'url_keyed_object/active_record'
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format=specdoc
@@ -0,0 +1,39 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'url_keyed_object'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+ require 'rubygems'
7
+ gem 'activerecord'
8
+
9
+ Spec::Runner.configure do |config|
10
+ config.mock_with :mocha
11
+ end
12
+
13
+ Spec::Matchers.define :use_method do |method|
14
+ chain :for_callback do |callback|
15
+ @callback = callback
16
+ end
17
+
18
+ match do |model_class|
19
+ model_class.send("#{@callback.to_s}_callback_chain").find(method)
20
+ end
21
+ end
22
+
23
+ class Class
24
+ def publicize_methods
25
+ saved_private_instance_methods = self.private_instance_methods
26
+ saved_protected_instance_methods = self.protected_instance_methods
27
+ self.class_eval do
28
+ public *saved_private_instance_methods
29
+ public *saved_protected_instance_methods
30
+ end
31
+
32
+ yield
33
+
34
+ self.class_eval do
35
+ private *saved_private_instance_methods
36
+ protected *saved_protected_instance_methods
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,104 @@
1
+ require 'spec_helper'
2
+ require 'active_record'
3
+ require 'tmpdir'
4
+ require 'fileutils'
5
+ require 'logger'
6
+
7
+ class SpecMigration < ActiveRecord::Migration
8
+ def self.up
9
+ create_table :things do |t|
10
+ t.string :url_key, :null => false
11
+
12
+ t.timestamps
13
+ end
14
+ end
15
+ end
16
+
17
+ describe UrlKeyedObject::ActiveRecord do
18
+ before(:all) do
19
+ @db_dir = ::Dir.mktmpdir
20
+ begin
21
+ # Create the SQLite database
22
+ ActiveRecord::Base.establish_connection({'adapter' => 'sqlite3',
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
+ include UrlKeyedObject::ActiveRecord
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
+
54
+ url_keyed_object.url_key.should be_nil
55
+ end
56
+
57
+ it "should not allow URL keys to be set" do
58
+ url_keyed_object = @url_keyed_class.new
59
+
60
+ url_keyed_object.url_key = 'dfghj'
61
+
62
+ url_keyed_object.url_key.should be_nil
63
+ end
64
+
65
+ describe "ensuring unique URL keys get created " do
66
+ it "should be able to verify that a URL key is valid" do
67
+ @url_keyed_class.expects(:find_by_url_key).with('a_url').returns(nil)
68
+ url_keyed_object = @url_keyed_class.new
69
+
70
+ @url_keyed_class.publicize_methods do
71
+ url_keyed_object.valid_url_key?('a_url').should be_true
72
+ end
73
+ end
74
+
75
+ it "should be able to verify that a URL key is not valid" do
76
+ @url_keyed_class.expects(:find_by_url_key).with('a_url').returns(@url_keyed_class.new)
77
+ url_keyed_object = @url_keyed_class.new
78
+
79
+ @url_keyed_class.publicize_methods do
80
+ url_keyed_object.valid_url_key?('a_url').should be_false
81
+ end
82
+ end
83
+
84
+ it "should be able to keep attempting to create a URL key until one is valid" do
85
+ url_keyed_object = @url_keyed_class.new
86
+ valid_url_sequence = sequence('valid url sequence')
87
+
88
+ UrlKeyedObject.expects(:generate_unchecked_url_key).in_sequence(valid_url_sequence).returns('a1url')
89
+ url_keyed_object.expects(:valid_url_key?).in_sequence(valid_url_sequence).returns(false)
90
+ UrlKeyedObject.expects(:generate_unchecked_url_key).in_sequence(valid_url_sequence).returns('a2url')
91
+ url_keyed_object.expects(:valid_url_key?).in_sequence(valid_url_sequence).returns(true)
92
+
93
+ @url_keyed_class.publicize_methods do
94
+ url_keyed_object.generate_valid_url_key
95
+
96
+ url_keyed_object.url_key.should == 'a2url'
97
+ end
98
+ end
99
+
100
+ it "should use a before_create callback to ensure that a URL key is created" do
101
+ @url_keyed_class.should use_method(:generate_valid_url_key).for_callback(:before_create)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe UrlKeyedObject do
4
+ it "should be able to generate a 5-character URL key" do
5
+ UrlKeyedObject.generate_unchecked_url_key.should match(/[0-9abcdefghjkmnpqrsvwxyz]{5}/)
6
+ end
7
+
8
+ describe "decoding a moderately dirty URL key (case insensitivity, ambiguous character replacement)" do
9
+ it "should be able to cope with O o 0 ambiguity" do
10
+ UrlKeyedObject.decode_url_key('Oo0ab').should == '000ab'
11
+ end
12
+
13
+ it "should be able to cope with 1 I i L l ambiguity" do
14
+ UrlKeyedObject.decode_url_key('1IiLl').should == '11111'
15
+ end
16
+
17
+ it "should be able to cope with uppercase" do
18
+ UrlKeyedObject.decode_url_key('ABCDE').should == 'abcde'
19
+ end
20
+ end
21
+
22
+ describe "well-formedness of URL keys" do
23
+ it "should be able to confirm that a URL key is well-formed" do
24
+ UrlKeyedObject.well_formed_url_key?('0ab9d').should be_true
25
+ end
26
+
27
+ it "should report that malformed URL keys are NOT well-formed" do
28
+ ['DfZZZ', # upper case
29
+ 'abcdef', # too long
30
+ 'iabcd', # ambiguous character
31
+ 'tabcd', # ambiguous character
32
+ 'uabcd', # ambiguous character
33
+ '_abcd', # illegal character
34
+ 'ab,cd', # illegal character
35
+ 'ab.cd', # illegal character
36
+ ].each do |malformed_url_key|
37
+ UrlKeyedObject.well_formed_url_key?(malformed_url_key).should be_false
38
+ end
39
+ end
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: url_keyed_object
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Matt Patterson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-19 00:00:00 +00:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.2.9
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: cucumber
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0.5"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: activerecord
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "2.3"
44
+ version:
45
+ description: Making it easy to work with Rails objects which use a URL key in their URL instead of their database ID.
46
+ email: matt@reprocessed.org
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - LICENSE
53
+ - README.rdoc
54
+ files:
55
+ - .document
56
+ - .gitignore
57
+ - LICENSE
58
+ - README.rdoc
59
+ - Rakefile
60
+ - VERSION
61
+ - cucumber.yml
62
+ - features/fixtures/.gitignore
63
+ - features/step_definitions/database_steps.rb
64
+ - features/step_definitions/eval_steps.rb
65
+ - features/step_definitions/feedback_steps.rb
66
+ - features/support/env.rb
67
+ - features/support/hooks.rb
68
+ - features/support/recording_logger.rb
69
+ - features/with_active_record-basic.feature
70
+ - lib/url_keyed_object.rb
71
+ - lib/url_keyed_object/active_record.rb
72
+ - spec/spec.opts
73
+ - spec/spec_helper.rb
74
+ - spec/url_keyed_object/active_record_spec.rb
75
+ - spec/url_keyed_object_spec.rb
76
+ has_rdoc: true
77
+ homepage: http://github.com/fidothe/url_keyed_object
78
+ licenses: []
79
+
80
+ post_install_message:
81
+ rdoc_options:
82
+ - --charset=UTF-8
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: "0"
90
+ version:
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: "0"
96
+ version:
97
+ requirements: []
98
+
99
+ rubyforge_project:
100
+ rubygems_version: 1.3.5
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: Making it easy to work with objects with URL keys
104
+ test_files:
105
+ - spec/spec_helper.rb
106
+ - spec/url_keyed_object/active_record_spec.rb
107
+ - spec/url_keyed_object_spec.rb