assistance 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,9 @@
1
+ === 0.0.1 (2008-01-10)
2
+
3
+ * Imported #blank? from DataMapper.
4
+
5
+ * Imported inflector from Merb.
6
+
7
+ * Imported ConnectionPool from Sequel.
8
+
9
+ * Imported time calculations from Sequel.
data/COPYING ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2008 Ezra Zygmuntowicz, Sam Smoot, Sharon Rosner
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
File without changes
data/Rakefile ADDED
@@ -0,0 +1,118 @@
1
+ require "rake"
2
+ require "rake/clean"
3
+ require "rake/gempackagetask"
4
+ require "rake/rdoctask"
5
+ require "fileutils"
6
+ include FileUtils
7
+
8
+ ##############################################################################
9
+ # Configuration
10
+ ##############################################################################
11
+ NAME = "assistance"
12
+ VERS = "0.0.1"
13
+ CLEAN.include ["**/.*.sw?", "pkg/*", ".config", "doc/*", "coverage/*"]
14
+ RDOC_OPTS = [
15
+ "--quiet",
16
+ "--title", "Assistance: light-weight application support",
17
+ "--opname", "index.html",
18
+ "--line-numbers",
19
+ "--main", "README",
20
+ "--inline-source"
21
+ ]
22
+
23
+ ##############################################################################
24
+ # RDoc
25
+ ##############################################################################
26
+ task :doc => [:rdoc]
27
+
28
+ Rake::RDocTask.new do |rdoc|
29
+ rdoc.rdoc_dir = "doc/rdoc"
30
+ rdoc.options += RDOC_OPTS
31
+ rdoc.main = "README"
32
+ rdoc.title = "Assistance: light-weight application support"
33
+ rdoc.rdoc_files.add ["README", "COPYING", "lib/assistance.rb", "lib/**/*.rb"]
34
+ end
35
+
36
+ ##############################################################################
37
+ # Gem packaging
38
+ ##############################################################################
39
+ desc "Packages up Assistance."
40
+ task :default => [:package]
41
+ task :package => [:clean]
42
+
43
+ spec = Gem::Specification.new do |s|
44
+ s.name = NAME
45
+ s.rubyforge_project = NAME
46
+ s.version = VERS
47
+ s.platform = Gem::Platform::RUBY
48
+ s.has_rdoc = true
49
+ s.extra_rdoc_files = ["README", "CHANGELOG", "COPYING"]
50
+ s.rdoc_options += RDOC_OPTS +
51
+ ["--exclude", "^(examples|extras)\/", "--exclude", "lib/assistance.rb"]
52
+ s.summary = "Database access for Ruby"
53
+ s.description = s.summary
54
+ s.author = "Ezra Zygmuntowicz, Sam Smoot, Sharon Rosner"
55
+ s.email = "ezmobius@gmail.com, ssmoot@gmail.com, ciconia@gmail.com"
56
+ s.homepage = "http://assistance.rubyforge.org"
57
+ s.required_ruby_version = ">= 1.8.4"
58
+
59
+ s.files = %w(COPYING README Rakefile) + Dir.glob("{doc,spec,lib}/**/*")
60
+
61
+ s.require_path = "lib"
62
+ end
63
+
64
+ Rake::GemPackageTask.new(spec) do |p|
65
+ p.need_tar = true
66
+ p.gem_spec = spec
67
+ end
68
+
69
+ ##############################################################################
70
+ # installation & removal
71
+ ##############################################################################
72
+ task :install do
73
+ sh %{rake package}
74
+ sh %{sudo gem install pkg/#{NAME}-#{VERS}}
75
+ end
76
+
77
+ task :install_no_docs do
78
+ sh %{rake package}
79
+ sh %{sudo gem install pkg/#{NAME}-#{VERS} --no-rdoc --no-ri}
80
+ end
81
+
82
+ task :uninstall => [:clean] do
83
+ sh %{sudo gem uninstall #{NAME}}
84
+ end
85
+
86
+ task :tag do
87
+ cwd = FileUtils.pwd
88
+ sh %{cd ../.. && svn copy #{cwd} tags/#{NAME}-#{VERS} && svn commit -m "#{NAME}-#{VERS} tag." tags}
89
+ end
90
+
91
+ ##############################################################################
92
+ # gem and rdoc release
93
+ ##############################################################################
94
+ task :release => [:package] do
95
+ sh %{rubyforge login}
96
+ sh %{rubyforge add_release #{NAME} #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.tgz}
97
+ sh %{rubyforge add_file #{NAME} #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.gem}
98
+ end
99
+
100
+ ##############################################################################
101
+ # specs
102
+ ##############################################################################
103
+ require "spec/rake/spectask"
104
+
105
+ desc "Run specs with coverage"
106
+ Spec::Rake::SpecTask.new("spec") do |t|
107
+ t.spec_files = FileList["spec/*_spec.rb"]
108
+ t.spec_opts = File.read("spec/spec.opts").split("\n")
109
+ t.rcov_opts = File.read("spec/rcov.opts").split("\n")
110
+ t.rcov = true
111
+ end
112
+
113
+ desc "Run specs without coverage"
114
+ Spec::Rake::SpecTask.new("spec_no_cov") do |t|
115
+ t.spec_files = FileList["spec/*_spec.rb"]
116
+ t.spec_opts = File.read("spec/spec.opts").split("\n")
117
+ end
118
+
data/lib/assistance.rb ADDED
@@ -0,0 +1,8 @@
1
+ dir = File.join(File.dirname(__FILE__), "assistance")
2
+ %w[
3
+ core_ext
4
+ time_calculations
5
+ connection_pool
6
+ inflector
7
+ blank
8
+ ].each {|f| require(File.join(dir, f))}
@@ -0,0 +1,35 @@
1
+ class Object
2
+ def blank?
3
+ nil? || (respond_to?(:empty?) && empty?)
4
+ end
5
+ end
6
+
7
+ class Numeric
8
+ def blank?
9
+ false
10
+ end
11
+ end
12
+
13
+ class NilClass
14
+ def blank?
15
+ true
16
+ end
17
+ end
18
+
19
+ class TrueClass
20
+ def blank?
21
+ false
22
+ end
23
+ end
24
+
25
+ class FalseClass
26
+ def blank?
27
+ true
28
+ end
29
+ end
30
+
31
+ class String
32
+ def blank?
33
+ empty? || self =~ /\A\s*\Z/
34
+ end
35
+ end
@@ -0,0 +1,152 @@
1
+ require 'thread'
2
+
3
+ module Sequel
4
+ # A ConnectionPool manages access to database connections by keeping
5
+ # multiple connections and giving threads exclusive access to each
6
+ # connection.
7
+ class ConnectionPool
8
+ attr_reader :mutex
9
+
10
+ # The maximum number of connections.
11
+ attr_reader :max_size
12
+
13
+ # The proc used to create a new connection.
14
+ attr_accessor :connection_proc
15
+
16
+ attr_reader :available_connections, :allocated, :created_count
17
+
18
+ # Constructs a new pool with a maximum size. If a block is supplied, it
19
+ # is used to create new connections as they are needed.
20
+ #
21
+ # pool = ConnectionPool.new(10) {MyConnection.new(opts)}
22
+ #
23
+ # The connection creation proc can be changed at any time by assigning a
24
+ # Proc to pool#connection_proc.
25
+ #
26
+ # pool = ConnectionPool.new(10)
27
+ # pool.connection_proc = proc {MyConnection.new(opts)}
28
+ def initialize(max_size = 4, &block)
29
+ @max_size = max_size
30
+ @mutex = Mutex.new
31
+ @connection_proc = block
32
+
33
+ @available_connections = []
34
+ @allocated = {}
35
+ @created_count = 0
36
+ end
37
+
38
+ # Returns the number of created connections.
39
+ def size
40
+ @created_count
41
+ end
42
+
43
+ # Assigns a connection to the current thread, yielding the connection
44
+ # to the supplied block.
45
+ #
46
+ # pool.hold {|conn| conn.execute('DROP TABLE posts')}
47
+ #
48
+ # Pool#hold is re-entrant, meaning it can be called recursively in
49
+ # the same thread without blocking.
50
+ #
51
+ # If no connection is available, Pool#hold will block until a connection
52
+ # is available.
53
+ def hold
54
+ t = Thread.current
55
+ if (conn = owned_connection(t))
56
+ return yield(conn)
57
+ end
58
+ while !(conn = acquire(t))
59
+ sleep 0.001
60
+ end
61
+ begin
62
+ yield conn
63
+ ensure
64
+ release(t)
65
+ end
66
+ rescue Exception => e
67
+ # if the error is not a StandardError it is converted into RuntimeError.
68
+ raise e.is_a?(StandardError) ? e : e.message
69
+ end
70
+
71
+ # Removes all connection currently available, optionally yielding each
72
+ # connection to the given block. This method has the effect of
73
+ # disconnecting from the database. Once a connection is requested using
74
+ # #hold, the connection pool creates new connections to the database.
75
+ def disconnect(&block)
76
+ @mutex.synchronize do
77
+ @available_connections.each {|c| block[c]} if block
78
+ @available_connections = []
79
+ @created_count = @allocated.size
80
+ end
81
+ end
82
+
83
+ private
84
+ # Returns the connection owned by the supplied thread, if any.
85
+ def owned_connection(thread)
86
+ @mutex.synchronize {@allocated[thread]}
87
+ end
88
+
89
+ # Assigns a connection to the supplied thread, if one is available.
90
+ def acquire(thread)
91
+ @mutex.synchronize do
92
+ if conn = available
93
+ @allocated[thread] = conn
94
+ end
95
+ end
96
+ end
97
+
98
+ # Returns an available connection. If no connection is available,
99
+ # tries to create a new connection.
100
+ def available
101
+ @available_connections.pop || make_new
102
+ end
103
+
104
+ # Creates a new connection if the size of the pool is less than the
105
+ # maximum size.
106
+ def make_new
107
+ if @created_count < @max_size
108
+ @created_count += 1
109
+ @connection_proc ? @connection_proc.call : \
110
+ (raise Error, "No connection proc specified")
111
+ end
112
+ end
113
+
114
+ # Releases the connection assigned to the supplied thread.
115
+ def release(thread)
116
+ @mutex.synchronize do
117
+ @available_connections << @allocated[thread]
118
+ @allocated.delete(thread)
119
+ end
120
+ end
121
+ end
122
+
123
+ # A SingleThreadedPool acts as a replacement for a ConnectionPool for use
124
+ # in single-threaded applications. ConnectionPool imposes a substantial
125
+ # performance penalty, so SingleThreadedPool is used to gain some speed.
126
+ class SingleThreadedPool
127
+ attr_reader :conn
128
+ attr_writer :connection_proc
129
+
130
+ # Initializes the instance with the supplied block as the connection_proc.
131
+ def initialize(&block)
132
+ @connection_proc = block
133
+ end
134
+
135
+ # Yields the connection to the supplied block. This method simulates the
136
+ # ConnectionPool#hold API.
137
+ def hold
138
+ @conn ||= @connection_proc.call
139
+ yield @conn
140
+ rescue Exception => e
141
+ # if the error is not a StandardError it is converted into RuntimeError.
142
+ raise e.is_a?(StandardError) ? e : e.message
143
+ end
144
+
145
+ # Disconnects from the database. Once a connection is requested using
146
+ # #hold, the connection is reestablished.
147
+ def disconnect(&block)
148
+ block[@conn] if block && @conn
149
+ @conn = nil
150
+ end
151
+ end
152
+ end
File without changes
@@ -0,0 +1,112 @@
1
+ Inflector.inflections do |inflect|
2
+ inflect.plural(/$/, 's')
3
+ inflect.plural(/s$/i, 's')
4
+ inflect.plural(/(ax|test)is$/i, '\1es')
5
+ inflect.plural(/(octop|vir)us$/i, '\1i')
6
+ inflect.plural(/(alias|status)$/i, '\1es')
7
+ inflect.plural(/(bu)s$/i, '\1ses')
8
+ inflect.plural(/(buffal|tomat)o$/i, '\1oes')
9
+ inflect.plural(/([ti])um$/i, '\1a')
10
+ inflect.plural(/sis$/i, 'ses')
11
+ inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
12
+ inflect.plural(/(hive)$/i, '\1s')
13
+ inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
14
+ inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
15
+ inflect.plural(/(matr|vert|ind)ix|ex$/i, '\1ices')
16
+ inflect.plural(/([m|l])ouse$/i, '\1ice')
17
+ inflect.plural(/^(ox)$/i, '\1en')
18
+ inflect.plural(/(quiz)$/i, '\1zes')
19
+
20
+ inflect.singular(/s$/i, '')
21
+ inflect.singular(/(n)ews$/i, '\1ews')
22
+ inflect.singular(/([ti])a$/i, '\1um')
23
+ inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis')
24
+ inflect.singular(/(^analy)ses$/i, '\1sis')
25
+ inflect.singular(/([^f])ves$/i, '\1fe')
26
+ inflect.singular(/(hive)s$/i, '\1')
27
+ inflect.singular(/(tive)s$/i, '\1')
28
+ inflect.singular(/([lr])ves$/i, '\1f')
29
+ inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
30
+ inflect.singular(/(s)eries$/i, '\1eries')
31
+ inflect.singular(/(m)ovies$/i, '\1ovie')
32
+ inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
33
+ inflect.singular(/([m|l])ice$/i, '\1ouse')
34
+ inflect.singular(/(bus)es$/i, '\1')
35
+ inflect.singular(/(o)es$/i, '\1')
36
+ inflect.singular(/(shoe)s$/i, '\1')
37
+ inflect.singular(/(cris|ax|test)es$/i, '\1is')
38
+ inflect.singular(/(octop|vir)i$/i, '\1us')
39
+ inflect.singular(/(alias|status)es$/i, '\1')
40
+ inflect.singular(/^(ox)en/i, '\1')
41
+ inflect.singular(/(vert|ind)ices$/i, '\1ex')
42
+ inflect.singular(/(matr)ices$/i, '\1ix')
43
+ inflect.singular(/(quiz)zes$/i, '\1')
44
+
45
+ inflect.irregular('person', 'people')
46
+ inflect.irregular('man', 'men')
47
+ inflect.irregular('child', 'children')
48
+ inflect.irregular('sex', 'sexes')
49
+ inflect.irregular('move', 'moves')
50
+
51
+ inflect.uncountable(%w(equipment information rice money species series fish sheep))
52
+ end
53
+
54
+ module Inflections #:nodoc:
55
+
56
+ def pluralize
57
+ Inflector.pluralize(self)
58
+ end
59
+
60
+ def singularize
61
+ Inflector.singularize(self)
62
+ end
63
+
64
+ def camelize(first_letter = :upper)
65
+ case first_letter
66
+ when :upper then Inflector.camelize(self, true)
67
+ when :lower then Inflector.camelize(self, false)
68
+ end
69
+ end
70
+ alias_method :camelcase, :camelize
71
+
72
+ def titleize
73
+ Inflector.titleize(self)
74
+ end
75
+ alias_method :titlecase, :titleize
76
+
77
+ def underscore
78
+ Inflector.underscore(self)
79
+ end
80
+
81
+ def dasherize
82
+ Inflector.dasherize(self)
83
+ end
84
+
85
+ def demodulize
86
+ Inflector.demodulize(self)
87
+ end
88
+
89
+ def tableize
90
+ Inflector.tableize(self)
91
+ end
92
+
93
+ def classify
94
+ Inflector.classify(self)
95
+ end
96
+
97
+ def humanize
98
+ Inflector.humanize(self)
99
+ end
100
+
101
+ def foreign_key(separate_class_name_and_id_with_underscore = true)
102
+ Inflector.foreign_key(self, separate_class_name_and_id_with_underscore)
103
+ end
104
+
105
+ def constantize
106
+ Inflector.constantize(self)
107
+ end
108
+ end
109
+
110
+ class String
111
+ include Inflections
112
+ end
@@ -0,0 +1,275 @@
1
+ require 'singleton'
2
+
3
+ # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
4
+ # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept
5
+ # in inflections.rb.
6
+ module Inflector
7
+ # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
8
+ # inflection rules. Examples:
9
+ #
10
+ # Inflector.inflections do |inflect|
11
+ # inflect.plural /^(ox)$/i, '\1\2en'
12
+ # inflect.singular /^(ox)en/i, '\1'
13
+ #
14
+ # inflect.irregular 'octopus', 'octopi'
15
+ #
16
+ # inflect.uncountable "equipment"
17
+ # end
18
+ #
19
+ # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
20
+ # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
21
+ # already have been loaded.
22
+ class Inflections
23
+ include Singleton
24
+
25
+ attr_reader :plurals, :singulars, :uncountables
26
+
27
+ def initialize
28
+ @plurals, @singulars, @uncountables = [], [], []
29
+ end
30
+
31
+ # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
32
+ # The replacement should always be a string that may include references to the matched data from the rule.
33
+ def plural(rule, replacement)
34
+ @plurals.insert(0, [rule, replacement])
35
+ end
36
+
37
+ # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
38
+ # The replacement should always be a string that may include references to the matched data from the rule.
39
+ def singular(rule, replacement)
40
+ @singulars.insert(0, [rule, replacement])
41
+ end
42
+
43
+ # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
44
+ # for strings, not regular expressions. You simply pass the irregular in singular and plural form.
45
+ #
46
+ # Examples:
47
+ # irregular 'octopus', 'octopi'
48
+ # irregular 'person', 'people'
49
+ def irregular(singular, plural)
50
+ plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
51
+ singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
52
+ end
53
+
54
+ # Add uncountable words that shouldn't be attempted inflected.
55
+ #
56
+ # Examples:
57
+ # uncountable "money"
58
+ # uncountable "money", "information"
59
+ # uncountable %w( money information rice )
60
+ def uncountable(*words)
61
+ (@uncountables << words).flatten!
62
+ end
63
+
64
+ # Clears the loaded inflections within a given scope (default is :all). Give the scope as a symbol of the inflection type,
65
+ # the options are: :plurals, :singulars, :uncountables
66
+ #
67
+ # Examples:
68
+ # clear :all
69
+ # clear :plurals
70
+ def clear(scope = :all)
71
+ case scope
72
+ when :all
73
+ @plurals, @singulars, @uncountables = [], [], []
74
+ else
75
+ instance_variable_set "@#{scope}", []
76
+ end
77
+ end
78
+ end
79
+
80
+ extend self
81
+
82
+ def inflections
83
+ if block_given?
84
+ yield Inflections.instance
85
+ else
86
+ Inflections.instance
87
+ end
88
+ end
89
+
90
+ # Returns the plural form of the word in the string.
91
+ #
92
+ # Examples
93
+ # "post".pluralize #=> "posts"
94
+ # "octopus".pluralize #=> "octopi"
95
+ # "sheep".pluralize #=> "sheep"
96
+ # "words".pluralize #=> "words"
97
+ # "the blue mailman".pluralize #=> "the blue mailmen"
98
+ # "CamelOctopus".pluralize #=> "CamelOctopi"
99
+ def pluralize(word)
100
+ result = word.to_s.dup
101
+
102
+ if inflections.uncountables.include?(result.downcase)
103
+ result
104
+ else
105
+ inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
106
+ result
107
+ end
108
+ end
109
+
110
+ # The reverse of pluralize, returns the singular form of a word in a string.
111
+ #
112
+ # Examples
113
+ # "posts".singularize #=> "post"
114
+ # "octopi".singularize #=> "octopus"
115
+ # "sheep".singluarize #=> "sheep"
116
+ # "word".singluarize #=> "word"
117
+ # "the blue mailmen".singularize #=> "the blue mailman"
118
+ # "CamelOctopi".singularize #=> "CamelOctopus"
119
+ def singularize(word)
120
+ result = word.to_s.dup
121
+
122
+ if inflections.uncountables.include?(result.downcase)
123
+ result
124
+ else
125
+ inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
126
+ result
127
+ end
128
+ end
129
+
130
+ # By default, camelize converts strings to UpperCamelCase. If the argument to camelize
131
+ # is set to ":lower" then camelize produces lowerCamelCase.
132
+ #
133
+ # camelize will also convert '/' to '::' which is useful for converting paths to namespaces
134
+ #
135
+ # Examples
136
+ # "active_record".camelize #=> "ActiveRecord"
137
+ # "active_record".camelize(:lower) #=> "activeRecord"
138
+ # "active_record/errors".camelize #=> "ActiveRecord::Errors"
139
+ # "active_record/errors".camelize(:lower) #=> "activeRecord::Errors"
140
+ def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
141
+ if first_letter_in_uppercase
142
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
143
+ else
144
+ lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
145
+ end
146
+ end
147
+
148
+ # Capitalizes all the words and replaces some characters in the string to create
149
+ # a nicer looking title. Titleize is meant for creating pretty output. It is not
150
+ # used in the Rails internals.
151
+ #
152
+ # titleize is also aliased as as titlecase
153
+ #
154
+ # Examples
155
+ # "man from the boondocks".titleize #=> "Man From The Boondocks"
156
+ # "x-men: the last stand".titleize #=> "X Men: The Last Stand"
157
+ def titleize(word)
158
+ humanize(underscore(word)).gsub(/\b([a-z])/) { $1.capitalize }
159
+ end
160
+
161
+ # The reverse of +camelize+. Makes an underscored form from the expression in the string.
162
+ #
163
+ # Changes '::' to '/' to convert namespaces to paths.
164
+ #
165
+ # Examples
166
+ # "ActiveRecord".underscore #=> "active_record"
167
+ # "ActiveRecord::Errors".underscore #=> active_record/errors
168
+ def underscore(camel_cased_word)
169
+ camel_cased_word.to_s.gsub(/::/, '/').
170
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
171
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
172
+ tr("-", "_").
173
+ downcase
174
+ end
175
+
176
+ # Replaces underscores with dashes in the string.
177
+ #
178
+ # Example
179
+ # "puni_puni" #=> "puni-puni"
180
+ def dasherize(underscored_word)
181
+ underscored_word.gsub(/_/, '-')
182
+ end
183
+
184
+ # Capitalizes the first word and turns underscores into spaces and strips _id.
185
+ # Like titleize, this is meant for creating pretty output.
186
+ #
187
+ # Examples
188
+ # "employee_salary" #=> "Employee salary"
189
+ # "author_id" #=> "Author"
190
+ def humanize(lower_case_and_underscored_word)
191
+ lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
192
+ end
193
+
194
+ # Removes the module part from the expression in the string
195
+ #
196
+ # Examples
197
+ # "ActiveRecord::CoreExtensions::String::Inflections".demodulize #=> "Inflections"
198
+ # "Inflections".demodulize #=> "Inflections"
199
+ def demodulize(class_name_in_module)
200
+ class_name_in_module.to_s.gsub(/^.*::/, '')
201
+ end
202
+
203
+ # Create the name of a table like Rails does for models to table names. This method
204
+ # uses the pluralize method on the last word in the string.
205
+ #
206
+ # Examples
207
+ # "RawScaledScorer".tableize #=> "raw_scaled_scorers"
208
+ # "egg_and_ham".tableize #=> "egg_and_hams"
209
+ # "fancyCategory".tableize #=> "fancy_categories"
210
+ def tableize(class_name)
211
+ pluralize(underscore(class_name))
212
+ end
213
+
214
+ # Create a class name from a table name like Rails does for table names to models.
215
+ # Note that this returns a string and not a Class. (To convert to an actual class
216
+ # follow classify with constantize.)
217
+ #
218
+ # Examples
219
+ # "egg_and_hams".classify #=> "EggAndHam"
220
+ # "post".classify #=> "Post"
221
+ def classify(table_name)
222
+ # strip out any leading schema name
223
+ camelize(singularize(table_name.to_s.sub(/.*\./, '')))
224
+ end
225
+
226
+ # Creates a foreign key name from a class name.
227
+ # +separate_class_name_and_id_with_underscore+ sets whether
228
+ # the method should put '_' between the name and 'id'.
229
+ #
230
+ # Examples
231
+ # "Message".foreign_key #=> "message_id"
232
+ # "Message".foreign_key(false) #=> "messageid"
233
+ # "Admin::Post".foreign_key #=> "post_id"
234
+ def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
235
+ underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
236
+ end
237
+
238
+ # Constantize tries to find a declared constant with the name specified
239
+ # in the string. It raises a NameError when the name is not in CamelCase
240
+ # or is not initialized.
241
+ #
242
+ # Examples
243
+ # "Module".constantize #=> Module
244
+ # "Class".constantize #=> Class
245
+ def constantize(camel_cased_word)
246
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
247
+ raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
248
+ end
249
+
250
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
251
+ end
252
+
253
+ # Ordinalize turns a number into an ordinal string used to denote the
254
+ # position in an ordered sequence such as 1st, 2nd, 3rd, 4th.
255
+ #
256
+ # Examples
257
+ # ordinalize(1) # => "1st"
258
+ # ordinalize(2) # => "2nd"
259
+ # ordinalize(1002) # => "1002nd"
260
+ # ordinalize(1003) # => "1003rd"
261
+ def ordinalize(number)
262
+ if (11..13).include?(number.to_i % 100)
263
+ "#{number}th"
264
+ else
265
+ case number.to_i % 10
266
+ when 1: "#{number}st"
267
+ when 2: "#{number}nd"
268
+ when 3: "#{number}rd"
269
+ else "#{number}th"
270
+ end
271
+ end
272
+ end
273
+ end
274
+
275
+ require File.dirname(__FILE__) + '/inflections'
@@ -0,0 +1,35 @@
1
+ # Facilitates time calculations by providing methods to convert from larger
2
+ # time units to seconds, and to convert relative time intervals to absolute
3
+ # ones. This module duplicates some of the functionality provided by Rails'
4
+ # ActiveSupport::CoreExtensions::Numeric::Time module.
5
+
6
+ module Assistance
7
+ module TimeCalculations
8
+ MINUTE = 60
9
+ HOUR = 3600
10
+ DAY = 86400
11
+ WEEK = DAY * 7
12
+
13
+ # Converts self from minutes to seconds
14
+ def minutes; self * MINUTE; end; alias_method :minute, :minutes
15
+ # Converts self from hours to seconds
16
+ def hours; self * HOUR; end; alias_method :hour, :hours
17
+ # Converts self from days to seconds
18
+ def days; self * DAY; end; alias_method :day, :days
19
+ # Converts self from weeks to seconds
20
+ def weeks; self * WEEK; end; alias_method :week, :weeks
21
+
22
+ # Returns the time at now - self.
23
+ def ago(t = Time.now); t - self; end
24
+ alias_method :before, :ago
25
+
26
+ # Returns the time at now + self.
27
+ def from_now(t = Time.now); t + self; end
28
+ alias_method :since, :from_now
29
+ end
30
+ end
31
+
32
+ # numeric extensions
33
+ class Numeric
34
+ include Assistance::TimeCalculations
35
+ end
@@ -0,0 +1,8 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "blank?" do
4
+ it "should mark empty objects as blank" do
5
+ [ nil, false, '', ' ', " \n\t \r ", [], {} ].each { |f| f.should be_blank }
6
+ [ Object.new, true, 0, 1, 'a', [nil], { nil => 0 } ].each { |t| t.should_not be_blank }
7
+ end
8
+ end
@@ -0,0 +1,358 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ context "An empty ConnectionPool" do
4
+ setup do
5
+ @cpool = Sequel::ConnectionPool.new
6
+ end
7
+
8
+ specify "should have no available connections" do
9
+ @cpool.available_connections.should == []
10
+ end
11
+
12
+ specify "should have no allocated connections" do
13
+ @cpool.allocated.should == {}
14
+ end
15
+
16
+ specify "should have a created_count of zero" do
17
+ @cpool.created_count.should == 0
18
+ end
19
+ end
20
+
21
+ context "A connection pool handling connections" do
22
+ setup do
23
+ @max_size = 2
24
+ @cpool = Sequel::ConnectionPool.new(@max_size) {:got_connection}
25
+ end
26
+
27
+ specify "#hold should increment #created_count" do
28
+ @cpool.hold do
29
+ @cpool.created_count.should == 1
30
+ @cpool.hold {@cpool.created_count.should == 1}
31
+ end
32
+ end
33
+
34
+ specify "#hold should add the connection to the #allocated hash" do
35
+ @cpool.hold do
36
+ @cpool.allocated.size.should == 1
37
+
38
+ @cpool.allocated.values.should == [:got_connection]
39
+ end
40
+ end
41
+
42
+ specify "#hold should yield a new connection" do
43
+ @cpool.hold {|conn| conn.should == :got_connection}
44
+ end
45
+
46
+ specify "a connection should be de-allocated after it has been used in #hold" do
47
+ @cpool.hold {}
48
+ @cpool.allocated.size.should == 0
49
+ end
50
+
51
+ specify "#hold should return the value of its block" do
52
+ @cpool.hold {:block_return}.should == :block_return
53
+ end
54
+
55
+ specify "#make_new should not make more than max_size connections" do
56
+ @cpool.send(:make_new).should == :got_connection
57
+ @cpool.send(:make_new).should == :got_connection
58
+ @cpool.send(:make_new).should == nil
59
+ @cpool.created_count.should == 2
60
+ end
61
+ end
62
+
63
+ class DummyConnection
64
+ @@value = 0
65
+ def initialize
66
+ @@value += 1
67
+ end
68
+
69
+ def value
70
+ @@value
71
+ end
72
+ end
73
+
74
+ context "ConnectionPool#hold" do
75
+ setup do
76
+ @pool = Sequel::ConnectionPool.new {DummyConnection.new}
77
+ end
78
+
79
+ specify "should pass the result of the connection maker proc to the supplied block" do
80
+ res = nil
81
+ @pool.hold {|c| res = c}
82
+ res.should be_a_kind_of(DummyConnection)
83
+ res.value.should == 1
84
+ @pool.hold {|c| res = c}
85
+ res.should be_a_kind_of(DummyConnection)
86
+ res.value.should == 1 # the connection maker is invoked only once
87
+ end
88
+
89
+ specify "should be re-entrant by the same thread" do
90
+ cc = nil
91
+ @pool.hold {|c| @pool.hold {|c| @pool.hold {|c| cc = c}}}
92
+ cc.should be_a_kind_of(DummyConnection)
93
+ end
94
+
95
+ specify "should catch exceptions and reraise them" do
96
+ proc {@pool.hold {|c| c.foobar}}.should raise_error(NoMethodError)
97
+ end
98
+
99
+ specify "should handle Exception errors (normally not caught by rescue)" do
100
+ err = nil
101
+ begin
102
+ @pool.hold {raise Exception}
103
+ rescue => e
104
+ err = e
105
+ end
106
+ err.should be_a_kind_of(RuntimeError)
107
+ end
108
+ end
109
+
110
+ context "ConnectionPool#connection_proc" do
111
+ setup do
112
+ @pool = Sequel::ConnectionPool.new
113
+ end
114
+
115
+ specify "should be nil if no block is supplied to the pool" do
116
+ @pool.connection_proc.should be_nil
117
+ proc {@pool.hold {}}.should raise_error
118
+ end
119
+
120
+ specify "should be mutable" do
121
+ @pool.connection_proc = proc {'herro'}
122
+ res = nil
123
+ proc {@pool.hold {|c| res = c}}.should_not raise_error
124
+ res.should == 'herro'
125
+ end
126
+ end
127
+
128
+ context "A connection pool with a max size of 1" do
129
+ setup do
130
+ @invoked_count = 0
131
+ @pool = Sequel::ConnectionPool.new(1) {@invoked_count += 1; 'herro'}
132
+ end
133
+
134
+ specify "should let only one thread access the connection at any time" do
135
+ cc,c1, c2 = nil
136
+
137
+ t1 = Thread.new {@pool.hold {|c| cc = c; c1 = c.dup; while c == 'herro';sleep 0.1;end}}
138
+ sleep 0.2
139
+ cc.should == 'herro'
140
+ c1.should == 'herro'
141
+
142
+ t2 = Thread.new {@pool.hold {|c| c2 = c.dup; while c == 'hello';sleep 0.1;end}}
143
+ sleep 0.2
144
+
145
+ # connection held by t1
146
+ t1.should be_alive
147
+ t2.should be_alive
148
+
149
+ cc.should == 'herro'
150
+ c1.should == 'herro'
151
+ c2.should be_nil
152
+
153
+ @pool.available_connections.should be_empty
154
+ @pool.allocated.should == {t1 => cc}
155
+
156
+ cc.gsub!('rr', 'll')
157
+ sleep 0.5
158
+
159
+ # connection held by t2
160
+ t1.should_not be_alive
161
+ t2.should be_alive
162
+
163
+ c2.should == 'hello'
164
+
165
+ @pool.available_connections.should be_empty
166
+ @pool.allocated.should == {t2 => cc}
167
+
168
+ cc.gsub!('ll', 'rr')
169
+ sleep 0.5
170
+
171
+ #connection released
172
+ t2.should_not be_alive
173
+
174
+ cc.should == 'herro'
175
+
176
+ @invoked_count.should == 1
177
+ @pool.size.should == 1
178
+ @pool.available_connections.should == [cc]
179
+ @pool.allocated.should be_empty
180
+ end
181
+
182
+ specify "should let the same thread reenter #hold" do
183
+ c1, c2, c3 = nil
184
+ @pool.hold do |c|
185
+ c1 = c
186
+ @pool.hold do |c|
187
+ c2 = c
188
+ @pool.hold do |c|
189
+ c3 = c
190
+ end
191
+ end
192
+ end
193
+ c1.should == 'herro'
194
+ c2.should == 'herro'
195
+ c3.should == 'herro'
196
+
197
+ @invoked_count.should == 1
198
+ @pool.size.should == 1
199
+ @pool.available_connections.size.should == 1
200
+ @pool.allocated.should be_empty
201
+ end
202
+ end
203
+
204
+ context "A connection pool with a max size of 5" do
205
+ setup do
206
+ @invoked_count = 0
207
+ @pool = Sequel::ConnectionPool.new(5) {@invoked_count += 1}
208
+ end
209
+
210
+ specify "should let five threads simultaneously access separate connections" do
211
+ cc = {}
212
+ threads = []
213
+ stop = nil
214
+
215
+ 5.times {|i| threads << Thread.new {@pool.hold {|c| cc[i] = c; while !stop;sleep 0.1;end}}; sleep 0.1}
216
+ sleep 0.2
217
+ threads.each {|t| t.should be_alive}
218
+ cc.size.should == 5
219
+ @invoked_count.should == 5
220
+ @pool.size.should == 5
221
+ @pool.available_connections.should be_empty
222
+ @pool.allocated.should == {threads[0] => 1, threads[1] => 2, threads[2] => 3,
223
+ threads[3] => 4, threads[4] => 5}
224
+
225
+ threads[0].raise "your'e dead"
226
+ sleep 0.1
227
+ threads[3].raise "your'e dead too"
228
+
229
+ sleep 0.1
230
+
231
+ @pool.available_connections.should == [1, 4]
232
+ @pool.allocated.should == {threads[1] => 2, threads[2] => 3, threads[4] => 5}
233
+
234
+ stop = true
235
+ sleep 0.2
236
+
237
+ @pool.available_connections.size.should == 5
238
+ @pool.allocated.should be_empty
239
+ end
240
+
241
+ specify "should block threads until a connection becomes available" do
242
+ cc = {}
243
+ threads = []
244
+ stop = nil
245
+
246
+ 5.times {|i| threads << Thread.new {@pool.hold {|c| cc[i] = c; while !stop;sleep 0.1;end}}; sleep 0.1}
247
+ sleep 0.2
248
+ threads.each {|t| t.should be_alive}
249
+ @pool.available_connections.should be_empty
250
+
251
+ 3.times {|i| threads << Thread.new {@pool.hold {|c| cc[i + 5] = c}}}
252
+
253
+ sleep 0.2
254
+ threads[5].should be_alive
255
+ threads[6].should be_alive
256
+ threads[7].should be_alive
257
+ cc.size.should == 5
258
+ cc[5].should be_nil
259
+ cc[6].should be_nil
260
+ cc[7].should be_nil
261
+
262
+ stop = true
263
+ sleep 0.3
264
+
265
+ threads.each {|t| t.should_not be_alive}
266
+
267
+ @pool.size.should == 5
268
+ @invoked_count.should == 5
269
+ @pool.available_connections.size.should == 5
270
+ @pool.allocated.should be_empty
271
+ end
272
+ end
273
+
274
+ context "ConnectionPool#disconnect" do
275
+ setup do
276
+ @count = 0
277
+ @pool = Sequel::ConnectionPool.new(5) {{:id => @count += 1}}
278
+ end
279
+
280
+ specify "should invoke the given block for each available connection" do
281
+ threads = []
282
+ stop = nil
283
+ 5.times {|i| threads << Thread.new {@pool.hold {|c| while !stop;sleep 0.1;end}}; sleep 0.1}
284
+ while @pool.size < 5
285
+ sleep 0.2
286
+ end
287
+ stop = true
288
+ sleep 1
289
+ threads.each {|t| t.join}
290
+
291
+ @pool.size.should == 5
292
+ @pool.available_connections.size.should == 5
293
+ @pool.available_connections.each {|c| c[:id].should_not be_nil}
294
+ conns = []
295
+ @pool.disconnect {|c| conns << c}
296
+ conns.size.should == 5
297
+ end
298
+
299
+ specify "should remove all available connections" do
300
+ threads = []
301
+ stop = nil
302
+ 5.times {|i| threads << Thread.new {@pool.hold {|c| while !stop;sleep 0.1;end}}; sleep 0.1}
303
+ while @pool.size < 5
304
+ sleep 0.2
305
+ end
306
+ stop = true
307
+ sleep 1
308
+ threads.each {|t| t.join}
309
+
310
+ @pool.size.should == 5
311
+ @pool.disconnect
312
+ @pool.size.should == 0
313
+ end
314
+
315
+ specify "should not touch connections in use" do
316
+ threads = []
317
+ stop = nil
318
+ 5.times {|i| threads << Thread.new {@pool.hold {|c| while !stop;sleep 0.1;end}}; sleep 0.1}
319
+ while @pool.size < 5
320
+ sleep 0.2
321
+ end
322
+ stop = true
323
+ sleep 1
324
+ threads.each {|t| t.join}
325
+
326
+ @pool.size.should == 5
327
+
328
+ @pool.hold do |conn|
329
+ @pool.available_connections.size.should == 4
330
+ @pool.available_connections.each {|c| c.should_not be(conn)}
331
+ conns = []
332
+ @pool.disconnect {|c| conns << c}
333
+ conns.size.should == 4
334
+ end
335
+ @pool.size.should == 1
336
+ end
337
+ end
338
+
339
+ context "SingleThreadedPool" do
340
+ setup do
341
+ @pool = Sequel::SingleThreadedPool.new {1234}
342
+ end
343
+
344
+ specify "should provide a #hold method" do
345
+ conn = nil
346
+ @pool.hold {|c| conn = c}
347
+ conn.should == 1234
348
+ end
349
+
350
+ specify "should provide a #disconnect method" do
351
+ @pool.hold {|c|}
352
+ @pool.conn.should == 1234
353
+ conn = nil
354
+ @pool.disconnect {|c| conn = c}
355
+ conn.should == 1234
356
+ @pool.conn.should be_nil
357
+ end
358
+ end
@@ -0,0 +1,34 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Inflector do
4
+ it "should transform words from singular to plural" do
5
+ "post".pluralize.should == "posts"
6
+ "octopus".pluralize.should =="octopi"
7
+ "the blue mailman".pluralize.should == "the blue mailmen"
8
+ "CamelOctopus".pluralize.should == "CamelOctopi"
9
+ end
10
+
11
+ it "should transform words from plural to singular" do
12
+ "posts".singularize.should == "post"
13
+ "octopi".singularize.should == "octopus"
14
+ "the blue mailmen".singularize.should == "the blue mailman"
15
+ "CamelOctopi".singularize.should == "CamelOctopus"
16
+ end
17
+
18
+ it "should transform class names to table names" do
19
+ "RawScaledScorer".tableize.should == "raw_scaled_scorers"
20
+ "egg_and_ham".tableize.should == "egg_and_hams"
21
+ "fancyCategory".tableize.should == "fancy_categories"
22
+ end
23
+
24
+ it "should tranform table names to class names" do
25
+ "egg_and_hams".classify.should == "EggAndHam"
26
+ "post".classify.should == "Post"
27
+ end
28
+
29
+ it "should create a foreign key name from a class name" do
30
+ "Message".foreign_key.should == "message_id"
31
+ "Message".foreign_key(false).should == "messageid"
32
+ "Admin::Post".foreign_key.should == "post_id"
33
+ end
34
+ end
data/spec/rcov.opts ADDED
@@ -0,0 +1,4 @@
1
+ --exclude
2
+ gems
3
+ --exclude
4
+ spec
data/spec/spec.opts ADDED
@@ -0,0 +1,5 @@
1
+ --colour
2
+ --backtrace
3
+ --format
4
+ specdoc
5
+ --diff
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), "../lib/assistance")
@@ -0,0 +1,45 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ context "Time calculations" do
4
+ specify "should support conversion of minutes to seconds" do
5
+ 1.minute.should == 60
6
+ 3.minutes.should == 180
7
+ end
8
+
9
+ specify "should support conversion of hours to seconds" do
10
+ 1.hour.should == 3600
11
+ 3.hours.should == 3600 * 3
12
+ end
13
+
14
+ specify "should support conversion of days to seconds" do
15
+ 1.day.should == 86400
16
+ 3.days.should == 86400 * 3
17
+ end
18
+
19
+ specify "should support conversion of weeks to seconds" do
20
+ 1.week.should == 86400 * 7
21
+ 3.weeks.should == 86400 * 7 * 3
22
+ end
23
+
24
+ specify "should provide #ago functionality" do
25
+ t1 = Time.now
26
+ t2 = 1.day.ago
27
+ t1.should > t2
28
+ ((t1 - t2).to_i - 86400).abs.should < 2
29
+
30
+ t1 = Time.now
31
+ t2 = 1.day.before(t1)
32
+ t2.should == t1 - 1.day
33
+ end
34
+
35
+ specify "should provide #from_now functionality" do
36
+ t1 = Time.now
37
+ t2 = 1.day.from_now
38
+ t1.should < t2
39
+ ((t2 - t1).to_i - 86400).abs.should < 2
40
+
41
+ t1 = Time.now
42
+ t2 = 1.day.since(t1)
43
+ t2.should == t1 + 1.day
44
+ end
45
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: assistance
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ezra Zygmuntowicz, Sam Smoot, Sharon Rosner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-01-10 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Database access for Ruby
17
+ email: ezmobius@gmail.com, ssmoot@gmail.com, ciconia@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ - CHANGELOG
25
+ - COPYING
26
+ files:
27
+ - COPYING
28
+ - README
29
+ - Rakefile
30
+ - spec/blank_spec.rb
31
+ - spec/connection_pool_spec.rb
32
+ - spec/inflector_spec.rb
33
+ - spec/rcov.opts
34
+ - spec/spec.opts
35
+ - spec/spec_helper.rb
36
+ - spec/time_calculations_spec.rb
37
+ - lib/assistance
38
+ - lib/assistance/blank.rb
39
+ - lib/assistance/connection_pool.rb
40
+ - lib/assistance/core_ext.rb
41
+ - lib/assistance/inflections.rb
42
+ - lib/assistance/inflector.rb
43
+ - lib/assistance/time_calculations.rb
44
+ - lib/assistance.rb
45
+ - CHANGELOG
46
+ has_rdoc: true
47
+ homepage: http://assistance.rubyforge.org
48
+ post_install_message:
49
+ rdoc_options:
50
+ - --quiet
51
+ - --title
52
+ - "Assistance: light-weight application support"
53
+ - --opname
54
+ - index.html
55
+ - --line-numbers
56
+ - --main
57
+ - README
58
+ - --inline-source
59
+ - --exclude
60
+ - ^(examples|extras)/
61
+ - --exclude
62
+ - lib/assistance.rb
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: 1.8.4
70
+ version:
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ requirements: []
78
+
79
+ rubyforge_project: assistance
80
+ rubygems_version: 1.0.1
81
+ signing_key:
82
+ specification_version: 2
83
+ summary: Database access for Ruby
84
+ test_files: []
85
+