rapids 0.1.0
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/.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
|