lincoln 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ == Creating the test database
2
+
3
+ This gem currently only supports Mysql. To run the unit tests, first create a database named lincoln_test on
4
+ localhost. See database.rb for connection details for the test suite.
5
+
6
+ == Running with Rake
7
+
8
+ After creating the default database, run "rake" to run all the tests.
data/Rakefile CHANGED
@@ -4,9 +4,10 @@ require 'bundler'
4
4
 
5
5
  Bundler::GemHelper.install_tasks
6
6
 
7
- require "rspec/core/rake_task"
8
- RSpec::Core::RakeTask.new(:spec) do |spec|
9
- spec.pattern = 'spec/**/*_spec.rb'
7
+ require 'rake/testtask'
8
+ Rake::TestTask.new do |t|
9
+ t.libs << "test"
10
+ t.test_files = FileList['test/**/test_*.rb']
10
11
  end
11
12
 
12
- task :default => :spec
13
+ task :default => :test
data/lib/lincoln.rb CHANGED
@@ -1,4 +1,3 @@
1
1
  require 'active_record'
2
2
  require File.dirname(__FILE__) + "/lincoln/attr_ignore"
3
-
4
- ActiveRecord::Base.send(:include, Lincoln::AttrIgnore)
3
+ require File.dirname(__FILE__) + "/lincoln/mysql_adapter"
@@ -37,4 +37,6 @@ module Lincoln
37
37
  module InstanceMethods
38
38
  end
39
39
  end
40
- end
40
+ end
41
+
42
+ ActiveRecord::Base.send(:include, Lincoln::AttrIgnore)
@@ -0,0 +1,111 @@
1
+ # ActiveRecord::ConnectionAdapters::SchemaStatements
2
+ # module ActiveRecord
3
+ # module ConnectionAdapters
4
+ # module SchemaStatements
5
+ # def change_table(table_name)
6
+ # yield Lincoln::Table.new(table_name, self)
7
+ # end
8
+ # end
9
+ # end
10
+ # end
11
+
12
+ module Lincoln
13
+ module ChangeTableOptimization
14
+ def change_table(table_name)
15
+ @table_name = table_name
16
+ @new_table_name = generate_table_name(table_name)
17
+ copy_table_structure(table_name, @new_table_name)
18
+
19
+ super(@new_table_name)
20
+
21
+ copy_data(table_name, @new_table_name)
22
+ swap_tables(@new_table_name, table_name)
23
+
24
+ @table_name = nil
25
+ @new_table_name = nil
26
+ end
27
+
28
+ def add_column(table_name, column_name, type, options = {})
29
+ track("add", column_name)
30
+ super
31
+ end
32
+
33
+ def remove_column(table_name, *column_names)
34
+ column_names.each { |c| track("remove", c) }
35
+ super
36
+ end
37
+
38
+ def rename_column_with_tracking(table_name, column_name, new_column_name)
39
+ track("rename", [column_name, new_column_name])
40
+ rename_column_without_tracking(table_name, column_name, new_column_name)
41
+ end
42
+
43
+ def index_name(table_name, options)
44
+ if @table_name != nil
45
+ return super(@table_name, options)
46
+ end
47
+ super(table_name, options)
48
+ end
49
+
50
+ private
51
+
52
+ def track(operation, data)
53
+ @track ||= {}
54
+ @track[operation] ||= []
55
+ @track[operation].push(data)
56
+ end
57
+
58
+ def column_removed?(column_name)
59
+ @track ||= {}
60
+ @track["remove"] ||= []
61
+ @track["remove"].include?(column_name)
62
+ end
63
+
64
+ def column_added?(column_name)
65
+ @track ||= {}
66
+ @track["add"] ||= []
67
+ @track["add"].include?(column_name)
68
+ end
69
+
70
+ def new_column_name(column_name)
71
+ @track ||= {}
72
+ @track["rename"] ||= []
73
+ @track["rename"].each do |rename_array|
74
+ if rename_array.first.to_s == column_name.to_s
75
+ return rename_array.last
76
+ end
77
+ end
78
+ column_name
79
+ end
80
+
81
+ def generate_table_name(table_name)
82
+ "#{table_name}_#{Time.now.to_i}_#{(rand * 100_000_000).to_i}"
83
+ end
84
+
85
+ def copy_table_structure(table_name, new_table_name)
86
+ execute("CREATE TABLE #{quote_table_name(new_table_name)} LIKE #{quote_table_name(table_name)}")
87
+ end
88
+
89
+ def copy_data(table_name, new_table_name)
90
+ original_column_names = columns(table_name).map do |column|
91
+ return nil if column_removed?(column.name) || column_added?(column.name)
92
+ quote_column_name(column.name)
93
+ end.flatten
94
+
95
+ new_column_names = columns(table_name).map do |column|
96
+ return nil if column_removed?(column.name) || column_added?(column.name)
97
+ quote_column_name(new_column_name(column.name))
98
+ end.flatten
99
+
100
+ execute("INSERT INTO #{quote_table_name(new_table_name)} (#{new_column_names.join(", ")}) SELECT #{original_column_names.join(", ")} FROM #{quote_table_name(table_name)}")
101
+ end
102
+
103
+ def swap_tables(new_table_name, table_name)
104
+ rename_table(table_name, "#{new_table_name}_bak")
105
+ rename_table(new_table_name, table_name)
106
+ end
107
+ end
108
+ end
109
+
110
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:include, Lincoln::ChangeTableOptimization)
111
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:alias_method_chain, :rename_column, :tracking)
@@ -1,3 +1,3 @@
1
1
  module Lincoln
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lincoln.gemspec CHANGED
@@ -18,7 +18,8 @@ Gem::Specification.new do |s|
18
18
  s.required_rubygems_version = ">= 1.3.6"
