lincoln 0.0.1 → 0.0.2

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