schema_plus_indexes 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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +19 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +99 -0
- data/Rakefile +9 -0
- data/gemfiles/Gemfile.base +8 -0
- data/gemfiles/rails-4.2/Gemfile.base +3 -0
- data/gemfiles/rails-4.2/Gemfile.mysql2 +10 -0
- data/gemfiles/rails-4.2/Gemfile.postgresql +10 -0
- data/gemfiles/rails-4.2/Gemfile.sqlite3 +10 -0
- data/lib/schema_plus_indexes.rb +11 -0
- data/lib/schema_plus_indexes/active_record/base.rb +29 -0
- data/lib/schema_plus_indexes/active_record/connection_adapters/abstract_adapter.rb +23 -0
- data/lib/schema_plus_indexes/active_record/connection_adapters/index_definition.rb +42 -0
- data/lib/schema_plus_indexes/middleware/dumper.rb +43 -0
- data/lib/schema_plus_indexes/middleware/migration.rb +65 -0
- data/lib/schema_plus_indexes/middleware/model.rb +17 -0
- data/lib/schema_plus_indexes/middleware/sqlite3.rb +25 -0
- data/lib/schema_plus_indexes/version.rb +3 -0
- data/schema_dev.yml +9 -0
- data/schema_plus_indexes.gemspec +29 -0
- data/spec/index_definition_spec.rb +102 -0
- data/spec/index_spec.rb +180 -0
- data/spec/migration_spec.rb +188 -0
- data/spec/named_schema_spec.rb +60 -0
- data/spec/sanity_spec.rb +13 -0
- data/spec/schema_dumper_spec.rb +118 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/support/matchers/have_index.rb +60 -0
- metadata +194 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "with multiple schemas" do
|
4
|
+
def connection
|
5
|
+
ActiveRecord::Base.connection
|
6
|
+
end
|
7
|
+
|
8
|
+
before(:all) do
|
9
|
+
newdb = case connection.adapter_name
|
10
|
+
when /^mysql/i then "CREATE SCHEMA IF NOT EXISTS schema_plus_indexes_test2"
|
11
|
+
when /^postgresql/i then "CREATE SCHEMA schema_plus_indexes_test2"
|
12
|
+
when /^sqlite/i then "ATTACH ':memory:' AS schema_plus_indexes_test2"
|
13
|
+
end
|
14
|
+
begin
|
15
|
+
ActiveRecord::Base.connection.execute newdb
|
16
|
+
rescue ActiveRecord::StatementInvalid => e
|
17
|
+
raise unless e.message =~ /already exists/
|
18
|
+
end
|
19
|
+
|
20
|
+
class User < ::ActiveRecord::Base ; end
|
21
|
+
end
|
22
|
+
|
23
|
+
before(:each) do
|
24
|
+
define_schema do
|
25
|
+
create_table :users, :force => true do |t|
|
26
|
+
t.string :login
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
connection.execute 'DROP TABLE IF EXISTS schema_plus_indexes_test2.users'
|
31
|
+
connection.execute 'CREATE TABLE schema_plus_indexes_test2.users (id ' + case connection.adapter_name
|
32
|
+
when /^mysql/i then "integer primary key auto_increment"
|
33
|
+
when /^postgresql/i then "serial primary key"
|
34
|
+
when /^sqlite/i then "integer primary key autoincrement"
|
35
|
+
end + ", login varchar(255))"
|
36
|
+
end
|
37
|
+
|
38
|
+
context "with indexes in each schema" do
|
39
|
+
before(:each) do
|
40
|
+
connection.execute 'CREATE INDEX ' + case connection.adapter_name
|
41
|
+
when /^mysql/i then "index_users_on_login ON schema_plus_indexes_test2.users"
|
42
|
+
when /^postgresql/i then "index_users_on_login ON schema_plus_indexes_test2.users"
|
43
|
+
when /^sqlite/i then "schema_plus_indexes_test2.index_users_on_login ON users"
|
44
|
+
end + " (login)"
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should not find indexes in other schema" do
|
48
|
+
User.reset_column_information
|
49
|
+
expect(User.indexes).to be_empty
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should find index in current schema" do
|
53
|
+
connection.execute 'CREATE INDEX index_users_on_login ON users (login)'
|
54
|
+
User.reset_column_information
|
55
|
+
expect(User.indexes.map(&:name)).to eq(['index_users_on_login'])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
data/spec/sanity_spec.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
# A basic sanity check to have as a spec when first starting. Feel free to delete this
|
5
|
+
# once you've got real content.
|
6
|
+
|
7
|
+
describe "Sanity Check" do
|
8
|
+
|
9
|
+
it "database is connected" do
|
10
|
+
expect(ActiveRecord::Base).to be_connected
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
describe "Schema dump" do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
ActiveRecord::Migration.suppress_messages do
|
8
|
+
ActiveRecord::Schema.define do
|
9
|
+
connection.tables.each do |table| drop_table table, :cascade => true end
|
10
|
+
|
11
|
+
create_table :users, :force => true do |t|
|
12
|
+
t.string :login
|
13
|
+
t.datetime :deleted_at
|
14
|
+
t.integer :first_post_id
|
15
|
+
end
|
16
|
+
|
17
|
+
create_table :posts, :force => true do |t|
|
18
|
+
t.text :body
|
19
|
+
t.integer :user_id
|
20
|
+
t.integer :first_comment_id
|
21
|
+
t.string :string_no_default
|
22
|
+
t.integer :short_id
|
23
|
+
t.string :str_short
|
24
|
+
t.integer :integer_col
|
25
|
+
t.float :float_col
|
26
|
+
t.decimal :decimal_col
|
27
|
+
t.datetime :datetime_col
|
28
|
+
t.timestamp :timestamp_col
|
29
|
+
t.time :time_col
|
30
|
+
t.date :date_col
|
31
|
+
t.binary :binary_col
|
32
|
+
t.boolean :boolean_col
|
33
|
+
end
|
34
|
+
|
35
|
+
create_table :comments, :force => true do |t|
|
36
|
+
t.text :body
|
37
|
+
t.integer :post_id
|
38
|
+
t.integer :commenter_id
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
class ::User < ActiveRecord::Base ; end
|
43
|
+
class ::Post < ActiveRecord::Base ; end
|
44
|
+
class ::Comment < ActiveRecord::Base ; end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should include in-table index definition" do
|
48
|
+
with_index Post, :user_id do
|
49
|
+
expect(dump_posts).to match(/"user_id",.*index:/)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should not include add_index statement" do
|
54
|
+
with_index Post, :user_id do
|
55
|
+
expect(dump_posts).not_to match(%r{add_index.*user_id})
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should include index name" do
|
60
|
+
with_index Post, :user_id, :name => "custom_name" do
|
61
|
+
expect(dump_posts).to match(/"user_id",.*index:.*name: "custom_name"/)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should define unique index" do
|
66
|
+
with_index Post, :user_id, :name => "posts_user_id_index", :unique => true do
|
67
|
+
expect(dump_posts).to match(/"user_id",.*index:.*name: "posts_user_id_index", unique: true/)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should include index order", :mysql => :skip do
|
72
|
+
with_index Post, [:user_id, :first_comment_id, :short_id], :order => { :user_id => :asc, :first_comment_id => :desc } do
|
73
|
+
expect(dump_posts).to match(/"user_id".*index: {.*with: \["first_comment_id", "short_id"\],.*order: {"user_id"=>:asc, "first_comment_id"=>:desc, "short_id"=>:asc}/)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
protected
|
78
|
+
|
79
|
+
def to_regexp(string)
|
80
|
+
Regexp.new(Regexp.escape(string))
|
81
|
+
end
|
82
|
+
|
83
|
+
def with_index(*args)
|
84
|
+
options = args.extract_options!
|
85
|
+
model, columns = args
|
86
|
+
ActiveRecord::Migration.suppress_messages do
|
87
|
+
ActiveRecord::Migration.add_index(model.table_name, columns, options)
|
88
|
+
end
|
89
|
+
model.reset_column_information
|
90
|
+
begin
|
91
|
+
yield
|
92
|
+
ensure
|
93
|
+
ActiveRecord::Migration.suppress_messages do
|
94
|
+
ActiveRecord::Migration.remove_index(model.table_name, :name => determine_index_name(model, columns, options))
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def determine_index_name(model, columns, options)
|
100
|
+
name = columns[:name] if columns.is_a?(Hash)
|
101
|
+
name ||= options[:name]
|
102
|
+
name ||= model.indexes.detect { |index| index.table == model.table_name.to_s && index.columns.sort == Array(columns).collect(&:to_s).sort }.name
|
103
|
+
name
|
104
|
+
end
|
105
|
+
|
106
|
+
def dump_schema(opts={})
|
107
|
+
stream = StringIO.new
|
108
|
+
ActiveRecord::SchemaDumper.ignore_tables = Array.wrap(opts[:ignore]) || []
|
109
|
+
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
|
110
|
+
stream.string
|
111
|
+
end
|
112
|
+
|
113
|
+
def dump_posts
|
114
|
+
dump_schema(:ignore => %w[users comments])
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
require 'simplecov-gem-profile'
|
3
|
+
SimpleCov.start "gem"
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
6
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
|
+
|
8
|
+
require 'rspec'
|
9
|
+
require 'active_record'
|
10
|
+
require 'schema_plus_indexes'
|
11
|
+
require 'schema_dev/rspec'
|
12
|
+
|
13
|
+
SchemaDev::Rspec.setup
|
14
|
+
|
15
|
+
Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
|
16
|
+
|
17
|
+
RSpec.configure do |config|
|
18
|
+
config.include(SchemaPlusIndexesMatchers)
|
19
|
+
config.warnings = true
|
20
|
+
end
|
21
|
+
|
22
|
+
def define_schema(config={}, &block)
|
23
|
+
ActiveRecord::Migration.suppress_messages do
|
24
|
+
ActiveRecord::Schema.define do
|
25
|
+
connection.tables.each do |table|
|
26
|
+
drop_table table, :cascade => true
|
27
|
+
end
|
28
|
+
instance_eval &block
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
SimpleCov.command_name "[ruby #{RUBY_VERSION} - ActiveRecord #{::ActiveRecord::VERSION::STRING} - #{ActiveRecord::Base.connection.adapter_name}]"
|
34
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module SchemaPlusIndexesMatchers
|
2
|
+
|
3
|
+
class HaveIndex
|
4
|
+
|
5
|
+
def initialize(expectation, options = {})
|
6
|
+
set_required_columns(expectation)
|
7
|
+
@unique = options.delete(:unique)
|
8
|
+
@name = options.delete(:name)
|
9
|
+
end
|
10
|
+
|
11
|
+
def matches?(model)
|
12
|
+
@too_many = nil
|
13
|
+
@model = model
|
14
|
+
indexes = @model.indexes.select { |index| index.columns.to_set == @required_columns }
|
15
|
+
if indexes.length > 1
|
16
|
+
@too_many = indexes.length
|
17
|
+
return false
|
18
|
+
end
|
19
|
+
index = indexes.first
|
20
|
+
return index && (@unique ? index.unique : true) && (@name ? index.name == @name.to_s : true)
|
21
|
+
end
|
22
|
+
|
23
|
+
def failure_message(should_not = false)
|
24
|
+
invert = should_not ? "not to" : "to"
|
25
|
+
what = ""
|
26
|
+
what += "unique " if @unique
|
27
|
+
what += "named '{@name}'" if @name
|
28
|
+
msg = "Expected #{@model.table_name} #{invert} contain one #{what}index on #{@required_columns.entries.inspect}"
|
29
|
+
msg += "; found #{@too_many} indexes" if @too_many
|
30
|
+
msg
|
31
|
+
end
|
32
|
+
|
33
|
+
def failure_message_when_negated
|
34
|
+
failure_message(true)
|
35
|
+
end
|
36
|
+
|
37
|
+
def on(expectation)
|
38
|
+
set_required_columns(expectation)
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def set_required_columns(expectation)
|
44
|
+
@required_columns = Array(expectation).collect(&:to_s).to_set
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
def have_index(*expectation)
|
50
|
+
options = expectation.extract_options!
|
51
|
+
HaveIndex.new(expectation, options)
|
52
|
+
end
|
53
|
+
|
54
|
+
def have_unique_index(*expectation)
|
55
|
+
options = expectation.extract_options!
|
56
|
+
options[:unique] = true
|
57
|
+
HaveIndex.new(expectation, options)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
metadata
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: schema_plus_indexes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ronen barzel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: schema_monkey
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.7'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.7'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.0.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.0.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: schema_dev
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: simplecov
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: simplecov-gem-profile
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description:
|
126
|
+
email:
|
127
|
+
- ronen@barzel.org
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- ".gitignore"
|
133
|
+
- ".travis.yml"
|
134
|
+
- Gemfile
|
135
|
+
- LICENSE.txt
|
136
|
+
- README.md
|
137
|
+
- Rakefile
|
138
|
+
- gemfiles/Gemfile.base
|
139
|
+
- gemfiles/rails-4.2/Gemfile.base
|
140
|
+
- gemfiles/rails-4.2/Gemfile.mysql2
|
141
|
+
- gemfiles/rails-4.2/Gemfile.postgresql
|
142
|
+
- gemfiles/rails-4.2/Gemfile.sqlite3
|
143
|
+
- lib/schema_plus_indexes.rb
|
144
|
+
- lib/schema_plus_indexes/active_record/base.rb
|
145
|
+
- lib/schema_plus_indexes/active_record/connection_adapters/abstract_adapter.rb
|
146
|
+
- lib/schema_plus_indexes/active_record/connection_adapters/index_definition.rb
|
147
|
+
- lib/schema_plus_indexes/middleware/dumper.rb
|
148
|
+
- lib/schema_plus_indexes/middleware/migration.rb
|
149
|
+
- lib/schema_plus_indexes/middleware/model.rb
|
150
|
+
- lib/schema_plus_indexes/middleware/sqlite3.rb
|
151
|
+
- lib/schema_plus_indexes/version.rb
|
152
|
+
- schema_dev.yml
|
153
|
+
- schema_plus_indexes.gemspec
|
154
|
+
- spec/index_definition_spec.rb
|
155
|
+
- spec/index_spec.rb
|
156
|
+
- spec/migration_spec.rb
|
157
|
+
- spec/named_schema_spec.rb
|
158
|
+
- spec/sanity_spec.rb
|
159
|
+
- spec/schema_dumper_spec.rb
|
160
|
+
- spec/spec_helper.rb
|
161
|
+
- spec/support/matchers/have_index.rb
|
162
|
+
homepage: https://github.com/SchemaPlus/schema_plus_indexes
|
163
|
+
licenses:
|
164
|
+
- MIT
|
165
|
+
metadata: {}
|
166
|
+
post_install_message:
|
167
|
+
rdoc_options: []
|
168
|
+
require_paths:
|
169
|
+
- lib
|
170
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
171
|
+
requirements:
|
172
|
+
- - ">="
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
175
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - ">="
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
180
|
+
requirements: []
|
181
|
+
rubyforge_project:
|
182
|
+
rubygems_version: 2.2.2
|
183
|
+
signing_key:
|
184
|
+
specification_version: 4
|
185
|
+
summary: Adds shorthands and conveniences to ActiveRecord's handling of indexes
|
186
|
+
test_files:
|
187
|
+
- spec/index_definition_spec.rb
|
188
|
+
- spec/index_spec.rb
|
189
|
+
- spec/migration_spec.rb
|
190
|
+
- spec/named_schema_spec.rb
|
191
|
+
- spec/sanity_spec.rb
|
192
|
+
- spec/schema_dumper_spec.rb
|
193
|
+
- spec/spec_helper.rb
|
194
|
+
- spec/support/matchers/have_index.rb
|