19
19
 
20
20
  s.add_dependency('activerecord', '~> 3.0')
21
- s.add_development_dependency "rspec", "~> 2.0"
21
+ s.add_development_dependency "shoulda"
22
+ s.add_development_dependency "mysql"
22
23
 
23
24
  s.files = `git ls-files`.split("\n")
24
25
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -0,0 +1,111 @@
1
+ module Lincoln
2
+ module Matchers
3
+ def have_column(column_name, &blk)
4
+ ColumnMatcher.new(column_name, &blk)
5
+ end
6
+
7
+ class ColumnMatcher
8
+ def initialize(column_name)
9
+ @column_name = column_name.to_s
10
+ end
11
+
12
+ def matches?(klass)
13
+ @klass = klass.class
14
+ @column = find_column
15
+
16
+ column_exists? &&
17
+ correct_type? &&
18
+ correct_precision? &&
19
+ correct_default? &&
20
+ correct_limit?
21
+ end
22
+
23
+ def with_type(type)
24
+ @type = type.to_s
25
+ self
26
+ end
27
+
28
+ def with_precision(precision)
29
+ @precision = precision.to_s
30
+ self
31
+ end
32
+
33
+ def with_default(default)
34
+ @default = default.to_s
35
+ self
36
+ end
37
+
38
+ def with_limit(limit)
39
+ @limit = limit.to_s
40
+ self
41
+ end
42
+
43
+ def description
44
+ "have a column named #{@column_name}"
45
+ end
46
+
47
+ def failure_message
48
+ "Expected column with name '#{@column_name}'"
49
+ end
50
+
51
+ def negative_failure_message
52
+ "Did not expect column with name '#{@column_name}'"
53
+ end
54
+
55
+ private
56
+
57
+ def find_column
58
+ columns = @klass.columns.find_all { |c| c.name == @column_name }
59
+ columns.size == 1 ? columns.first : nil
60
+ end
61
+
62
+ def column_exists?
63
+ !@column.nil?
64
+ end
65
+
66
+ def correct_type?
67
+ return true if @type.nil?
68
+
69
+ if @column.type.to_s != @type
70
+ @missing = "actual column type was '#{@column.type}'"
71
+ false
72
+ else
73
+ true
74
+ end
75
+ end
76
+
77
+ def correct_precision?
78
+ return true if @precision.nil?
79
+
80
+ if @column.precision.to_s != @precision
81
+ @missing = "actual column precision was '#{@column.precision}'"
82
+ false
83
+ else
84
+ true
85
+ end
86
+ end
87
+
88
+ def correct_default?
89
+ return true if @default.nil?
90
+
91
+ if @column.default.to_s != @default
92
+ @missing = "actual column default was '#{@column.default}'"
93
+ false
94
+ else
95
+ true
96
+ end
97
+ end
98
+
99
+ def correct_limit?
100
+ return true if @limit.nil?
101
+
102
+ if @column.limit.to_s != @limit
103
+ @missing = "actual column limit was '#{@column.limit}'"
104
+ false
105
+ else
106
+ true
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,48 @@
1
+ module Lincoln
2
+ module Matchers
3
+ def have_index(column_name, &blk)
4
+ ColumnIndex.new(column_name, &blk)
5
+ end
6
+
7
+ class ColumnIndex
8
+ def initialize(columns_for_index)
9
+ @columns_for_index = columns_for_index
10
+ @columns_for_index = [columns_for_index] unless columns_for_index.kind_of?(Array)
11
+ @columns_for_index = @columns_for_index.map { |c| c.to_s }
12
+ end
13
+
14
+ def matches?(klass)
15
+ @klass = klass.class
16
+ @index = find_index
17
+
18
+ index_exists?
19
+ end
20
+
21
+ def description
22
+ "have an index for column '#{@columns_for_index.inspect}'"
23
+ end
24
+
25
+ def failure_message
26
+ "Expected index with name '#{@columns_for_index.inspect}'"
27
+ end
28
+
29
+ def negative_failure_message
30
+ "Did not expect column with name '#{@columns_for_index.inspect}'"
31
+ end
32
+
33
+ private
34
+
35
+ def find_index
36
+ indexes = @klass.connection.indexes(@klass.table_name).find_all do |index|
37
+ index.columns == @columns_for_index
38
+ end
39
+ indexes.size == 1 ? indexes.first : nil
40
+ end
41
+
42
+ def index_exists?
43
+ !@index.nil?
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,9 @@
1
+ Dir.glob("#{File.dirname(__FILE__)}/**/*.rb") do |file|
2
+ require file
3
+ end
4
+
5
+ # Mix in the matchers to test case
6
+ class Test::Unit::TestCase
7
+ include Lincoln::Matchers
8
+ extend Lincoln::Matchers
9
+ end
@@ -0,0 +1,16 @@
1
+ require 'active_record'
2
+
3
+ ActiveRecord::Base.establish_connection({
4
+ :adapter => 'mysql',
5
+ :host => 'localhost',
6
+ :user => 'root',
7
+ :database => 'lincoln_test'
8
+ })
9
+
10
+ def create_my_models
11
+ ActiveRecord::Schema.define do
12
+ create_table "my_models", :force => true do |t|
13
+ t.string :column1, :column2, :column3
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ require File.dirname(__FILE__) + "/test_helper"
2
+
3
+ class AttrIgnoreTest < ActiveSupport::TestCase
4
+ setup do
5
+ create_my_models
6
+ end
7
+
8
+ should "have ignore_attributes" do
9
+ assert_equal Set.new(["column1", "column2"]), MyModel.ignore_attributes
10
+ end
11
+
12
+ should "ignore columns specified in ignore_attr" do
13
+ column_names = MyModel.columns.collect { |column| column.name }
14
+ assert_equal ["id", "column3"], column_names
15
+ end
16
+ end
17
+
18
+ class MyModel < ActiveRecord::Base
19
+ attr_ignore :column1, :column2
20
+ end
@@ -0,0 +1,114 @@
1
+ require File.dirname(__FILE__) + "/test_helper"
2
+
3
+ class MyModelTest < ActiveSupport::TestCase
4
+ setup do
5
+ create_my_models
6
+ end
7
+
8
+ context "MyModel" do
9
+ setup do
10
+ MyModelsAddColumn.up
11
+ MyModel.reset_column_information
12
+ end
13
+
14
+ should have_index("column2")
15
+ should have_column("column4").with_type(:string)
16
+
17
+ should have_column("created_at")
18
+ should have_column("updated_at")
19
+
20
+ should have_column("column5").with_type(:string)
21
+ should have_column("column6").with_type(:text)
22
+ should have_column("column7").with_type(:integer)
23
+ should have_column("column8").with_type(:float)
24
+ should have_column("column9").with_type(:integer).with_precision(10)
25
+ should have_column("column10").with_type(:datetime)
26
+ should have_column("column11").with_type(:datetime)
27
+ should have_column("column12").with_type(:time)
28
+ should have_column("column13").with_type(:date)
29
+ should have_column("column14").with_type(:binary)
30
+ should have_column("column15").with_type(:boolean)
31
+
32
+ should have_column("model1_id").with_type(:integer)
33
+ should have_column("model2_id").with_type(:integer)
34
+ should have_column("model2_type").with_type(:string)
35
+
36
+ context "remove columns" do
37
+ setup do
38
+ MyModelsRemoveColumn.up
39
+ MyModel.reset_column_information
40
+ end
41
+
42
+ should_not have_column("column1")
43
+ should_not have_index("column2")
44
+ should_not have_column("created_at")
45
+ should_not have_column("updated_at")
46
+ should_not have_column("model1_id")
47
+ should_not have_column("model2_id")
48
+ should_not have_column("model2_type")
49
+ end
50
+
51
+ context "changes columns" do
52
+ setup do
53
+ MyModelsChangeColumn.up
54
+ MyModel.reset_column_information
55
+ end
56
+
57
+ should_not have_column("column4")
58
+ should have_column("column40")
59
+ should have_column("column3").with_default('default')
60
+ should have_column("column3").with_limit(20)
61
+ end
62
+ end
63
+ end
64
+
65
+ class MyModel < ActiveRecord::Base
66
+ end
67
+
68
+ class MyModelsAddColumn < ActiveRecord::Migration
69
+ def self.up
70
+ change_table(:my_models) do |t|
71
+ t.index :column2
72
+
73
+ t.column :column4, :string
74
+ t.string :column5
75
+ t.text :column6
76
+ t.integer :column7
77
+ t.float :column8
78
+ t.decimal :column9
79
+ t.datetime :column10
80
+ t.timestamp :column11
81
+ t.time :column12
82
+ t.date :column13
83
+ t.binary :column14
84
+ t.boolean :column15
85
+
86
+ t.timestamps
87
+
88
+ t.references :model1
89
+ t.belongs_to :model2, :polymorphic => true
90
+ end
91
+ end
92
+ end
93
+
94
+ class MyModelsRemoveColumn < ActiveRecord::Migration
95
+ def self.up
96
+ change_table(:my_models) do |t|
97
+ t.remove :column1
98
+ t.remove_index :column2
99
+ t.remove_timestamps
100
+ t.remove_references :model1
101
+ t.remove_belongs_to :model2, :polymorphic => true
102
+ end
103
+ end
104
+ end
105
+
106
+ class MyModelsChangeColumn < ActiveRecord::Migration
107
+ def self.up
108
+ change_table(:my_models) do |t|
109
+ t.change :column3, :string, :limit => 20
110
+ t.change_default :column3, 'default'
111
+ t.rename :column4, :column40
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,19 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'rubygems'
4
+ require 'shoulda'
5
+
6
+ require 'active_record'
7
+ # Debug activerecord
8
+ # require 'logger'
9
+ # ActiveRecord::Base.logger = Logger.new(STDOUT)
10
+
11
+ # Load the schema
12
+ TEST_ROOT = File.expand_path(File.dirname(__FILE__))
13
+ $: << File.join(TEST_ROOT, 'lib')
14
+ load 'database.rb'
15
+
16
+ # Load the matchers
17
+ require File.dirname(__FILE__) + '/../shoulda_matchers/lincoln_matchers'
18
+
19
+ require 'lincoln'
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+ awk '/--/{i++}{print > ("split."i)}' $1
3
+ ls | awk '/split\.[0-9]+/'
@@ -0,0 +1,27 @@
1
+ file_names=`./file_split.sh $1`
2
+
3
+ times_to_run_query=${2:-"4"}
4
+
5
+ for file_name in $file_names
6
+ do
7
+ uncached_sum=0
8
+ cached_sum=0
9
+
10
+ for (( i=1; i<=$times_to_run_query; i++ ))
11
+ do
12
+ sudo /etc/init.d/mysql restart &>/dev/null
13
+ uncached=`mysql -u root quantum_production -vvv < $file_name | awk '/\((.*) sec\)/' | sed 's/^.*(//' | sed 's/ .*$//'`
14
+ cached=`mysql -u root quantum_production -vvv < $file_name | awk '/\((.*) sec\)/' | sed 's/^.*(//' | sed 's/ .*$//'`
15
+ uncached_sum=`ruby -e "print ($uncached_sum + $uncached)"`
16
+ cached_sum=`ruby -e "print ($cached_sum + $cached)"`
17
+ echo "$uncached $cached"
18
+ done
19
+
20
+ uncached_average=`ruby -e "print ($uncached_sum/$times_to_run_query)"`
21
+ cached_average=`ruby -e "print ($cached_sum/$times_to_run_query)"`
22
+
23
+ echo "Average of uncached: $uncached_average"
24
+ echo "Average of cached: $cached_average"
25
+ done
26
+
27
+ rm $file_names
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lincoln
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 1
10
- version: 0.0.1
9
+ - 2
10
+ version: 0.0.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Ben Curren
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-04-05 00:00:00 -07:00
18
+ date: 2011-05-17 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -34,20 +34,33 @@ dependencies:
34
34
  type: :runtime
