assistance 0.0.1

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/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
+