downpour 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/README.rdoc +9 -0
- data/downpour.gemspec +25 -0
- data/ext/downpour/connection.c +23 -0
- data/ext/downpour/drizzle.c +19 -0
- data/ext/downpour/drizzle.h +24 -0
- data/ext/downpour/extconf.rb +9 -0
- data/ext/downpour/helpers.c +39 -0
- data/ext/downpour/module.c +19 -0
- data/ext/downpour/result.c +92 -0
- data/ext/downpour/status.c +67 -0
- data/lib/downpour/version.rb +3 -0
- data/lib/downpour.rb +2 -0
- data/spec/bootstrap.sql +7 -0
- data/spec/module_spec.rb +5 -0
- data/spec/query_spec.rb +27 -0
- data/spec/smoke_spec.rb +61 -0
- data/spec/spec_helper.rb +5 -0
- metadata +98 -0
data/README.rdoc
ADDED
data/downpour.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.expand_path('../lib/downpour/version', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{downpour}
|
5
|
+
s.version = Downpour::VERSION
|
6
|
+
s.authors = ["Tejas Dinkar"]
|
7
|
+
s.date = Time.now.utc.strftime("%Y-%m-%d")
|
8
|
+
s.email = %q{tejas@gja.in}
|
9
|
+
s.extensions = ["ext/downpour/extconf.rb"]
|
10
|
+
s.extra_rdoc_files = [
|
11
|
+
"README.rdoc"
|
12
|
+
]
|
13
|
+
s.files = `git ls-files ext lib downpour.gemspec`.split("\n")
|
14
|
+
s.homepage = %q{http://github.com/gja/downpour}
|
15
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
16
|
+
s.require_paths = ["lib", "ext"]
|
17
|
+
s.rubygems_version = %q{1.4.2}
|
18
|
+
s.summary = %q{A simple, fast Mysql and Drizzle library for Ruby, binding to libdrizzle}
|
19
|
+
s.description = %q{A simple, fast Mysql and Drizzle library for Ruby, binding to libdrizzle. Still in early alpha}
|
20
|
+
s.test_files = `git ls-files spec examples`.split("\n")
|
21
|
+
|
22
|
+
# tests
|
23
|
+
s.add_development_dependency 'rake-compiler', "~> 0.7.1"
|
24
|
+
s.add_development_dependency 'rspec'
|
25
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#include "drizzle.h"
|
2
|
+
|
3
|
+
#define SELF_TYPE drizzle_con_st
|
4
|
+
|
5
|
+
static VALUE query(VALUE self, VALUE query)
|
6
|
+
{
|
7
|
+
read_self_ptr();
|
8
|
+
|
9
|
+
Check_Type(query, T_STRING);
|
10
|
+
|
11
|
+
drizzle_return_t retptr;
|
12
|
+
drizzle_result_st *result = drizzle_query(self_ptr, NULL, RSTRING_PTR(query), RSTRING_LEN(query), &retptr);
|
13
|
+
|
14
|
+
CHECK_OK(retptr);
|
15
|
+
|
16
|
+
return Data_Wrap_Struct(DrizzleResult, NULL, NULL, result);
|
17
|
+
}
|
18
|
+
|
19
|
+
void init_drizzle_connection()
|
20
|
+
{
|
21
|
+
DrizzleConnection = drizzle_gem_create_class_with_private_constructor("Connection", rb_cObject);
|
22
|
+
rb_define_method(DrizzleConnection, "query", query, 1);
|
23
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#include "drizzle.h"
|
2
|
+
|
3
|
+
VALUE DrizzleModule;
|
4
|
+
VALUE DrizzleStatus;
|
5
|
+
VALUE DrizzleConnection;
|
6
|
+
VALUE DrizzleResult;
|
7
|
+
|
8
|
+
void init_drizzle_module();
|
9
|
+
void init_drizzle_status();
|
10
|
+
void init_drizzle_connection();
|
11
|
+
void init_drizzle_result();
|
12
|
+
|
13
|
+
void Init_downpour()
|
14
|
+
{
|
15
|
+
init_drizzle_module();
|
16
|
+
init_drizzle_status();
|
17
|
+
init_drizzle_connection();
|
18
|
+
init_drizzle_result();
|
19
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#ifndef DRIZZLE_H
|
2
|
+
#define DRIZZLE_H
|
3
|
+
|
4
|
+
#include "ruby.h"
|
5
|
+
#include <libdrizzle/drizzle.h>
|
6
|
+
#include <libdrizzle/drizzle_client.h>
|
7
|
+
|
8
|
+
extern VALUE DrizzleModule;
|
9
|
+
extern VALUE DrizzleStatus;
|
10
|
+
extern VALUE DrizzleConnection;
|
11
|
+
extern VALUE DrizzleResult;
|
12
|
+
|
13
|
+
VALUE drizzle_gem_create_class_with_private_constructor(const char *name, VALUE super);
|
14
|
+
void drizzle_gem_assert_value_is_ok(drizzle_return_t value);
|
15
|
+
VALUE drizzle_gem_to_string_array(char **array, long count);
|
16
|
+
const char *drizzle_gem_read_string_with_default(VALUE string, const char *default_value);
|
17
|
+
|
18
|
+
#define convert_to_struct(datatype, var_name, value) datatype *var_name; Data_Get_Struct(value, datatype, var_name)
|
19
|
+
#define read_self_ptr() convert_to_struct(SELF_TYPE, self_ptr, self)
|
20
|
+
#define CHECK_OK(value) drizzle_gem_assert_value_is_ok(value)
|
21
|
+
#define rb_call(self, string) rb_funcall(self, rb_intern(string), 0)
|
22
|
+
#define read_string(value, default_value) drizzle_gem_read_string_with_default(value, default_value)
|
23
|
+
|
24
|
+
#endif
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#include "drizzle.h"
|
2
|
+
|
3
|
+
static VALUE do_not_use_this_constructor(VALUE self)
|
4
|
+
{
|
5
|
+
rb_raise(rb_eNameError, "private method `new' called for %s:Class.", rb_class2name(CLASS_OF(self)));
|
6
|
+
}
|
7
|
+
|
8
|
+
VALUE drizzle_gem_to_string_array(char **array, long count)
|
9
|
+
{
|
10
|
+
VALUE ary = rb_ary_new2(count);
|
11
|
+
|
12
|
+
long i;
|
13
|
+
for(i = 0; i < count; i++)
|
14
|
+
rb_ary_push(ary, rb_str_new2(array[i]));
|
15
|
+
|
16
|
+
return ary;
|
17
|
+
}
|
18
|
+
|
19
|
+
VALUE drizzle_gem_create_class_with_private_constructor(const char *name, VALUE super)
|
20
|
+
{
|
21
|
+
VALUE ret = rb_define_class_under(DrizzleModule, name, super);
|
22
|
+
rb_define_method(ret, "initialize", do_not_use_this_constructor, 0);
|
23
|
+
return ret;
|
24
|
+
}
|
25
|
+
|
26
|
+
void drizzle_gem_assert_value_is_ok(drizzle_return_t ret)
|
27
|
+
{
|
28
|
+
if(ret != DRIZZLE_RETURN_OK)
|
29
|
+
rb_raise(rb_eIOError, "Action Failed with error code: %d. Please check the error string on status object for more information", ret);
|
30
|
+
}
|
31
|
+
|
32
|
+
const char *drizzle_gem_read_string_with_default(VALUE string, const char *default_value)
|
33
|
+
{
|
34
|
+
if(string == Qnil)
|
35
|
+
return default_value;
|
36
|
+
|
37
|
+
Check_Type(string, T_STRING);
|
38
|
+
return RSTRING_PTR(string);
|
39
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#include "drizzle.h"
|
2
|
+
|
3
|
+
static VALUE version(VALUE self)
|
4
|
+
{
|
5
|
+
return rb_str_new2(drizzle_version());
|
6
|
+
}
|
7
|
+
|
8
|
+
static VALUE create(VALUE self)
|
9
|
+
{
|
10
|
+
drizzle_st *ptr = drizzle_create(NULL);
|
11
|
+
return Data_Wrap_Struct(DrizzleStatus, NULL, drizzle_free, ptr);
|
12
|
+
}
|
13
|
+
|
14
|
+
void init_drizzle_module()
|
15
|
+
{
|
16
|
+
DrizzleModule = rb_define_module("Drizzle");
|
17
|
+
rb_define_singleton_method(DrizzleModule, "version", version, 0);
|
18
|
+
rb_define_singleton_method(DrizzleModule, "create", create, 0);
|
19
|
+
}
|
@@ -0,0 +1,92 @@
|
|
1
|
+
#include "drizzle.h"
|
2
|
+
|
3
|
+
#define SELF_TYPE drizzle_result_st
|
4
|
+
|
5
|
+
static uint64_t do_column_count(drizzle_result_st *self_ptr)
|
6
|
+
{
|
7
|
+
return drizzle_result_column_count(self_ptr);
|
8
|
+
}
|
9
|
+
|
10
|
+
static VALUE is_buffered(VALUE self)
|
11
|
+
{
|
12
|
+
VALUE buffered = rb_iv_get(self, "@buffered");
|
13
|
+
|
14
|
+
if(buffered == Qnil || buffered == Qfalse)
|
15
|
+
return Qfalse;
|
16
|
+
|
17
|
+
return buffered;
|
18
|
+
}
|
19
|
+
|
20
|
+
static bool is_buffered_bool(VALUE self)
|
21
|
+
{
|
22
|
+
return RTEST(rb_call(self, "buffered?"));
|
23
|
+
}
|
24
|
+
|
25
|
+
static VALUE buffer_if_needed(VALUE self)
|
26
|
+
{
|
27
|
+
read_self_ptr();
|
28
|
+
|
29
|
+
// Only buffer once
|
30
|
+
if(is_buffered_bool(self))
|
31
|
+
return Qfalse;
|
32
|
+
|
33
|
+
CHECK_OK(drizzle_result_buffer(self_ptr));
|
34
|
+
rb_iv_set(self, "@buffered", Qtrue);
|
35
|
+
return Qtrue;
|
36
|
+
}
|
37
|
+
|
38
|
+
static VALUE row_count(VALUE self)
|
39
|
+
{
|
40
|
+
rb_call(self, "buffer!");
|
41
|
+
read_self_ptr();
|
42
|
+
return UINT2NUM(drizzle_result_row_count(self_ptr));
|
43
|
+
}
|
44
|
+
|
45
|
+
static VALUE wrap_row(drizzle_result_st *self_ptr, drizzle_row_t row)
|
46
|
+
{
|
47
|
+
// No more rows to read :-)
|
48
|
+
if(row == NULL)
|
49
|
+
return Qnil;
|
50
|
+
|
51
|
+
// TODO: wrap this in a Ruby Object. It should be freed
|
52
|
+
return drizzle_gem_to_string_array(row, do_column_count(self_ptr));
|
53
|
+
}
|
54
|
+
|
55
|
+
static VALUE next_row_buffered(drizzle_result_st *self_ptr)
|
56
|
+
{
|
57
|
+
return wrap_row(self_ptr, drizzle_row_next(self_ptr));
|
58
|
+
}
|
59
|
+
|
60
|
+
static VALUE next_row_unbuffered(drizzle_result_st *self_ptr)
|
61
|
+
{
|
62
|
+
drizzle_return_t ret;
|
63
|
+
drizzle_row_t result = drizzle_row_buffer(self_ptr, &ret);
|
64
|
+
CHECK_OK(ret);
|
65
|
+
return wrap_row(self_ptr, result);
|
66
|
+
}
|
67
|
+
|
68
|
+
static VALUE next_row(VALUE self)
|
69
|
+
{
|
70
|
+
read_self_ptr();
|
71
|
+
|
72
|
+
if(is_buffered_bool(self))
|
73
|
+
return next_row_buffered(self_ptr);
|
74
|
+
|
75
|
+
return next_row_unbuffered(self_ptr);
|
76
|
+
}
|
77
|
+
|
78
|
+
static VALUE column_count(VALUE self)
|
79
|
+
{
|
80
|
+
read_self_ptr();
|
81
|
+
return UINT2NUM(do_column_count(self_ptr));
|
82
|
+
}
|
83
|
+
|
84
|
+
void init_drizzle_result()
|
85
|
+
{
|
86
|
+
DrizzleResult = drizzle_gem_create_class_with_private_constructor("Result", rb_cObject);
|
87
|
+
rb_define_method(DrizzleResult, "row_count", row_count, 0);
|
88
|
+
rb_define_method(DrizzleResult, "column_count", column_count, 0);
|
89
|
+
rb_define_method(DrizzleResult, "buffer!", buffer_if_needed, 0);
|
90
|
+
rb_define_method(DrizzleResult, "buffered?", is_buffered, 0);
|
91
|
+
rb_define_method(DrizzleResult, "next_row", next_row, 0);
|
92
|
+
}
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#include "drizzle.h"
|
2
|
+
|
3
|
+
#define SELF_TYPE drizzle_st
|
4
|
+
|
5
|
+
static VALUE clone_status(VALUE self)
|
6
|
+
{
|
7
|
+
read_self_ptr();
|
8
|
+
drizzle_st *cloned = drizzle_clone(NULL, self_ptr);
|
9
|
+
return Data_Wrap_Struct(DrizzleStatus, NULL, drizzle_free, cloned);
|
10
|
+
}
|
11
|
+
|
12
|
+
static in_port_t get_port(VALUE port)
|
13
|
+
{
|
14
|
+
if(port == Qnil)
|
15
|
+
return DRIZZLE_DEFAULT_TCP_PORT;
|
16
|
+
Check_Type(port, T_FIXNUM);
|
17
|
+
return NUM2UINT(port);
|
18
|
+
}
|
19
|
+
|
20
|
+
|
21
|
+
static VALUE add_tcp_connection(int argc, VALUE *argv, VALUE self)
|
22
|
+
{
|
23
|
+
read_self_ptr();
|
24
|
+
|
25
|
+
VALUE host, port, user, passwd, db, options;
|
26
|
+
|
27
|
+
//TODO: We ignore options
|
28
|
+
rb_scan_args(argc, argv, "06", &host, &user, &passwd, &db, &port, &options);
|
29
|
+
|
30
|
+
drizzle_con_st *connection = drizzle_con_add_tcp(self_ptr, NULL,
|
31
|
+
read_string(host, "localhost"),
|
32
|
+
get_port(port),
|
33
|
+
read_string(user, ""),
|
34
|
+
read_string(passwd, ""),
|
35
|
+
read_string(db, "test"),
|
36
|
+
DRIZZLE_CON_NONE);
|
37
|
+
|
38
|
+
VALUE val = Data_Wrap_Struct(DrizzleConnection, NULL, NULL, connection);
|
39
|
+
rb_iv_set(val, "@status", self);
|
40
|
+
return val;
|
41
|
+
}
|
42
|
+
|
43
|
+
static VALUE error(VALUE self)
|
44
|
+
{
|
45
|
+
read_self_ptr();
|
46
|
+
|
47
|
+
return rb_str_new2(drizzle_error(self_ptr));
|
48
|
+
}
|
49
|
+
|
50
|
+
static VALUE set_verbose(VALUE self, VALUE newVerbocity)
|
51
|
+
{
|
52
|
+
read_self_ptr();
|
53
|
+
|
54
|
+
int verbocity = NUM2INT(newVerbocity);
|
55
|
+
drizzle_set_verbose(self_ptr, verbocity);
|
56
|
+
|
57
|
+
return newVerbocity;
|
58
|
+
}
|
59
|
+
|
60
|
+
void init_drizzle_status()
|
61
|
+
{
|
62
|
+
DrizzleStatus = drizzle_gem_create_class_with_private_constructor("Status", rb_cObject);
|
63
|
+
rb_define_method(DrizzleStatus, "clone", clone_status, 0);
|
64
|
+
rb_define_method(DrizzleStatus, "add_tcp_connection", add_tcp_connection, -1);
|
65
|
+
rb_define_method(DrizzleStatus, "error", error, 0);
|
66
|
+
rb_define_method(DrizzleStatus, "verbose=", set_verbose, 1);
|
67
|
+
}
|
data/lib/downpour.rb
ADDED
data/spec/bootstrap.sql
ADDED
data/spec/module_spec.rb
ADDED
data/spec/query_spec.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
describe Drizzle do
|
2
|
+
before(:each) do
|
3
|
+
@status = Drizzle.create
|
4
|
+
@connection = create_connection(@status)
|
5
|
+
end
|
6
|
+
|
7
|
+
context "error handling" do
|
8
|
+
|
9
|
+
it "should be able to catch errors" do
|
10
|
+
begin
|
11
|
+
query = @connection.query "this is invalid sql"
|
12
|
+
raise "should have failed here"
|
13
|
+
rescue IOError
|
14
|
+
@status.error.should == "You have an error in your SQL syntax; check the manual that corresponds to your Drizzle server version for the right syntax to use near 'this is invalid sql' at line 1"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should only accept a string as query" do
|
19
|
+
begin
|
20
|
+
@connection.query 42
|
21
|
+
raise "should have failed here"
|
22
|
+
rescue TypeError
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
data/spec/smoke_spec.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
describe Drizzle do
|
2
|
+
|
3
|
+
before(:each) do
|
4
|
+
@status = Drizzle.create
|
5
|
+
@conn = create_connection(@status)
|
6
|
+
end
|
7
|
+
|
8
|
+
context "select query" do
|
9
|
+
before(:each) do
|
10
|
+
@results = @conn.query "select * from Test1"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should count records" do
|
14
|
+
@results.row_count.should == 3
|
15
|
+
@results.should be_buffered
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should buffer records" do
|
19
|
+
@results.should_not be_buffered
|
20
|
+
@results.buffer!
|
21
|
+
@results.should be_buffered
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should only buffer records once" do
|
25
|
+
@results.buffer!.should be_true
|
26
|
+
@results.buffer!.should be_false
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should count columns" do
|
30
|
+
@results.column_count.should == 1
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should not buffer when reading a single row" do
|
34
|
+
@results.next_row
|
35
|
+
@results.should_not be_buffered
|
36
|
+
end
|
37
|
+
|
38
|
+
shared_examples_for "a read query" do
|
39
|
+
it "should read all rows" do
|
40
|
+
@results.next_row.should == ["foo"]
|
41
|
+
@results.next_row.should == ["bar"]
|
42
|
+
@results.next_row.should == ["baz"]
|
43
|
+
@results.next_row.should be_nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "without buffering" do
|
48
|
+
it_should_behave_like "a read query"
|
49
|
+
end
|
50
|
+
|
51
|
+
context "with buffering" do
|
52
|
+
before(:each) { @results.buffer! }
|
53
|
+
|
54
|
+
it_should_behave_like "a read query"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
after(:each) do
|
59
|
+
#puts @status.error
|
60
|
+
end
|
61
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: downpour
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tejas Dinkar
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-05-25 00:00:00 +05:30
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rake-compiler
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ~>
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.7.1
|
25
|
+
type: :development
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: "0"
|
36
|
+
type: :development
|
37
|
+
version_requirements: *id002
|
38
|
+
description: A simple, fast Mysql and Drizzle library for Ruby, binding to libdrizzle. Still in early alpha
|
39
|
+
email: tejas@gja.in
|
40
|
+
executables: []
|
41
|
+
|
42
|
+
extensions:
|
43
|
+
- ext/downpour/extconf.rb
|
44
|
+
extra_rdoc_files:
|
45
|
+
- README.rdoc
|
46
|
+
files:
|
47
|
+
- downpour.gemspec
|
48
|
+
- ext/downpour/connection.c
|
49
|
+
- ext/downpour/drizzle.c
|
50
|
+
- ext/downpour/drizzle.h
|
51
|
+
- ext/downpour/extconf.rb
|
52
|
+
- ext/downpour/helpers.c
|
53
|
+
- ext/downpour/module.c
|
54
|
+
- ext/downpour/result.c
|
55
|
+
- ext/downpour/status.c
|
56
|
+
- lib/downpour.rb
|
57
|
+
- lib/downpour/version.rb
|
58
|
+
- README.rdoc
|
59
|
+
- spec/bootstrap.sql
|
60
|
+
- spec/module_spec.rb
|
61
|
+
- spec/query_spec.rb
|
62
|
+
- spec/smoke_spec.rb
|
63
|
+
- spec/spec_helper.rb
|
64
|
+
has_rdoc: true
|
65
|
+
homepage: http://github.com/gja/downpour
|
66
|
+
licenses: []
|
67
|
+
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options:
|
70
|
+
- --charset=UTF-8
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
- ext
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: "0"
|
86
|
+
requirements: []
|
87
|
+
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 1.6.2
|
90
|
+
signing_key:
|
91
|
+
specification_version: 3
|
92
|
+
summary: A simple, fast Mysql and Drizzle library for Ruby, binding to libdrizzle
|
93
|
+
test_files:
|
94
|
+
- spec/bootstrap.sql
|
95
|
+
- spec/module_spec.rb
|
96
|
+
- spec/query_spec.rb
|
97
|
+
- spec/smoke_spec.rb
|
98
|
+
- spec/spec_helper.rb
|