35
35
  version_requirements: *id001
36
36
  - !ruby/object:Gem::Dependency
37
- name: rspec
37
+ name: shoulda
38
38
  prerelease: false
39
39
  requirement: &id002 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
- - - ~>
42
+ - - ">="
43
43
  - !ruby/object:Gem::Version
44
44
  hash: 3
45
45
  segments:
46
- - 2
47
46
  - 0
48
- version: "2.0"
47
+ version: "0"
49
48
  type: :development
50
49
  version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: mysql
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ type: :development
63
+ version_requirements: *id003
51
64
  description: Extensions to ActiveRecord to make it easier to run at scale.
52
65
  email:
53
66
  - ben@outright.com
@@ -61,15 +74,22 @@ files:
61
74
  - .gitignore
62
75
  - Gemfile
63
76
  - README.md
77
+ - RUNNING_UNIT_TESTS
64
78
  - Rakefile
65
- - autotest/discover.rb
66
79
  - lib/lincoln.rb
67
80
  - lib/lincoln/attr_ignore.rb
81
+ - lib/lincoln/mysql_adapter.rb
68
82
  - lib/lincoln/version.rb
69
83
  - lincoln.gemspec
70
- - spec/attr_ignore_spec.rb
71
- - spec/spec.opts
72
- - spec/spec_helper.rb
84
+ - shoulda_matchers/column_matcher.rb
85
+ - shoulda_matchers/index_matcher.rb
86
+ - shoulda_matchers/lincoln_matchers.rb
87
+ - test/lib/database.rb
88
+ - test/test_attr_ignore.rb
89
+ - test/test_change_table.rb
90
+ - test/test_helper.rb
91
+ - tools/file_split.sh
92
+ - tools/query_tester.sh
73
93
  has_rdoc: true
