data_factory 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/DataFactory-0.1.gem +0 -0
- data/README.md +129 -0
- data/Rakefile.rb +30 -0
- data/lib/data_factory.rb +12 -0
- data/lib/data_factory/base.rb +16 -0
- data/lib/data_factory/base_api.rb +245 -0
- data/lib/data_factory/base_dsl.rb +175 -0
- data/lib/data_factory/base_factory.rb +41 -0
- data/lib/data_factory/column.rb +18 -0
- data/lib/data_factory/exceptions.rb +6 -0
- data/lib/data_factory/random.rb +28 -0
- data/test/base_api_test.rb +151 -0
- data/test/base_dsl_test.rb +109 -0
- data/test/helper.rb +49 -0
- data/test/sanity.rb +22 -0
- data/test/test_runner.rb +11 -0
- metadata +77 -0
data/DataFactory-0.1.gem
ADDED
Binary file
|
data/README.md
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
# DataFactory
|
2
|
+
|
3
|
+
DataFactory is a simple Ruby gem that generates data random test data and inserts it into database tables. It was created to help with unit testing in a project that had database tables having many (50+) columns, and manually crafing insert statements was tedious and error prone.
|
4
|
+
|
5
|
+
DataFactory reads the table definition from the database, and generates random values for all not null columns. It inserts this data into the table, while providing the option of specifying non-random defaults.
|
6
|
+
|
7
|
+
In the 0.1 release DataFactory can be considered beta software. In other words it is not really production ready, but then as it generates random test data, it is not really designed to run on a production system, so that is probably OK. It is also missing many potential features, and could easily be enhanced to do much more.
|
8
|
+
|
9
|
+
# Database Compatibility
|
10
|
+
|
11
|
+
Right now, DataFactory only works with Oracle databases. It should be fairly easy to extend it to other databases, but all the DB interactions have not been abstracted into an access layer, so there would be some work to do.
|
12
|
+
|
13
|
+
# Requirements
|
14
|
+
|
15
|
+
There are no strict dependencies for DataFactory to work, and it is a pure Ruby gem.
|
16
|
+
|
17
|
+
However, DataFactory doesn't actually have a database access layer built in, as it is designed to use an external access layer that knows how to connect and query the database.
|
18
|
+
|
19
|
+
If you can use JRuby, consider using Simple JDBC Oracle to interact with your database. If you cannot use JRuby, implementing your own database interface is simple. Create a class that handles creating a database connection, and implement the following three methods to run SQL statements on the database, and issue commits:
|
20
|
+
|
21
|
+
# should return an object that implements the each_array method
|
22
|
+
# below
|
23
|
+
def execute_sql(statement, *binds)
|
24
|
+
end
|
25
|
+
|
26
|
+
def commit
|
27
|
+
end
|
28
|
+
|
29
|
+
def each_array(&blk)
|
30
|
+
end
|
31
|
+
|
32
|
+
The first two methods are fairly obvious. The each_array method is expected to iterate over the result set returned by the executed sql statement, passing an array of columns to the block for each row returned. The array should contain the value of each column in Ruby types, ie not Java types if using JRuby.
|
33
|
+
|
34
|
+
The OCI8 gem is pretty good place to start if you are using MRI Ruby, but you will need the Oracle client installed. Raw JDBC can get the job done if you are using JRuby and you do not want to use Simple JDBC Oracle.
|
35
|
+
|
36
|
+
# Usage
|
37
|
+
|
38
|
+
DataFactory is a simple gem, so a few examples explore a lot of the functionality. Note that these examples use Simple Oracle JDBC as the database access layer.
|
39
|
+
|
40
|
+
For these examples to run, create a table on the database as follows:
|
41
|
+
|
42
|
+
create table employees (emp_id integer,
|
43
|
+
dept_id integer,
|
44
|
+
first_name varchar2(50),
|
45
|
+
last_name varchar2(50),
|
46
|
+
email varchar2(50),
|
47
|
+
ssn varchar2(10));
|
48
|
+
|
49
|
+
## Define a DataFactory Class
|
50
|
+
|
51
|
+
To use DataFactory, create a class for each table you want to interface with, and make it a sub-class of DataFactory::Base:
|
52
|
+
|
53
|
+
class Employee < DataFactory::Base
|
54
|
+
|
55
|
+
set_table_name "employees"
|
56
|
+
|
57
|
+
set_column_default :last_name, "Smith"
|
58
|
+
set_column_default :email, begin
|
59
|
+
"#{rand(10000)}@#{rand(10000)}.com"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
In the class definition, use the set_table_name method to map the class to a particular table on the database.
|
64
|
+
|
65
|
+
Optionally, you can specify default values for columns in the table with the set_column_default method, which takes the table name followed by a value for the column, or a block that generates the value each time it is called, as with the email example.
|
66
|
+
|
67
|
+
|
68
|
+
## Creating a Row
|
69
|
+
|
70
|
+
The first requirement is to connect to the database, and hand an instance of the database interface to DataFactory:
|
71
|
+
|
72
|
+
interface = SimpleOracleJDBC::Interface.create('sodonnel',
|
73
|
+
'sodonnel',
|
74
|
+
'local11gr2.world',
|
75
|
+
'localhost',
|
76
|
+
'1521')
|
77
|
+
|
78
|
+
DataFactory::Base.set_database_interface(interface)
|
79
|
+
|
80
|
+
Then a row can be created using the create! method, for example:
|
81
|
+
|
82
|
+
f = Employee.create!("emp_id" => 1001)
|
83
|
+
|
84
|
+
The create! call will take the column defaults defined in the Employee class, and merge in any column values passed into the create! method. Then it will generate a value for any other non-nullable columns in the table, and insert the row into the database.
|
85
|
+
|
86
|
+
An Employee instance is returned, containing all the generated values.
|
87
|
+
|
88
|
+
## Putting It Together
|
89
|
+
|
90
|
+
Combining each of the steps above, gives the following script:
|
91
|
+
|
92
|
+
require 'rubygems'
|
93
|
+
require 'simple_oracle_jdbc'
|
94
|
+
require 'data_factory'
|
95
|
+
|
96
|
+
class Employee < DataFactory::Base
|
97
|
+
|
98
|
+
set_table_name "employees"
|
99
|
+
|
100
|
+
set_column_default :last_name, "Smith"
|
101
|
+
set_column_default :email, begin
|
102
|
+
"#{rand(10000)}@#{rand(10000)}.com"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
interface = SimpleOracleJDBC::Interface.create('sodonnel',
|
107
|
+
'sodonnel',
|
108
|
+
'local11gr2.world',
|
109
|
+
'localhost',
|
110
|
+
'1521')
|
111
|
+
|
112
|
+
DataFactory::Base.set_database_interface(interface)
|
113
|
+
|
114
|
+
f = Employee.create!("emp_id" => 1001)
|
115
|
+
|
116
|
+
f.column_values.keys.each do |k|
|
117
|
+
puts f.column_values[k]
|
118
|
+
end
|
119
|
+
|
120
|
+
## Other Methods
|
121
|
+
|
122
|
+
The sample above illustrates how the create! method returns an Employee object, giving access to the generated values. For an overview of other methods browse the documentation for the base_api, base_dsl and base_factory methods.
|
123
|
+
|
124
|
+
|
125
|
+
|
126
|
+
|
127
|
+
|
128
|
+
|
129
|
+
|
data/Rakefile.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
desc "Generate RDoc"
|
2
|
+
task :doc => ['doc:generate']
|
3
|
+
|
4
|
+
namespace :doc do
|
5
|
+
project_root = File.expand_path(File.dirname(__FILE__))
|
6
|
+
doc_destination = File.join(project_root, 'doc')
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'yard'
|
10
|
+
require 'yard/rake/yardoc_task'
|
11
|
+
|
12
|
+
YARD::Rake::YardocTask.new(:generate) do |yt|
|
13
|
+
yt.files = Dir.glob(File.join(project_root, 'lib/**/*.rb')) + #, '**', '*.rb')) +
|
14
|
+
[ File.join(project_root, 'README.md') ]
|
15
|
+
puts yt.files
|
16
|
+
yt.options = ['--output-dir', doc_destination, '--readme', 'README.md']
|
17
|
+
end
|
18
|
+
rescue LoadError
|
19
|
+
desc "Generate YARD Documentation"
|
20
|
+
task :generate do
|
21
|
+
abort "Please install the YARD gem to generate rdoc."
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "Remove generated documenation"
|
26
|
+
task :clean do
|
27
|
+
rm_r doc_dir if File.exists?(doc_destination)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/lib/data_factory.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
require 'data_factory/random'
|
4
|
+
require 'data_factory/exceptions'
|
5
|
+
require 'data_factory/column'
|
6
|
+
require 'data_factory/base_dsl'
|
7
|
+
require 'data_factory/base_factory'
|
8
|
+
require 'data_factory/base_api'
|
9
|
+
|
10
|
+
require 'data_factory/base'
|
11
|
+
|
12
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module DataFactory
|
2
|
+
|
3
|
+
# The Base class provides a class designed to be sub-classed. It does not create any
|
4
|
+
# methods on its own, but is extended with class methods from the BaseDSL and BaseFactory
|
5
|
+
# modules and has the BaseAPI module included to provide instance methods.
|
6
|
+
|
7
|
+
class Base
|
8
|
+
|
9
|
+
extend BaseDSL
|
10
|
+
extend BaseFactory
|
11
|
+
|
12
|
+
include BaseAPI
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,245 @@
|
|
1
|
+
module DataFactory
|
2
|
+
|
3
|
+
# This module is included into the DataFactory::Base class providing methods
|
4
|
+
# to generate data and insert it into the database.
|
5
|
+
#
|
6
|
+
# For most use cases, these methods will not need to be called. The
|
7
|
+
# DataFactory::BaseFactory module provides factory methods that create objects and insert
|
8
|
+
# the data into database
|
9
|
+
|
10
|
+
module BaseAPI
|
11
|
+
|
12
|
+
include Random
|
13
|
+
|
14
|
+
attr_reader :column_values, :insert_statement, :binds
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
unless self.class.meta_data_loaded
|
18
|
+
self.class.load_meta_data
|
19
|
+
end
|
20
|
+
@column_values = Hash.new
|
21
|
+
end
|
22
|
+
|
23
|
+
# Retrieves the table name this class interfaces with on the database
|
24
|
+
def table_name
|
25
|
+
self.class.table_name
|
26
|
+
end
|
27
|
+
|
28
|
+
def column_details # :nodoc:
|
29
|
+
self.class.column_details
|
30
|
+
end
|
31
|
+
|
32
|
+
def column_defaults # :nodoc:
|
33
|
+
self.class.column_defaults
|
34
|
+
end
|
35
|
+
|
36
|
+
def column_detail(key) # :nodoc:
|
37
|
+
self.class.column_detail(key)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Retrieves the default value set for a column, which can be a proc
|
41
|
+
# or any Ruby object in general, but in practice is likely to be a
|
42
|
+
# Date, Time, String, Float, Integer
|
43
|
+
def column_default(key)
|
44
|
+
self.class.column_default(key)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the value assigned to a column, or nil if it is not defined
|
48
|
+
def column_value(key)
|
49
|
+
# ensure the requested column is in the table
|
50
|
+
column_detail(key)
|
51
|
+
@column_values[key.to_s.upcase]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the database interface object
|
55
|
+
def db_interface
|
56
|
+
self.class.database_interface
|
57
|
+
end
|
58
|
+
|
59
|
+
# Commit changes to the database
|
60
|
+
def commit
|
61
|
+
db_interface.commit
|
62
|
+
end
|
63
|
+
|
64
|
+
# Generates values for all the columns in the table.
|
65
|
+
#
|
66
|
+
# If no value is passed into this procedure for a column, a default will be used
|
67
|
+
# if it is configured. Otherwise a random value will be generated.
|
68
|
+
#
|
69
|
+
# Values for a column can be passed into this method using a hash, eg:
|
70
|
+
#
|
71
|
+
# obj.generate_column_data(:emp_id => 1, :last_name => 'Smith)
|
72
|
+
def generate_column_data(params=Hash.new)
|
73
|
+
self.class.column_details.keys.each do |k|
|
74
|
+
if column_default(k)
|
75
|
+
if column_default(k).is_a?(Proc)
|
76
|
+
@column_values[k] = column_default(k).call
|
77
|
+
else
|
78
|
+
@column_values[k] = column_default(k)
|
79
|
+
end
|
80
|
+
else
|
81
|
+
@column_values[k] = generate_value(column_detail(k))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
normalised_params = internalize_column_params(params)
|
85
|
+
validate_columns(normalised_params)
|
86
|
+
@column_values.merge! normalised_params
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
# Generates an insert statement for the current state of the object. It is intended
|
91
|
+
# to be called after generate_column_data has been called, otherwise all the values
|
92
|
+
# will be null.
|
93
|
+
#
|
94
|
+
# The generated statement returned as a string and also stored in the @insert_statement
|
95
|
+
# instance variable.
|
96
|
+
#
|
97
|
+
# The insert statement will contain bind variables for all columns. The generated bind
|
98
|
+
# variabled are stored in @binds.
|
99
|
+
def generate_insert
|
100
|
+
@binds = Array.new
|
101
|
+
@insert_statement = "insert into #{table_name} ("
|
102
|
+
@insert_statement << column_details.keys.sort.map { |k| column_detail(k).column_name }.join(',')
|
103
|
+
@insert_statement << ') values ('
|
104
|
+
@insert_statement << column_details.keys.sort.map { |k|
|
105
|
+
":#{k}"
|
106
|
+
}.join(',')
|
107
|
+
column_details.keys.sort.each { |k|
|
108
|
+
if @column_values[k] == nil
|
109
|
+
@binds.push [column_type_to_ruby_type(column_details[k]), nil]
|
110
|
+
else
|
111
|
+
@binds.push @column_values[k]
|
112
|
+
end
|
113
|
+
}
|
114
|
+
@insert_statement << ')'
|
115
|
+
@insert_statement
|
116
|
+
end
|
117
|
+
|
118
|
+
# Runs the insert statement prepared by generate_insert, using the insert
|
119
|
+
# statement stored in @insert_statement and the bind variables in @binds
|
120
|
+
#
|
121
|
+
# If generate_insert has not be executed, this procedure will raise
|
122
|
+
# a DataFactory::NoInsertStatement exeception
|
123
|
+
def run_insert
|
124
|
+
raise DataFactory::NoInsertStatement unless @insert_statement
|
125
|
+
|
126
|
+
stmt = db_interface.execute_sql(@insert_statement, *@binds)
|
127
|
+
stmt.close
|
128
|
+
end
|
129
|
+
|
130
|
+
# Generates a where clause for the DataFactory object. This method looks at the columns
|
131
|
+
# on the table, and the values set against them, and generates a string representing a
|
132
|
+
# where clause that can be used in an SQL query.
|
133
|
+
#
|
134
|
+
# The generated string does not contain the 'where' keyword.
|
135
|
+
def where_clause_for(cols)
|
136
|
+
cols.map{|c| c.to_s.upcase}.map{|c|
|
137
|
+
if @column_values[c] == nil
|
138
|
+
"#{c} is null"
|
139
|
+
else
|
140
|
+
"#{c} = #{quote_value(c) }"
|
141
|
+
end
|
142
|
+
}.join " and "
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
# TODO - This needs to be extracted into a DB specific access layer, as it is
|
148
|
+
# Oracle specific.
|
149
|
+
def quote_value(col)
|
150
|
+
case column_detail(col).data_type
|
151
|
+
when 'CHAR', 'VARCHAR2', 'CLOB'
|
152
|
+
"'#{@column_values[col]}'"
|
153
|
+
when 'DATE', 'DATETIME'
|
154
|
+
"to_date('#{@column_values[col].strftime('%Y%m%d %H:%M:%S')}', 'YYYYMMDD HH24:MI:SS')"
|
155
|
+
else
|
156
|
+
@column_values[col].to_s
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def generate_value(col)
|
161
|
+
if col.nullable?
|
162
|
+
return nil
|
163
|
+
end
|
164
|
+
|
165
|
+
case col.data_type
|
166
|
+
when 'CHAR', 'VARCHAR2', 'CLOB'
|
167
|
+
random_string_upto_length(col.data_length)
|
168
|
+
when 'DATE', 'DATETIME', 'TIMESTAMP'
|
169
|
+
Time.now
|
170
|
+
when 'NUMBER', 'INTEGER'
|
171
|
+
scale = 2
|
172
|
+
if col.data_scale && col.data_scale == 0
|
173
|
+
1
|
174
|
+
else
|
175
|
+
22.23
|
176
|
+
end
|
177
|
+
|
178
|
+
# # random numbers is very much beta and very untested.
|
179
|
+
# #
|
180
|
+
# # 38 digits total, 28 before decimal point, and 10 after.
|
181
|
+
# precision = 38
|
182
|
+
# scale = 10
|
183
|
+
# if col.data_precision
|
184
|
+
# precision = col.data_precision
|
185
|
+
# end
|
186
|
+
# if col.data_scale
|
187
|
+
# scale = col.data_scale
|
188
|
+
# end
|
189
|
+
|
190
|
+
# if scale == 0
|
191
|
+
# # its an integer
|
192
|
+
# random_integer(10**col.data_precision - 1)
|
193
|
+
# else
|
194
|
+
# # its a number
|
195
|
+
# max_left = 10**(precision - scale) - 1
|
196
|
+
# max_right = 10**scale - 1
|
197
|
+
# "#{random_integer(max_left)}.#{random_integer(max_right)}".to_f
|
198
|
+
# end
|
199
|
+
when 'NUMBER'
|
200
|
+
23.34
|
201
|
+
when 'INTEGER'
|
202
|
+
else
|
203
|
+
nil
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def column_type_to_ruby_type(col)
|
208
|
+
case col.data_type
|
209
|
+
when 'CHAR', 'VARCHAR2', 'CLOB'
|
210
|
+
String
|
211
|
+
when 'DATE', 'DATETIME', 'TIMESTAMP'
|
212
|
+
Time
|
213
|
+
when 'INTEGER'
|
214
|
+
Integer
|
215
|
+
when 'NUMBER'
|
216
|
+
if col.data_scale == 0
|
217
|
+
Integer
|
218
|
+
else
|
219
|
+
Float
|
220
|
+
end
|
221
|
+
else
|
222
|
+
raise "unsupported datatype #{col.data_type}"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def internalize_column_params(params)
|
227
|
+
upcased_params = Hash.new
|
228
|
+
params.keys.each do |k|
|
229
|
+
# change it from a symbol(if it is a symbol) to a string and uppercase.
|
230
|
+
upcased_params[k.to_s.upcase] = params[k]
|
231
|
+
end
|
232
|
+
upcased_params
|
233
|
+
end
|
234
|
+
|
235
|
+
def validate_columns(params)
|
236
|
+
params.keys.each do |k|
|
237
|
+
unless column_details.has_key? k.upcase
|
238
|
+
raise DataFactory::ColumnNotInTable
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
end
|
245
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
module DataFactory
|
2
|
+
|
3
|
+
# This module is used to extend the DataFactory::Base class, and so creates a
|
4
|
+
# series of class instance methods that can be used in class definitions.
|
5
|
+
#
|
6
|
+
# For example:
|
7
|
+
#
|
8
|
+
# class Foo < DataFactory::Base
|
9
|
+
#
|
10
|
+
# set_table_name "Foobar"
|
11
|
+
# set_column_default :last_name, 'Smith'
|
12
|
+
#
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# For this reason, calling any of these methods on a class, will affect ALL instances
|
16
|
+
# of that class.
|
17
|
+
#
|
18
|
+
# Any subclasses expect the class variables @@db_interface to be set, containing a database
|
19
|
+
# access class, eg:
|
20
|
+
#
|
21
|
+
# DataFactory::Base.set_database_interface(interface_obj)
|
22
|
+
#
|
23
|
+
# As this is stored in a class variable the same database interface is shared down the entire
|
24
|
+
# inheritance chain. Therefore if many database connections are required, several base classes
|
25
|
+
# will need to be created by including the relevant modules, eg:
|
26
|
+
#
|
27
|
+
# class OtherBaseClass
|
28
|
+
# extend BaseDSL
|
29
|
+
# extend BaseFactory
|
30
|
+
# include BaseAPI
|
31
|
+
# end
|
32
|
+
|
33
|
+
module BaseDSL
|
34
|
+
|
35
|
+
attr_reader :table_name, :column_details, :column_defaults, :meta_data_loaded
|
36
|
+
|
37
|
+
# Pass a database interface object to be used by all DataFactory sub-classes
|
38
|
+
# The interface must implement the following two methods:
|
39
|
+
#
|
40
|
+
# def execute_sql(statement, *binds)
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# def each_array(&blk)
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# def commit
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# This method is normally called on the DataFactory::Base class. It will
|
50
|
+
# set a class variable (@@db_interface) in the Base class which is shared
|
51
|
+
# by all sub-classes of the class.
|
52
|
+
#
|
53
|
+
# Ideally use the SimpleOracleJDBC gem to provide the interface
|
54
|
+
def set_database_interface(interface)
|
55
|
+
class_variable_set("@@db_interface", interface)
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
# Returns the database interface that was set by set_database_interface
|
60
|
+
def database_interface
|
61
|
+
class_variable_get("@@db_interface")
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
# Defines the table a subclass of DataFactory interacts with on the database. This
|
66
|
+
# method stores the tables in a class instance variable (@table_name) that is shared by
|
67
|
+
# all instances of the class, but is not inherited with subclasses.
|
68
|
+
def set_table_name(tab)
|
69
|
+
@table_name = tab.to_s.upcase
|
70
|
+
end
|
71
|
+
|
72
|
+
# Sets the default value to be used for a column if nothing else is
|
73
|
+
# specified. The block is optional and should be used if dynamic defaults
|
74
|
+
# are required. If the default is a constant, a simple value can be specified.
|
75
|
+
# The default is stored within a hash which is stored in a class instance variable
|
76
|
+
# (@column_defaults) that is shared by all instances of the class, but is not inherited
|
77
|
+
# with subclasses.
|
78
|
+
def set_column_default(column_name, value=nil, &block)
|
79
|
+
unless defined? @column_defaults
|
80
|
+
@column_defaults = Hash.new
|
81
|
+
end
|
82
|
+
@column_defaults[column_name.to_s.upcase] = block_given? ? block :
|
83
|
+
value.is_a?(Symbol) ? value.to_s : value
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
# Returns the value for a column default. If it is a simple value, the value
|
88
|
+
# is returned. If the default is a proc, the proc is executed and the resulting
|
89
|
+
# values is returned.
|
90
|
+
def column_default(column_name)
|
91
|
+
return nil unless defined? @column_defaults
|
92
|
+
|
93
|
+
val = @column_defaults[column_name.to_s.upcase]
|
94
|
+
if val.is_a?(Proc)
|
95
|
+
val.call
|
96
|
+
else
|
97
|
+
val
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
# Returns a DataFactory::Column object that defines the properties
|
103
|
+
# of the column
|
104
|
+
def column_detail(column_name)
|
105
|
+
load_meta_data unless meta_data_loaded
|
106
|
+
|
107
|
+
unless @column_details.has_key?(column_name.to_s.upcase)
|
108
|
+
raise DataFactory::ColumnNotInTable
|
109
|
+
end
|
110
|
+
|
111
|
+
@column_details[column_name.to_s.upcase]
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
# Used to load the table meta-data from the database
|
116
|
+
# TODO - abstract into a database layer as this code is Oracle specific
|
117
|
+
def load_meta_data # :nodoc:
|
118
|
+
raise DataFactory::TableNotSet unless @table_name
|
119
|
+
raise DataFactory::DatabaseInterfaceNotSet unless class_variable_defined?("@@db_interface")
|
120
|
+
|
121
|
+
if meta_data_loaded
|
122
|
+
return
|
123
|
+
end
|
124
|
+
|
125
|
+
@column_details = Hash.new
|
126
|
+
|
127
|
+
unless defined? @column_defaults
|
128
|
+
@column_defaults = Hash.new
|
129
|
+
end
|
130
|
+
|
131
|
+
table_details_sql = "select column_name,
|
132
|
+
data_type,
|
133
|
+
data_length,
|
134
|
+
data_precision,
|
135
|
+
data_scale,
|
136
|
+
column_id,
|
137
|
+
nullable
|
138
|
+
from user_tab_columns
|
139
|
+
where table_name = ?
|
140
|
+
order by column_id asc"
|
141
|
+
|
142
|
+
database_interface.execute_sql(table_details_sql, @table_name).each_array do |r|
|
143
|
+
c = Column.new
|
144
|
+
c.column_name = r[0].upcase
|
145
|
+
c.data_type = r[1].upcase
|
146
|
+
c.data_length = r[2].to_i
|
147
|
+
c.data_precision = r[3].to_i
|
148
|
+
c.data_scale = r[4].to_i
|
149
|
+
c.position = r[5].to_i
|
150
|
+
c.nullable = r[6] == 'N' ? false : true
|
151
|
+
@column_details[r[0].upcase] = c
|
152
|
+
end
|
153
|
+
@meta_data_loaded = true
|
154
|
+
# This is needed here as some column defaults will have been set
|
155
|
+
# before the meta_data was loaded and hence will not have been checked
|
156
|
+
validate_column_defaults
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
def validate_column_defaults
|
162
|
+
@column_defaults.keys.each do |k|
|
163
|
+
validate_column_default(k, @column_defaults[k])
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
def validate_column_default(column_name, column_value)
|
169
|
+
unless @column_details.has_key? column_name
|
170
|
+
raise DataFactory::ColumnNotInTable, column_name
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module DataFactory
|
2
|
+
module BaseFactory
|
3
|
+
|
4
|
+
# This module implements the class methods of the DataFactory::Base class.
|
5
|
+
# These factory methods should be used to generate database records.
|
6
|
+
|
7
|
+
# Builds a new instance of a DataFactory class, inserts it into the database
|
8
|
+
# and commits the transaction. The params parameter is expected to be a hash, mapping columns
|
9
|
+
# in the underlying table to values. These values will be merged with any
|
10
|
+
# defaults set for the columns, and will override the defaults if passed. For example:
|
11
|
+
#
|
12
|
+
# obj = Employee.create!(:last_name => 'Smith')
|
13
|
+
def create!(params)
|
14
|
+
df = create(params)
|
15
|
+
df.commit
|
16
|
+
df
|
17
|
+
end
|
18
|
+
|
19
|
+
# Same as the create! method, only the transaction is not committed on the database.
|
20
|
+
# For example:
|
21
|
+
#
|
22
|
+
# obj = Employee.create(:last_name => 'Smith')
|
23
|
+
def create(params)
|
24
|
+
df = build(params)
|
25
|
+
df.generate_insert
|
26
|
+
df.run_insert
|
27
|
+
df
|
28
|
+
end
|
29
|
+
|
30
|
+
# Builds and returns a new instance of a DataFactory class, but does not insert it
|
31
|
+
# into the database. For example:
|
32
|
+
#
|
33
|
+
# obj = Employee.build(:last_name => 'Smith')
|
34
|
+
def build(params)
|
35
|
+
df = self.new
|
36
|
+
df.generate_column_data(params)
|
37
|
+
df
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module DataFactory
|
2
|
+
class Column
|
3
|
+
|
4
|
+
attr_accessor :column_name, :data_type, :data_length, :data_scale, :data_precision, :position, :nullable
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
end
|
8
|
+
|
9
|
+
def nullable?
|
10
|
+
nullable
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"#{@column_name} #{@data_type} #{data_length} #{data_scale} #{data_precision} #{position.to_s}"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module DataFactory
|
2
|
+
module Random
|
3
|
+
|
4
|
+
CHARS = ['A'..'Z', 'a'..'z', '0'..'9'].map{|r|r.to_a}.flatten
|
5
|
+
|
6
|
+
# Generates a random string of letters and numbers of length
|
7
|
+
def random_string_of_length(length)
|
8
|
+
str = ''
|
9
|
+
1.upto(length) do
|
10
|
+
str << CHARS[rand(CHARS.size)]
|
11
|
+
end
|
12
|
+
str
|
13
|
+
end
|
14
|
+
|
15
|
+
# Generates a random string of random length, which has a maximum
|
16
|
+
# length of max_length
|
17
|
+
def random_string_upto_length(max_length)
|
18
|
+
length = random_integer(max_length)
|
19
|
+
random_string_of_length(length)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Generates a random integer that is at most max_size
|
23
|
+
def random_integer(max_size)
|
24
|
+
1 + rand(max_size)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class BaseAPITest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
include TestHelper
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@klass = Class.new
|
9
|
+
@klass.extend DataFactory::BaseDSL
|
10
|
+
@dbinterface = DBInterfaceMock.new
|
11
|
+
@klass.set_database_interface(@dbinterface)
|
12
|
+
@klass.set_table_name('foobar')
|
13
|
+
@klass.send :include, DataFactory::BaseAPI
|
14
|
+
end
|
15
|
+
|
16
|
+
def teardown
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_exception_if_table_name_not_set
|
20
|
+
klass = Class.new
|
21
|
+
klass.extend DataFactory::BaseDSL
|
22
|
+
dbinterface = DBInterfaceMock.new
|
23
|
+
klass.set_database_interface(dbinterface)
|
24
|
+
klass.send :include, DataFactory::BaseAPI
|
25
|
+
assert_raises DataFactory::TableNotSet do
|
26
|
+
instance = klass.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_table_name_can_be_retrieved
|
31
|
+
instance = @klass.new
|
32
|
+
assert_equal('FOOBAR', instance.table_name)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_column_default_is_retrieved
|
36
|
+
@klass.set_column_default('COL1', 'abcd')
|
37
|
+
instance = @klass.new
|
38
|
+
assert_equal('abcd', instance.column_default('COL1'))
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_column_default_is_nil_if_not_set
|
42
|
+
instance = @klass.new
|
43
|
+
assert_equal(nil, instance.column_default('COL1'))
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_column_default_hash_is_retrieved
|
47
|
+
@klass.set_column_default('COL1', 'abcd')
|
48
|
+
instance = @klass.new
|
49
|
+
assert(instance.column_defaults.is_a?(Hash))
|
50
|
+
assert_equal('abcd', instance.column_defaults['COL1'])
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_empty_default_hash_retrieved_if_no_defaults
|
54
|
+
instance = @klass.new
|
55
|
+
assert(instance.column_defaults.is_a?(Hash))
|
56
|
+
assert_equal(0, instance.column_defaults.keys.length)
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_column_detail_is_retrieved
|
60
|
+
instance = @klass.new
|
61
|
+
assert(instance.column_detail('COL1').is_a? DataFactory::Column)
|
62
|
+
assert_equal('COL1', instance.column_detail('COL1').column_name)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_exception_raised_if_column_not_in_table
|
66
|
+
instance = @klass.new
|
67
|
+
assert_raises DataFactory::ColumnNotInTable do
|
68
|
+
instance.column_detail('not exist')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_column_detail_hash_retrieved
|
73
|
+
instance = @klass.new
|
74
|
+
assert(instance.column_details.is_a? Hash)
|
75
|
+
assert_equal('COL1', instance.column_details['COL1'].column_name)
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_db_interface_returned
|
79
|
+
instance = @klass.new
|
80
|
+
assert_equal(@dbinterface, instance.db_interface)
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_commit_called_on_db_interface
|
84
|
+
# Mocha will raise an exception if the commit method is not invoked
|
85
|
+
instance = @klass.new
|
86
|
+
@dbinterface.expects(:commit).at_least_once
|
87
|
+
instance.commit
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_column_value_returns_nil_when_not_generated
|
91
|
+
instance = @klass.new
|
92
|
+
assert_equal(nil, instance.column_value('COL1'))
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_execption_raised_for_column_value_when_not_in_table
|
96
|
+
instance = @klass.new
|
97
|
+
assert_raises DataFactory::ColumnNotInTable do
|
98
|
+
instance.column_value('NOTTHERE')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_generate_data_generates_nil_if_column_nullable
|
103
|
+
instance = @klass.new
|
104
|
+
instance.generate_column_data
|
105
|
+
assert_nil(instance.column_value('COL1'))
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_generate_data_generates_column_data_if_column_not_nullable
|
109
|
+
instance = @klass.new
|
110
|
+
instance.generate_column_data
|
111
|
+
assert_not_nil(instance.column_value('COL3'))
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_generate_data_default_used_for_column_when_present
|
115
|
+
@klass.set_column_default :col1, 'PRESET'
|
116
|
+
instance = @klass.new
|
117
|
+
instance.generate_column_data
|
118
|
+
assert_equal('PRESET', instance.column_value('COL1'))
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_generate_data_passed_in_value_overrides_default
|
122
|
+
@klass.set_column_default :col1, 'PRESET'
|
123
|
+
instance = @klass.new
|
124
|
+
instance.generate_column_data(:col1 => 'OVERRIDE')
|
125
|
+
assert_equal('OVERRIDE', instance.column_value('COL1'))
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_generate_data_raises_exception_when_passed_in_column_not_exist
|
129
|
+
instance = @klass.new
|
130
|
+
assert_raises DataFactory::ColumnNotInTable do
|
131
|
+
instance.generate_column_data(:notexist => 'OVERRIDE')
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
# TODO - tests for generate insert
|
137
|
+
# TODO - tests for
|
138
|
+
|
139
|
+
|
140
|
+
|
141
|
+
|
142
|
+
def test_run_insert_invokes_execute_on_db_interface
|
143
|
+
instance = @klass.new
|
144
|
+
instance.generate_column_data
|
145
|
+
instance.generate_insert
|
146
|
+
@dbinterface.expects(:execute_sql).returns(@dbinterface)
|
147
|
+
instance.run_insert
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class BaseDSLTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
include TestHelper
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@klass = Class.new
|
9
|
+
@klass.extend DataFactory::BaseDSL
|
10
|
+
end
|
11
|
+
|
12
|
+
def teardown
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_table_name_can_be_set_as_a_class_instance_variable
|
16
|
+
@klass.set_table_name('foobar')
|
17
|
+
assert_equal('FOOBAR', @klass.table_name)
|
18
|
+
assert_equal('FOOBAR', @klass.instance_variable_get('@table_name'))
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_table_name_as_symbol_converted_to_string
|
22
|
+
@klass.set_table_name(:foobar)
|
23
|
+
assert_equal('FOOBAR', @klass.table_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_db_interface_is_set_as_a_class_variable
|
27
|
+
dbklass = Class.new
|
28
|
+
@klass.set_database_interface(dbklass)
|
29
|
+
# need to use send here as @@db_interface is a private variable
|
30
|
+
assert_equal(dbklass, @klass.send(:class_variable_get, '@@db_interface'))
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_set_column_default_value
|
34
|
+
@klass.set_column_default("COL1", 'value1')
|
35
|
+
assert_equal('value1', @klass.column_default('COL1'))
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_set_column_default_as_symbol_converted_to_string
|
39
|
+
@klass.set_column_default(:col1, 'value1')
|
40
|
+
assert_equal('value1', @klass.column_default('COL1'))
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_set_column_default_as_proc
|
44
|
+
@klass.set_column_default(:col1) { 'value1' }
|
45
|
+
assert_equal('value1', @klass.column_default('COL1'))
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_set_column_default_value_as_symbol_converted_to_string
|
49
|
+
@klass.set_column_default("COL1", :value1)
|
50
|
+
assert_equal('value1', @klass.column_default('COL1'))
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_get_column_default_returns_nil_when_not_set
|
54
|
+
assert_equal(nil, @klass.column_default('COL1'))
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_get_column_default_as_symbol_converted_to_string
|
58
|
+
@klass.set_column_default("COL1", 'value1')
|
59
|
+
assert_equal('value1', @klass.column_default(:col1))
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_load_meta_data_loads_columns
|
63
|
+
@klass.set_database_interface(DBInterfaceMock.new)
|
64
|
+
@klass.set_table_name(:foobar)
|
65
|
+
@klass.load_meta_data
|
66
|
+
assert(@klass.column_detail('COL1').is_a?(DataFactory::Column))
|
67
|
+
assert_equal('COL1', @klass.column_detail('COL1').column_name)
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_load_meta_data_exception_raised_when_table_not_set
|
71
|
+
assert_raises DataFactory::TableNotSet do
|
72
|
+
@klass.set_database_interface(DBInterfaceMock.new)
|
73
|
+
@klass.load_meta_data
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_load_meta_data_exception_raised_when_db_interface_not_set
|
78
|
+
@klass.set_table_name(:foobar)
|
79
|
+
assert_raises DataFactory::DatabaseInterfaceNotSet do
|
80
|
+
@klass.load_meta_data
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_load_meta_data_raises_exception_when_invalid_column_default_set
|
85
|
+
@klass.set_database_interface(DBInterfaceMock.new)
|
86
|
+
@klass.set_table_name(:foobar)
|
87
|
+
@klass.set_column_default :not_exists, :value1
|
88
|
+
assert_raises DataFactory::ColumnNotInTable do
|
89
|
+
@klass.load_meta_data
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_column_detail_loads_meta_data_if_not_loaded
|
94
|
+
@klass.set_database_interface(DBInterfaceMock.new)
|
95
|
+
@klass.set_table_name(:foobar)
|
96
|
+
assert(@klass.column_detail('COL1').is_a?(DataFactory::Column))
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_column_detail_raises_exception_if_column_does_not_exist
|
100
|
+
@klass.set_database_interface(DBInterfaceMock.new)
|
101
|
+
@klass.set_table_name(:foobar)
|
102
|
+
assert_raises DataFactory::ColumnNotInTable do
|
103
|
+
assert(@klass.column_detail('COLNOTTHERE').is_a?(DataFactory::Column))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
end
|
109
|
+
|
data/test/helper.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
$:.unshift File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
|
2
|
+
$:.unshift File.expand_path(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
#require 'rubygems'
|
5
|
+
#require 'simple_oracle_jdbc'
|
6
|
+
require 'data_factory'
|
7
|
+
require 'test/unit'
|
8
|
+
require 'mocha/setup'
|
9
|
+
|
10
|
+
module TestHelper
|
11
|
+
|
12
|
+
DB_USER = 'sodonnel'
|
13
|
+
DB_PASSWORD = 'sodonnel'
|
14
|
+
DB_SERVICE = 'local11gr2.world'
|
15
|
+
DB_HOST = 'localhost'
|
16
|
+
DB_PORT = '1521'
|
17
|
+
|
18
|
+
# @@interface ||= SimpleOracleJDBC::Interface.create(DB_USER,
|
19
|
+
# DB_PASSWORD,
|
20
|
+
# DB_SERVICE,
|
21
|
+
# DB_HOST,
|
22
|
+
# DB_PORT)
|
23
|
+
|
24
|
+
class DBInterfaceMock
|
25
|
+
def execute_sql(sql, binds)
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
# Mock out returing a list of columns for a table.
|
30
|
+
def each_array(&blk)
|
31
|
+
data = [
|
32
|
+
# cname, type len precision, scale, position, nullable
|
33
|
+
['col1', 'varchar2', 20, nil, nil, 1, 'Y'],
|
34
|
+
['col2', 'number', 20, 9, 2, 2, 'Y'],
|
35
|
+
['col3', 'DATE', 20, nil, nil, 3, 'N'],
|
36
|
+
['col4', 'varchar2', 20, nil, nil, 4, 'N'],
|
37
|
+
['col5', 'integer', 20, 38, 0, 5, 'N'],
|
38
|
+
['col6', 'number', 20, 20, 5, 6, 'N']
|
39
|
+
]
|
40
|
+
data.each do |d|
|
41
|
+
yield d
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def close
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
data/test/sanity.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
$:.unshift File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
|
2
|
+
$:.unshift File.expand_path(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'simple_oracle_jdbc'
|
6
|
+
require 'data_factory'
|
7
|
+
|
8
|
+
#interface = DataFactory::DBInterface::Oracle.create('sodonnel', 'sodonnel', 'local11gr2')
|
9
|
+
interface = SimpleOracleJDBC::Interface.create('sodonnel', 'sodonnel', 'local11gr2.world', 'localhost', '1521')
|
10
|
+
|
11
|
+
DataFactory::Base.set_database_interface(interface)
|
12
|
+
|
13
|
+
class Foo < DataFactory::Base
|
14
|
+
set_table_name "employees"
|
15
|
+
set_column_default "EMP_NAME", 'john'
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
f = Foo.create!("emp_id" => 1001)
|
20
|
+
f.column_values.keys.each do |k|
|
21
|
+
puts f.column_values[k]
|
22
|
+
end
|
data/test/test_runner.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
$:.unshift File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
|
2
|
+
$:.unshift File.expand_path(File.join(File.dirname(__FILE__), "."))
|
3
|
+
|
4
|
+
current_dir = File.expand_path(File.dirname(__FILE__))
|
5
|
+
|
6
|
+
|
7
|
+
# Find all files that end in _test.rb and require them ...
|
8
|
+
files = Dir.entries(current_dir).grep(/^[^#].+_test\.rb$/).sort
|
9
|
+
files.each do |f|
|
10
|
+
require File.join(current_dir, f)
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: data_factory
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Stephen O'Donnell
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2013-02-12 00:00:00 +00:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Generates data to insert into database tables, allowing columns to be defaulted or overriden. Intended to be used when testing wide tables where many not null columns may need to be populated but are not part of the test
|
22
|
+
email: stephen@betteratoracle.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README.md
|
29
|
+
files:
|
30
|
+
- test/base_api_test.rb
|
31
|
+
- test/base_dsl_test.rb
|
32
|
+
- test/helper.rb
|
33
|
+
- test/sanity.rb
|
34
|
+
- test/test_runner.rb
|
35
|
+
- lib/data_factory/base.rb
|
36
|
+
- lib/data_factory/base_api.rb
|
37
|
+
- lib/data_factory/base_dsl.rb
|
38
|
+
- lib/data_factory/base_factory.rb
|
39
|
+
- lib/data_factory/column.rb
|
40
|
+
- lib/data_factory/exceptions.rb
|
41
|
+
- lib/data_factory/random.rb
|
42
|
+
- lib/data_factory.rb
|
43
|
+
- DataFactory-0.1.gem
|
44
|
+
- Rakefile.rb
|
45
|
+
- README.md
|
46
|
+
has_rdoc: true
|
47
|
+
homepage: http://betteratoracle.com
|
48
|
+
licenses: []
|
49
|
+
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.3.6
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: A gem to generate template insert statements for use when unit testing database code
|
76
|
+
test_files: []
|
77
|
+
|