rapids 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rapids.gemspec
4
+ gemspec
data/README ADDED
File without changes
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
data/lib/rapids.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__),'rapids','batch')
@@ -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
@@ -0,0 +1,3 @@
1
+ module Rapids
2
+ VERSION = "0.1.0"
3
+ 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
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ class RapidsBatchTestCase < ActiveRecord::Base
4
+ extend Rapids::Batch
5
+ end
6
+
7
+ describe Rapids::Batch do
8
+ end
@@ -0,0 +1,3 @@
1
+ require 'rapids'
2
+ require 'active_record'
3
+
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