74
94
  homepage: ""
75
95
  licenses: []
@@ -109,6 +129,7 @@ signing_key:
109
129
  specification_version: 3
110
130
  summary: Extensions to ActiveRecord to make it easier to run at scale.
111
131
  test_files:
112
- - spec/attr_ignore_spec.rb
113
- - spec/spec.opts
114
- - spec/spec_helper.rb
132
+ - test/lib/database.rb
133
+ - test/test_attr_ignore.rb
134
+ - test/test_change_table.rb
135
+ - test/test_helper.rb
data/autotest/discover.rb DELETED
@@ -1 +0,0 @@
1
- Autotest.add_discovery { "rspec" }
@@ -1,16 +0,0 @@
1
- require File.dirname(__FILE__) + "/spec_helper"
2
-
3
- describe "attr_reader" do
4
- it "has ignore_attributes" do
5
- MyModel.ignore_attributes.should == Set.new(["column1", "column2"])
6
- end
7
-
8
- it "ignores columns specified in ignore_attr" do
9
- column_names = MyModel.columns.collect { |column| column.name }
10
- column_names.should == ["column3"]
11
- end
12
- end
13
-
14
- class MyModel < ActiveRecord::Base
15
- attr_ignore :column1, :column2
16
- end
data/spec/spec.opts DELETED
File without changes
data/spec/spec_helper.rb DELETED
@@ -1,21 +0,0 @@
1
- $LOAD_PATH.unshift(File.dirname(__FILE__))
2
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
- require 'rubygems'
4
- require 'rspec'
5
- require 'rspec/autorun'
6
-
7
- # Need to override ActivreRecord::Base before we require lincoln
8
- # for testing
9
- require 'active_record'
10
- class ActiveRecord::Base
11
- def self.columns
12
- [ActiveRecord::ConnectionAdapters::Column.new("column1", nil, "string", false),
13
- ActiveRecord::ConnectionAdapters::Column.new("column2", nil, "string", false),
14
- ActiveRecord::ConnectionAdapters::Column.new("column3", nil, "string", false)]
15
- end
16
- end
17
- require 'lincoln'
18
-
19
- RSpec.configure do |config|
20
-
21
- end