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 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