rapids 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/README +0 -0
- data/Rakefile +8 -0
- data/lib/rapids.rb +1 -0
- data/lib/rapids/batch.rb +64 -0
- data/lib/rapids/batch/columns_helper.rb +81 -0
- data/lib/rapids/batch/create_table.rb +37 -0
- data/lib/rapids/batch/create_trigger.rb +177 -0
- data/lib/rapids/batch/find_or_create.rb +13 -0
- data/lib/rapids/batch/insert_into.rb +59 -0
- data/lib/rapids/batch/model_extensions.rb +46 -0
- data/lib/rapids/version.rb +3 -0
- data/rapids.gemspec +31 -0
- data/spec/batch/columns_helper_spec.rb +37 -0
- data/spec/batch/create_table_spec.rb +44 -0
- data/spec/batch/create_trigger_spec.rb +36 -0
- data/spec/batch/insert_into_spec.rb +42 -0
- data/spec/batch/spec_helper.rb +98 -0
- data/spec/rapids_batch_spec.rb +8 -0
- data/spec/spec_helper.rb +3 -0
- metadata +144 -0
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/README
ADDED
File without changes
|
data/Rakefile
ADDED
data/lib/rapids.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__),'rapids','batch')
|
data/lib/rapids/batch.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'rapids/batch/create_table'
|
2
|
+
require 'rapids/batch/insert_into'
|
3
|
+
require 'rapids/batch/create_trigger'
|
4
|
+
require 'rapids/batch/find_or_create'
|
5
|
+
|
6
|
+
module Rapids
|
7
|
+
module Batch
|
8
|
+
class DefineBatch
|
9
|
+
attr_reader :find_or_creates
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@find_or_creates = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def find_or_create(name,find_columns,*fill_columns_plus_other_params)
|
16
|
+
@find_or_creates << FindOrCreate.new(name,find_columns,fill_columns_plus_other_params.first)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def batch_create(collection)
|
21
|
+
drop_batch_table_if_exists
|
22
|
+
|
23
|
+
create_batch_table
|
24
|
+
|
25
|
+
create_batch_trigger
|
26
|
+
|
27
|
+
batch_insert(collection)
|
28
|
+
end
|
29
|
+
|
30
|
+
def batch(&block)
|
31
|
+
@batch = DefineBatch.new
|
32
|
+
@batch.instance_exec(&block) if block_given?
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def drop_batch_table_if_exists
|
37
|
+
drop_table_sql = "DROP TABLE IF EXISTS `#{batch_table_name}`"
|
38
|
+
self.connection.execute(drop_table_sql)
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_batch_table
|
42
|
+
create_table = CreateTable.new(self,@batch)
|
43
|
+
|
44
|
+
connection.execute(create_table.to_sql)
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_batch_trigger
|
48
|
+
create_trigger = CreateTrigger.new(self,@batch)
|
49
|
+
|
50
|
+
connection.execute(create_trigger.to_sql)
|
51
|
+
end
|
52
|
+
|
53
|
+
def batch_insert(values)
|
54
|
+
insert_into = InsertInto.new(self,@batch,values)
|
55
|
+
|
56
|
+
connection.execute(insert_into.to_sql)
|
57
|
+
end
|
58
|
+
|
59
|
+
#TODO refactor this it duplicates what's done in model_extensions.rb
|
60
|
+
def batch_table_name
|
61
|
+
"$#{table_name}_batch"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Rapids
|
2
|
+
module Batch
|
3
|
+
class ColumnsHelper
|
4
|
+
include Enumerable
|
5
|
+
include ModelExtensions
|
6
|
+
|
7
|
+
def initialize(model,find_or_creates)
|
8
|
+
@hash = generate_columns_hash(model,find_or_creates)
|
9
|
+
end
|
10
|
+
|
11
|
+
def each(&block)
|
12
|
+
internal_each(@hash,[],&block)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def internal_each(columns_hash,hash_path,&block)
|
17
|
+
columns_hash.sort{|a,b| a[0].to_s <=> b[0].to_s}.each do |key,column_or_hash|
|
18
|
+
if column_or_hash.is_a?(Hash)
|
19
|
+
internal_each(column_or_hash,hash_path + [key],&block)
|
20
|
+
else
|
21
|
+
yield(column_or_hash,hash_path)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def generate_columns_hash(model,criteria_hash,skip_columns = [])
|
27
|
+
hash = {}
|
28
|
+
skip_columns_next_time = {}
|
29
|
+
|
30
|
+
batch_association_primary_keys = if criteria_hash.is_a?(Array)
|
31
|
+
criteria_hash.map do |find_or_create|
|
32
|
+
name = find_or_create.name
|
33
|
+
if model.reflections[name]
|
34
|
+
if model.reflections[name].collection?
|
35
|
+
skip_columns_next_time[name] = [model.reflections[name].primary_key_name]
|
36
|
+
nil
|
37
|
+
else
|
38
|
+
model.reflections[name].primary_key_name
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end.compact
|
42
|
+
else
|
43
|
+
[]
|
44
|
+
end
|
45
|
+
|
46
|
+
columns(model).reject{|c|batch_association_primary_keys.include?(c.name) || skip_columns.include?(c.name)}.each do |column|
|
47
|
+
hash[column.name.to_sym] = column
|
48
|
+
end
|
49
|
+
|
50
|
+
if criteria_hash.is_a?(Array)
|
51
|
+
criteria_hash.each do |find_or_create|
|
52
|
+
name,criteria_array = find_or_create.name,find_or_create.find_columns
|
53
|
+
|
54
|
+
new_model = if criteria_array.is_a?(Array)
|
55
|
+
if name.is_a?(String) && Kernel.const_get(name)
|
56
|
+
Kernel.const_get(name)
|
57
|
+
elsif model.reflections[name]
|
58
|
+
model.reflections[name].klass
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if new_model
|
63
|
+
hash[name] = criteria_array.inject({}) do |memo,criteria|
|
64
|
+
if criteria.is_a?(Hash)
|
65
|
+
new_find_or_creates = criteria.map do |n,c|
|
66
|
+
FindOrCreate.new(n,c,[])
|
67
|
+
end
|
68
|
+
memo.merge(generate_columns_hash(new_model,new_find_or_creates,skip_columns_next_time[name] || []))
|
69
|
+
else
|
70
|
+
memo.merge(generate_columns_hash(new_model,[],skip_columns_next_time[name] || []))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
hash
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rapids/batch/model_extensions'
|
2
|
+
require 'rapids/batch/columns_helper'
|
3
|
+
|
4
|
+
module Rapids
|
5
|
+
module Batch
|
6
|
+
class CreateTable
|
7
|
+
include ModelExtensions
|
8
|
+
|
9
|
+
def initialize(model,batch_definition)
|
10
|
+
@model = model
|
11
|
+
@batch = batch_definition
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_sql
|
15
|
+
columns_helper = ColumnsHelper.new(@model,@batch.find_or_creates)
|
16
|
+
columns_sql = columns_helper.map do |column,path|
|
17
|
+
association = nil
|
18
|
+
model = @model
|
19
|
+
path.each do |association_name|
|
20
|
+
if model.reflections[association_name]
|
21
|
+
association = model.reflections[association_name]
|
22
|
+
model = model.reflections[association_name].klass
|
23
|
+
end
|
24
|
+
end
|
25
|
+
column_type = if association && association.collection? && column.number?
|
26
|
+
"varchar(255)"
|
27
|
+
else
|
28
|
+
column.sql_type
|
29
|
+
end
|
30
|
+
"#{sql_column_name(column,path)} #{column_type}"
|
31
|
+
end.join(",")
|
32
|
+
|
33
|
+
"CREATE TABLE `#{batch_table_name}` (#{columns_sql}) ENGINE=BLACKHOLE DEFAULT CHARSET=utf8"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'rapids/batch/model_extensions'
|
2
|
+
require 'rapids/batch/columns_helper'
|
3
|
+
|
4
|
+
module Rapids
|
5
|
+
module Batch
|
6
|
+
class CreateTrigger
|
7
|
+
include ModelExtensions
|
8
|
+
|
9
|
+
def initialize(model,batch_definition)
|
10
|
+
@model = model
|
11
|
+
@batch = batch_definition
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_sql
|
15
|
+
declares = @batch.find_or_creates.map do |find_or_create|
|
16
|
+
"declare #{variable_name(find_or_create.name,[])} integer;"
|
17
|
+
end.join("\n")
|
18
|
+
|
19
|
+
columns_helper = ColumnsHelper.new(@model,@batch.find_or_creates)
|
20
|
+
main_columns = columns_helper.find_all{|column,path| path == []}
|
21
|
+
|
22
|
+
insert_header = (main_columns.map(&:first) + criteria_columns(@model,association_find_or_creates.map(&:name))).map{|a|sql_column_name(a,[])}
|
23
|
+
insert_values = main_columns.map{|a|"new.#{sql_column_name(a.first,[])}"} + association_find_or_creates.map(&:name).map{|name|variable_name(name,[])}
|
24
|
+
|
25
|
+
<<-TRIGGER_SQL
|
26
|
+
create trigger `#{batch_table_name}_trigger` after insert on `#{batch_table_name}` for each row
|
27
|
+
begin
|
28
|
+
#{declares}
|
29
|
+
|
30
|
+
#{find_or_create_sql(@model,@batch.find_or_creates)}
|
31
|
+
|
32
|
+
insert into `#{@model.table_name}` (#{insert_header.join(",")})
|
33
|
+
values (#{insert_values.join(",")});
|
34
|
+
end
|
35
|
+
TRIGGER_SQL
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def association_find_or_creates(model = @model,find_or_creates = @batch.find_or_creates)
|
40
|
+
find_or_creates.reject{|foc|model.reflections[foc.name].nil?}
|
41
|
+
end
|
42
|
+
|
43
|
+
def find_or_create_sql(model,find_or_creates, recursion_path = [])
|
44
|
+
columns_helper = ColumnsHelper.new(model,find_or_creates)
|
45
|
+
|
46
|
+
find_or_creates.map do |find_or_create|
|
47
|
+
name,criteria = find_or_create.name,find_or_create.find_columns
|
48
|
+
if model.reflections[name]
|
49
|
+
sub_model = model.reflections[name].klass
|
50
|
+
association_table_name = sub_model.table_name
|
51
|
+
sub_model_columns = columns_helper.find_all{|column,path| path == [name]}
|
52
|
+
|
53
|
+
where_sql = foc_where_sql(sub_model,criteria,[name])
|
54
|
+
|
55
|
+
"select id from `#{association_table_name}` where #{where_sql} into #{variable_name(name,recursion_path)};
|
56
|
+
if #{variable_name(name,recursion_path)} is null then
|
57
|
+
insert into #{association_table_name} (#{sub_model_columns.map{|a|sql_column_name(a.first,[])}.join(",")})
|
58
|
+
values (#{sub_model_columns.map{|a|"new.#{sql_column_name(a.first,a.last)}"}.join(",")});
|
59
|
+
|
60
|
+
select last_insert_id() into #{variable_name(name,recursion_path)};
|
61
|
+
|
62
|
+
#{sub_inserts(sub_model,criteria,[name],variable_name(name,recursion_path))}
|
63
|
+
end if;
|
64
|
+
"
|
65
|
+
elsif name.is_a?(String) && Kernel.const_get(name)
|
66
|
+
sub_model = Kernel.const_get(name)
|
67
|
+
association_table_name = sub_model.table_name
|
68
|
+
sub_model_columns = columns_helper.find_all{|column,path| path == [name]}
|
69
|
+
|
70
|
+
where_sql = foc_where_sql(sub_model,criteria,[name])
|
71
|
+
|
72
|
+
"select id from `#{association_table_name}` where #{where_sql} into #{variable_name(name,recursion_path)};
|
73
|
+
if #{variable_name(name,recursion_path)} is null then
|
74
|
+
insert into #{association_table_name} (#{sub_model_columns.map{|a|sql_column_name(a.first,[])}.join(",")})
|
75
|
+
values (#{sub_model_columns.map{|a|"new.#{sql_column_name(a.first,a.last)}"}.join(",")});
|
76
|
+
|
77
|
+
#{sub_inserts(sub_model,criteria,[name],variable_name(name,recursion_path))}
|
78
|
+
end if;
|
79
|
+
"
|
80
|
+
end
|
81
|
+
end.join("\n")
|
82
|
+
end
|
83
|
+
|
84
|
+
def variable_name(name,recursion_path)
|
85
|
+
"find_or_create$#{(recursion_path + [name]).join("$")}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def foc_where_sql(model,criteria_array,path)
|
89
|
+
and_comparisons = criteria_array.map do |column_or_association_name_or_hash|
|
90
|
+
if column_or_association_name_or_hash.is_a?(Hash)
|
91
|
+
sub_comparisons = column_or_association_name_or_hash.map do |association_name,sub_criteria|
|
92
|
+
association = model.reflections[association_name]
|
93
|
+
if association.collection?
|
94
|
+
sub_criteria.map do |column_name|
|
95
|
+
column = lookup_column_by_name(association.klass,column_name)
|
96
|
+
"new.#{sql_column_name(column,path+[association_name])} = (select GROUP_CONCAT(#{sql_column_name(column,[])} ORDER BY #{sql_column_name(column,[])})
|
97
|
+
from #{association.quoted_table_name}
|
98
|
+
where `#{model.table_name}`.id = `#{association.primary_key_name}`)
|
99
|
+
"
|
100
|
+
end.join(" and ")
|
101
|
+
else
|
102
|
+
"true"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
"("+sub_comparisons.join(" and ")+")"
|
107
|
+
else
|
108
|
+
if column_or_association_name_or_hash.is_a?(Array)
|
109
|
+
destination_column = lookup_column_by_name(model,column_or_association_name_or_hash.first)
|
110
|
+
source_column = lookup_column_by_name(model,column_or_association_name_or_hash.last)
|
111
|
+
else
|
112
|
+
destination_column = lookup_column_by_name(model,column_or_association_name_or_hash)
|
113
|
+
source_column = lookup_column_by_name(model,column_or_association_name_or_hash)
|
114
|
+
end
|
115
|
+
|
116
|
+
"#{sql_column_name(destination_column,[])} <=> new.#{sql_column_name(destination_column,path)}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
and_comparisons.join(" and ")
|
121
|
+
end
|
122
|
+
|
123
|
+
def sub_inserts(model,criteria_array,path,key_variable)
|
124
|
+
criteria_array.map do |column_or_association_name_or_hash|
|
125
|
+
if column_or_association_name_or_hash.is_a?(Hash)
|
126
|
+
column_or_association_name_or_hash.map do |association_name,sub_criteria|
|
127
|
+
association = model.reflections[association_name]
|
128
|
+
column = lookup_column_by_name(association.klass,sub_criteria.first)
|
129
|
+
"begin
|
130
|
+
DECLARE cur_position INT DEFAULT 1;
|
131
|
+
DECLARE remainder VARCHAR(255);
|
132
|
+
DECLARE cur_string VARCHAR(255);
|
133
|
+
|
134
|
+
SET remainder = new.#{sql_column_name(column,path+[association_name])};
|
135
|
+
|
136
|
+
WHILE CHAR_LENGTH(remainder) > 0 AND cur_position > 0 DO
|
137
|
+
SET cur_position = INSTR(remainder, ',');
|
138
|
+
IF cur_position = 0 THEN
|
139
|
+
SET cur_string = remainder;
|
140
|
+
ELSE
|
141
|
+
SET cur_string = LEFT(remainder, cur_position - 1);
|
142
|
+
END IF;
|
143
|
+
INSERT INTO #{association.quoted_table_name} (#{["`#{association.primary_key_name}`",sql_column_name(column,[])].join(",")}) VALUES (#{[key_variable,'cur_string'].join(",")});
|
144
|
+
SET remainder = SUBSTRING(remainder, cur_position + 1);
|
145
|
+
END WHILE;
|
146
|
+
end;"
|
147
|
+
end.join("\n")
|
148
|
+
end
|
149
|
+
end.join("\n")
|
150
|
+
end
|
151
|
+
|
152
|
+
def lookup_column_by_name(model,column_name)
|
153
|
+
column = model.columns.detect{|c|c.name == column_name.to_s}
|
154
|
+
|
155
|
+
unless column
|
156
|
+
# doesn't match a column name look for an assocation instead then
|
157
|
+
association = model.reflections[column_name]
|
158
|
+
|
159
|
+
column = if association.nil? || association.collection?
|
160
|
+
nil #TODO implement
|
161
|
+
else
|
162
|
+
model.columns.detect{|c|c.name == association.primary_key_name}
|
163
|
+
end
|
164
|
+
end
|
165
|
+
column
|
166
|
+
end
|
167
|
+
|
168
|
+
def criteria_columns(model,criteria_array)
|
169
|
+
criteria_array.map do |column_or_association_name_or_hash|
|
170
|
+
unless column_or_association_name_or_hash.is_a?(Hash)
|
171
|
+
lookup_column_by_name(model,column_or_association_name_or_hash)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Rapids
|
2
|
+
module Batch
|
3
|
+
class FindOrCreate
|
4
|
+
attr_reader :name, :find_columns, :fill_columns
|
5
|
+
|
6
|
+
def initialize(name,find_columns,fill_columns)
|
7
|
+
@name = name
|
8
|
+
@find_columns = find_columns
|
9
|
+
@fill_columns = fill_columns
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rapids/batch/model_extensions'
|
2
|
+
require 'rapids/batch/columns_helper'
|
3
|
+
|
4
|
+
module Rapids
|
5
|
+
module Batch
|
6
|
+
class InsertInto
|
7
|
+
include ModelExtensions
|
8
|
+
|
9
|
+
def initialize(model,batch_definition,values)
|
10
|
+
@model = model
|
11
|
+
@batch = batch_definition
|
12
|
+
@values = values
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_sql
|
16
|
+
columns_helper = ColumnsHelper.new(@model,@batch.find_or_creates)
|
17
|
+
insert_header_sql = columns_helper.map{|column,path|sql_column_name(column,path)}.join(",")
|
18
|
+
|
19
|
+
values_sql = @values.map do |row|
|
20
|
+
row_sql = columns_helper.map do |column,path|
|
21
|
+
source_column_name = column.name
|
22
|
+
destination_column = column
|
23
|
+
|
24
|
+
@batch.find_or_creates.each do |find_or_create|
|
25
|
+
if find_or_create.name == path.first && find_or_create.find_columns.any?{|fc|fc.is_a?(Array) && fc.first.to_s == column.name}
|
26
|
+
source_column_name = find_or_create.find_columns.detect{|fc|fc.is_a?(Array) && fc.first.to_s == column.name}.last.to_s
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
specific_object = path.inject(row) do |memo,association|
|
31
|
+
if memo.respond_to?(association)
|
32
|
+
memo.send(association)
|
33
|
+
elsif association.is_a?(String)
|
34
|
+
memo
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
if specific_object.respond_to?(:each)
|
39
|
+
many_attributes = specific_object.map do |s|
|
40
|
+
s.instance_variable_get(:@attributes)[source_column_name]
|
41
|
+
end.compact
|
42
|
+
if many_attributes.empty?
|
43
|
+
default_on_nil(nil,destination_column)
|
44
|
+
else
|
45
|
+
default_on_nil(many_attributes.sort.join(","),destination_column)
|
46
|
+
end
|
47
|
+
else
|
48
|
+
val = specific_object.instance_variable_get(:@attributes)[source_column_name] #speeds things up a bit since it's not typed first before being quoted
|
49
|
+
default_on_nil(val,destination_column)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
"(#{row_sql.join(",")})"
|
53
|
+
end.join(",")
|
54
|
+
|
55
|
+
"INSERT INTO `#{batch_table_name}` (#{insert_header_sql}) VALUES #{values_sql}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Rapids
|
2
|
+
module Batch
|
3
|
+
module ModelExtensions
|
4
|
+
def columns(model = @model)
|
5
|
+
model.columns.reject{|c|c.primary}
|
6
|
+
end
|
7
|
+
|
8
|
+
def batch_table_name
|
9
|
+
"$#{@model.table_name}_batch"
|
10
|
+
end
|
11
|
+
|
12
|
+
def sql_column_name(column,hash_path)
|
13
|
+
prefix = hash_path.empty? ? "" : "foc$"
|
14
|
+
association_list = (hash_path + [column.name]).map(&:to_s).join("$")
|
15
|
+
"`#{prefix+association_list}`"
|
16
|
+
end
|
17
|
+
|
18
|
+
def default_on_nil(value,column)
|
19
|
+
if value.nil?
|
20
|
+
case
|
21
|
+
when %w{created_at updated_at}.include?(column.name)
|
22
|
+
"UTC_TIMESTAMP()"
|
23
|
+
else
|
24
|
+
quote_bound_value(value)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
quote_bound_value(value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
#Stolen from ActiveRecord::Base
|
32
|
+
def quote_bound_value(value)
|
33
|
+
c = ActiveRecord::Base.connection
|
34
|
+
if value.respond_to?(:map) && !value.acts_like?(:string)
|
35
|
+
if value.respond_to?(:empty?) && value.empty?
|
36
|
+
c.quote(nil)
|
37
|
+
else
|
38
|
+
value.map { |v| c.quote(v) }.join(',')
|
39
|
+
end
|
40
|
+
else
|
41
|
+
c.quote(value)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/rapids.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rapids/version"
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
rapids_summary = "A ruby library to provide rapid insertion of rows into a database"
|
9
|
+
|
10
|
+
s.name = "rapids"
|
11
|
+
s.version = Rapids::VERSION
|
12
|
+
s.platform = Gem::Platform::RUBY
|
13
|
+
s.authors = ['James Smith']
|
14
|
+
s.email = ["sunblaze@gmail.com"]
|
15
|
+
s.homepage = 'http://github.com/sunblaze/rapids'
|
16
|
+
s.summary = rapids_summary
|
17
|
+
s.description = rapids_summary #TODO write a better long form description
|
18
|
+
s.license = 'MIT'
|
19
|
+
s.required_ruby_version = '>= 1.8.7'
|
20
|
+
|
21
|
+
s.rubyforge_project = "rapids"
|
22
|
+
|
23
|
+
s.files = `git ls-files`.split("\n")
|
24
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
25
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
26
|
+
s.require_paths = ["lib"]
|
27
|
+
|
28
|
+
s.add_development_dependency "rspec", "2.5.0"
|
29
|
+
s.add_development_dependency "activerecord", "~> 3.0.6"
|
30
|
+
s.add_development_dependency "mysql", "~> 2.8.1"
|
31
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'batch/spec_helper'
|
2
|
+
|
3
|
+
module Rapids::Batch
|
4
|
+
describe ColumnsHelper do
|
5
|
+
it "should intialize with an empty find or create list" do
|
6
|
+
ColumnsHelper.new(Post,[])
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should initialize with the association find or create" do
|
10
|
+
find_or_creates = [FindOrCreate.new(:author,[:name],[])]
|
11
|
+
columns_helper = ColumnsHelper.new(Post,find_or_creates)
|
12
|
+
|
13
|
+
columns_helper.map{|a|a.first.name}.include?("name").should be_true
|
14
|
+
columns_helper.map{|a|a.first.name}.include?("author_id").should be_false, "expected author_id not to be present in this list:\n#{columns_helper.to_a.inspect}"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should initialize with the manual find or create format" do
|
18
|
+
find_or_creates = [FindOrCreate.new("Category",[:category],[])]
|
19
|
+
columns_helper = ColumnsHelper.new(Post,find_or_creates)
|
20
|
+
|
21
|
+
columns_helper.any? do |pair|
|
22
|
+
column,path = *pair
|
23
|
+
column.name == "category" && path == ["Category"]
|
24
|
+
end.should be_true, "expected to find a column named 'category' and the path to be [\"Category\"] in the following:\n#{columns_helper.to_a.inspect}"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should initialize with the association find or create with a has_many find or create" do
|
28
|
+
find_or_creates = [FindOrCreate.new(:post,[:name,{:post_tags => [:tag_id]}],[])]
|
29
|
+
columns_helper = ColumnsHelper.new(Comment,find_or_creates)
|
30
|
+
|
31
|
+
columns_helper.any? do |pair|
|
32
|
+
column,path = *pair
|
33
|
+
column.name == "tag_id" && path == [:post,:post_tags]
|
34
|
+
end.should be_true, "expected to find a column named 'tag_id' and the path to be [:post,:post_tags] in the following:\n#{columns_helper.to_a.inspect}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'batch/spec_helper'
|
2
|
+
|
3
|
+
module Rapids::Batch
|
4
|
+
describe CreateTable do
|
5
|
+
it "should generate sql for a simple batch definition" do
|
6
|
+
batch = Rapids::Batch::DefineBatch.new
|
7
|
+
|
8
|
+
create_table = CreateTable.new(Category,batch)
|
9
|
+
create_table.to_sql.should == "CREATE TABLE `$categories_batch` (`category` varchar(255)) ENGINE=BLACKHOLE DEFAULT CHARSET=utf8"
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should generate sql for a basic definition" do
|
13
|
+
batch = Rapids::Batch::DefineBatch.new
|
14
|
+
batch.find_or_create(:author,[:name])
|
15
|
+
|
16
|
+
create_table = CreateTable.new(Post,batch)
|
17
|
+
create_table.to_sql.should == "CREATE TABLE `$posts_batch` (`foc$author$name` varchar(255),`category` varchar(255),`name` varchar(255)) ENGINE=BLACKHOLE DEFAULT CHARSET=utf8"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should generate sql for a manual definition" do
|
21
|
+
batch = Rapids::Batch::DefineBatch.new
|
22
|
+
batch.find_or_create("Category",[:category])
|
23
|
+
|
24
|
+
create_table = CreateTable.new(Post,batch)
|
25
|
+
create_table.to_sql.should == "CREATE TABLE `$posts_batch` (`foc$Category$category` varchar(255),`author_id` int(11),`category` varchar(255),`name` varchar(255)) ENGINE=BLACKHOLE DEFAULT CHARSET=utf8"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should generate sql for a manual definition, with explicit source column" do
|
29
|
+
batch = Rapids::Batch::DefineBatch.new
|
30
|
+
batch.find_or_create("AltCategory",[[:name,:category]])
|
31
|
+
|
32
|
+
create_table = CreateTable.new(Post,batch)
|
33
|
+
create_table.to_sql.should == "CREATE TABLE `$posts_batch` (`foc$AltCategory$name` varchar(255),`author_id` int(11),`category` varchar(255),`name` varchar(255)) ENGINE=BLACKHOLE DEFAULT CHARSET=utf8"
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should generate sql for a more complicated find or create with reliances on a has_many relationship" do
|
37
|
+
batch = Rapids::Batch::DefineBatch.new
|
38
|
+
batch.find_or_create(:post,[:name,{:post_tags => [:tag_id]}])
|
39
|
+
|
40
|
+
create_table = CreateTable.new(Comment,batch)
|
41
|
+
create_table.to_sql.should == "CREATE TABLE `$comments_batch` (`body` varchar(255),`foc$post$author_id` int(11),`foc$post$category` varchar(255),`foc$post$name` varchar(255),`foc$post$post_tags$tag_id` varchar(255),`title` varchar(255)) ENGINE=BLACKHOLE DEFAULT CHARSET=utf8"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'batch/spec_helper'
|
2
|
+
|
3
|
+
module Rapids::Batch
|
4
|
+
describe CreateTrigger do
|
5
|
+
it "should generate sql for a simple batch definition" do
|
6
|
+
batch = Rapids::Batch::DefineBatch.new
|
7
|
+
|
8
|
+
create_trigger = CreateTrigger.new(Category,batch)
|
9
|
+
clean_sql(create_trigger.to_sql).should == "create trigger `$categories_batch_trigger` after insert on `$categories_batch` for each row\nbegin\ninsert into `categories` (`category`)\nvalues (new.`category`);\nend"
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should generate sql for a basic definition" do
|
13
|
+
batch = Rapids::Batch::DefineBatch.new
|
14
|
+
batch.find_or_create(:author,[:name])
|
15
|
+
|
16
|
+
create_trigger = CreateTrigger.new(Post,batch)
|
17
|
+
clean_sql(create_trigger.to_sql).should == "create trigger `$posts_batch_trigger` after insert on `$posts_batch` for each row\nbegin\ndeclare find_or_create$author integer;\nselect id from `authors` where `name` <=> new.`foc$author$name` into find_or_create$author;\nif find_or_create$author is null then\ninsert into authors (`name`)\nvalues (new.`foc$author$name`);\nselect last_insert_id() into find_or_create$author;\nend if;\ninsert into `posts` (`category`,`name`,`author_id`)\nvalues (new.`category`,new.`name`,find_or_create$author);\nend"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should generate sql for a manual definition" do
|
21
|
+
batch = Rapids::Batch::DefineBatch.new
|
22
|
+
batch.find_or_create("Category",[:category])
|
23
|
+
|
24
|
+
create_trigger = CreateTrigger.new(Post,batch)
|
25
|
+
clean_sql(create_trigger.to_sql).should == "create trigger `$posts_batch_trigger` after insert on `$posts_batch` for each row\nbegin\ndeclare find_or_create$Category integer;\nselect id from `categories` where `category` <=> new.`foc$Category$category` into find_or_create$Category;\nif find_or_create$Category is null then\ninsert into categories (`category`)\nvalues (new.`foc$Category$category`);\nend if;\ninsert into `posts` (`author_id`,`category`,`name`)\nvalues (new.`author_id`,new.`category`,new.`name`);\nend"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should generate sql for a manual definition, with explicit source column" do
|
29
|
+
batch = Rapids::Batch::DefineBatch.new
|
30
|
+
batch.find_or_create("AltCategory",[[:name,:category]])
|
31
|
+
|
32
|
+
create_trigger = CreateTrigger.new(Post,batch)
|
33
|
+
clean_sql(create_trigger.to_sql).should == "create trigger `$posts_batch_trigger` after insert on `$posts_batch` for each row\nbegin\ndeclare find_or_create$AltCategory integer;\nselect id from `alt_categories` where `name` <=> new.`foc$AltCategory$name` into find_or_create$AltCategory;\nif find_or_create$AltCategory is null then\ninsert into alt_categories (`name`)\nvalues (new.`foc$AltCategory$name`);\nend if;\ninsert into `posts` (`author_id`,`category`,`name`)\nvalues (new.`author_id`,new.`category`,new.`name`);\nend"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'batch/spec_helper'
|
2
|
+
|
3
|
+
module Rapids::Batch
|
4
|
+
describe InsertInto do
|
5
|
+
it "should generate sql for a simple batch definition" do
|
6
|
+
batch = Rapids::Batch::DefineBatch.new
|
7
|
+
collection = %w{food politics}.map do |category_name|
|
8
|
+
Category.new(:category => category_name)
|
9
|
+
end
|
10
|
+
|
11
|
+
insert_into = InsertInto.new(Category,batch,collection)
|
12
|
+
clean_sql(insert_into.to_sql).should == "INSERT INTO `$categories_batch` (`category`) VALUES ('food'),('politics')"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should generate sql for a basic definition" do
|
16
|
+
batch = Rapids::Batch::DefineBatch.new
|
17
|
+
batch.find_or_create(:author,[:name])
|
18
|
+
collection = [Post.new(:name => "Dining at 323 Butter St",:category => "food",:author => Author.new(:name => "Joe"))]
|
19
|
+
|
20
|
+
insert_into = InsertInto.new(Post,batch,collection)
|
21
|
+
clean_sql(insert_into.to_sql).should == "INSERT INTO `$posts_batch` (`foc$author$name`,`category`,`name`) VALUES ('Joe','food','Dining at 323 Butter St')"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should generate sql for a manual definition" do
|
25
|
+
batch = Rapids::Batch::DefineBatch.new
|
26
|
+
batch.find_or_create("Category",[:name])
|
27
|
+
collection = [Post.new(:name => "Dining at 323 Butter St",:category => "food")]
|
28
|
+
|
29
|
+
insert_into = InsertInto.new(Post,batch,collection)
|
30
|
+
clean_sql(insert_into.to_sql).should == "INSERT INTO `$posts_batch` (`foc$Category$category`,`author_id`,`category`,`name`) VALUES ('food',NULL,'food','Dining at 323 Butter St')"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should generate sql for a manual definition, with explicit source column" do
|
34
|
+
batch = Rapids::Batch::DefineBatch.new
|
35
|
+
batch.find_or_create("AltCategory",[[:name,:category]])
|
36
|
+
collection = [Post.new(:name => "Dining at 323 Butter St",:category => "food")]
|
37
|
+
|
38
|
+
insert_into = InsertInto.new(Post,batch,collection)
|
39
|
+
clean_sql(insert_into.to_sql).should == "INSERT INTO `$posts_batch` (`foc$AltCategory$name`,`author_id`,`category`,`name`) VALUES ('food',NULL,'food','Dining at 323 Butter St')"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
DATABASE_NAME = 'rapids_test'
|
4
|
+
ActiveRecord::Base.establish_connection(
|
5
|
+
:adapter => 'mysql',
|
6
|
+
:username => 'root',
|
7
|
+
:password => '',
|
8
|
+
:host => 'localhost')
|
9
|
+
ActiveRecord::Base.connection.create_database(DATABASE_NAME) rescue ActiveRecord::StatementInvalid
|
10
|
+
|
11
|
+
ActiveRecord::Base.establish_connection(
|
12
|
+
:adapter => 'mysql',
|
13
|
+
:database => DATABASE_NAME,
|
14
|
+
:username => 'root',
|
15
|
+
:password => '',
|
16
|
+
:host => 'localhost')
|
17
|
+
|
18
|
+
class ColumnHelperMigrations < ActiveRecord::Migration
|
19
|
+
def self.up
|
20
|
+
create_table :posts do |t|
|
21
|
+
t.column :name, :string, :null => false
|
22
|
+
t.column :category, :string, :null => false
|
23
|
+
t.column :author_id, :integer, :null => false
|
24
|
+
end
|
25
|
+
create_table :authors do |t|
|
26
|
+
t.column :name, :string, :null => false
|
27
|
+
end
|
28
|
+
create_table :categories do |t|
|
29
|
+
t.column :category, :string, :null => false
|
30
|
+
end
|
31
|
+
create_table :alt_categories do |t|
|
32
|
+
t.column :name, :string, :null => false
|
33
|
+
end
|
34
|
+
create_table :tags do |t|
|
35
|
+
t.column :name, :string, :null => false
|
36
|
+
end
|
37
|
+
create_table :post_tags do |t|
|
38
|
+
t.column :post_id, :integer, :null => false
|
39
|
+
t.column :tag_id, :integer, :null => false
|
40
|
+
end
|
41
|
+
create_table :comments do |t|
|
42
|
+
t.column :title, :string, :null => false
|
43
|
+
t.column :body, :string, :null => false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.down
|
48
|
+
drop_table :posts
|
49
|
+
drop_table :categories
|
50
|
+
drop_table :alt_categories
|
51
|
+
drop_table :authors
|
52
|
+
drop_table :tags
|
53
|
+
drop_table :post_tags
|
54
|
+
drop_table :comments
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Post < ActiveRecord::Base
|
59
|
+
belongs_to :author
|
60
|
+
belongs_to :blog
|
61
|
+
has_many :post_tags
|
62
|
+
end
|
63
|
+
|
64
|
+
class Category < ActiveRecord::Base
|
65
|
+
end
|
66
|
+
|
67
|
+
class AltCategory < ActiveRecord::Base
|
68
|
+
end
|
69
|
+
|
70
|
+
class Author < ActiveRecord::Base
|
71
|
+
end
|
72
|
+
|
73
|
+
class Tag < ActiveRecord::Base
|
74
|
+
has_many :post_tags
|
75
|
+
end
|
76
|
+
|
77
|
+
class PostTag < ActiveRecord::Base
|
78
|
+
belongs_to :tag
|
79
|
+
belongs_to :post
|
80
|
+
end
|
81
|
+
|
82
|
+
class Comment < ActiveRecord::Base
|
83
|
+
belongs_to :post
|
84
|
+
end
|
85
|
+
|
86
|
+
RSpec.configure do |config|
|
87
|
+
config.before(:suite) do
|
88
|
+
ColumnHelperMigrations.migrate(:up)
|
89
|
+
end
|
90
|
+
config.after(:suite) do
|
91
|
+
puts "\n\n" #To help make the output nicer with these migration outputs in the testing
|
92
|
+
ColumnHelperMigrations.migrate(:down)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def clean_sql(sql)
|
97
|
+
sql.gsub(/(\s)\s*/,'\1').lstrip.rstrip
|
98
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rapids
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- James Smith
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-08-22 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - "="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 27
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 5
|
33
|
+
- 0
|
34
|
+
version: 2.5.0
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: activerecord
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 11
|
46
|
+
segments:
|
47
|
+
- 3
|
48
|
+
- 0
|
49
|
+
- 6
|
50
|
+
version: 3.0.6
|
51
|
+
type: :development
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: mysql
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 45
|
62
|
+
segments:
|
63
|
+
- 2
|
64
|
+
- 8
|
65
|
+
- 1
|
66
|
+
version: 2.8.1
|
67
|
+
type: :development
|
68
|
+
version_requirements: *id003
|
69
|
+
description: A ruby library to provide rapid insertion of rows into a database
|
70
|
+
email:
|
71
|
+
- sunblaze@gmail.com
|
72
|
+
executables: []
|
73
|
+
|
74
|
+
extensions: []
|
75
|
+
|
76
|
+
extra_rdoc_files: []
|
77
|
+
|
78
|
+
files:
|
79
|
+
- .gitignore
|
80
|
+
- .rspec
|
81
|
+
- Gemfile
|
82
|
+
- README
|
83
|
+
- Rakefile
|
84
|
+
- lib/rapids.rb
|
85
|
+
- lib/rapids/batch.rb
|
86
|
+
- lib/rapids/batch/columns_helper.rb
|
87
|
+
- lib/rapids/batch/create_table.rb
|
88
|
+
- lib/rapids/batch/create_trigger.rb
|
89
|
+
- lib/rapids/batch/find_or_create.rb
|
90
|
+
- lib/rapids/batch/insert_into.rb
|
91
|
+
- lib/rapids/batch/model_extensions.rb
|
92
|
+
- lib/rapids/version.rb
|
93
|
+
- rapids.gemspec
|
94
|
+
- spec/batch/columns_helper_spec.rb
|
95
|
+
- spec/batch/create_table_spec.rb
|
96
|
+
- spec/batch/create_trigger_spec.rb
|
97
|
+
- spec/batch/insert_into_spec.rb
|
98
|
+
- spec/batch/spec_helper.rb
|
99
|
+
- spec/rapids_batch_spec.rb
|
100
|
+
- spec/spec_helper.rb
|
101
|
+
has_rdoc: true
|
102
|
+
homepage: http://github.com/sunblaze/rapids
|
103
|
+
licenses:
|
104
|
+
- MIT
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
|
108
|
+
require_paths:
|
109
|
+
- lib
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
hash: 57
|
116
|
+
segments:
|
117
|
+
- 1
|
118
|
+
- 8
|
119
|
+
- 7
|
120
|
+
version: 1.8.7
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
hash: 3
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
version: "0"
|
130
|
+
requirements: []
|
131
|
+
|
132
|
+
rubyforge_project: rapids
|
133
|
+
rubygems_version: 1.6.2
|
134
|
+
signing_key:
|
135
|
+
specification_version: 3
|
136
|
+
summary: A ruby library to provide rapid insertion of rows into a database
|
137
|
+
test_files:
|
138
|
+
- spec/batch/columns_helper_spec.rb
|
139
|
+
- spec/batch/create_table_spec.rb
|
140
|
+
- spec/batch/create_trigger_spec.rb
|
141
|
+
- spec/batch/insert_into_spec.rb
|
142
|
+
- spec/batch/spec_helper.rb
|
143
|
+
- spec/rapids_batch_spec.rb
|
144
|
+
- spec/spec_helper.rb
|