schema_plus_indexes 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c53c8e9e64713769dfa034d9f28f83251553e23b
|
4
|
+
data.tar.gz: 56b8cc58c8e1332ed557fa75e76c3c30e01a3e78
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f89e5ac13330c1e3a111fcd22c94376f362fda21d5fe52d17f909c87ff878e5a2d5fad4b7d477d96e93b84b3a77450f47c19d020296da080f0c467cadc5231ff
|
7
|
+
data.tar.gz: 63acf447a5afacc21fa08da17f4c9adc121dee9a16b364cdf77ec12e0012e245db3d6870ed5ef63a2bbc5851afb0631af5575bebe54b4d3202f1b2db3908b8d1
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# This file was auto-generated by the schema_dev tool, based on the data in
|
2
|
+
# ./schema_dev.yml
|
3
|
+
# Please do not edit this file; any changes will be overwritten next time
|
4
|
+
# schema_dev gets run.
|
5
|
+
---
|
6
|
+
sudo: false
|
7
|
+
rvm:
|
8
|
+
- 1.9.3
|
9
|
+
- 2.1.5
|
10
|
+
gemfile:
|
11
|
+
- gemfiles/rails-4.2/Gemfile.mysql2
|
12
|
+
- gemfiles/rails-4.2/Gemfile.postgresql
|
13
|
+
- gemfiles/rails-4.2/Gemfile.sqlite3
|
14
|
+
env: POSTGRESQL_DB_USER=postgres MYSQL_DB_USER=travis
|
15
|
+
addons:
|
16
|
+
postgresql: '9.3'
|
17
|
+
before_script: bundle exec rake create_databases
|
18
|
+
after_script: bundle exec rake drop_databases
|
19
|
+
script: bundle exec rake travis
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 ronen barzel
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/schema_plus_indexes.svg)](http://badge.fury.io/rb/schema_plus_indexes)
|
2
|
+
[![Build Status](https://secure.travis-ci.org/SchemaPlus/schema_plus_indexes.svg)](http://travis-ci.org/SchemaPlus/schema_plus_indexes)
|
3
|
+
[![Coverage Status](https://img.shields.io/coveralls/SchemaPlus/schema_plus_indexes.svg)](https://coveralls.io/r/SchemaPlus/schema_plus_indexes)
|
4
|
+
[![Dependency Status](https://gemnasium.com/lomba/schema_plus_indexes.svg)](https://gemnasium.com/SchemaPlus/schema_plus_indexes)
|
5
|
+
|
6
|
+
# schema_plus_indexes
|
7
|
+
|
8
|
+
Schema_plus_index adds various convenient capabilities to `ActiveRecord`'s index handling:
|
9
|
+
|
10
|
+
* Adds shorthands to the `:index` option in migrations
|
11
|
+
|
12
|
+
create_table :parts do |t|
|
13
|
+
t.string :role, index: true # shorthand for index: {}
|
14
|
+
t.string :product_code, index: :unique # shorthand for index: { unique: true }
|
15
|
+
t.string :first_name
|
16
|
+
t.string :last_name, index: { with: :first_name } # multi-column index
|
17
|
+
|
18
|
+
t.string :country_code
|
19
|
+
t.string :area_code
|
20
|
+
t.string :local_number, index: { with: [:country_code, :area_code] } # multi-column index
|
21
|
+
end
|
22
|
+
|
23
|
+
Of course options can be combined, such as `index: { with: :first_name, unique: true, name: "my_index"}`
|
24
|
+
|
25
|
+
* Ensures that the `:index` option is respected by `Migration.add_column` and in `Migration.change_table`
|
26
|
+
|
27
|
+
* Adds `:if_exists` option to `ActiveRecord::Migration.remove_index`
|
28
|
+
|
29
|
+
* Provides consistent behavior regarding attempted duplicate index
|
30
|
+
creation: Ignore and log a warning. Different versions of Rails with
|
31
|
+
different db adapters otherwise behave inconsistently: some ignore the
|
32
|
+
attempt, some raise an error.
|
33
|
+
|
34
|
+
* `Model.indexes` returns the indexes defined for the `ActiveRecord` model.
|
35
|
+
Shorthand for `connection.indexes(Model.table_name)`; the value is cached
|
36
|
+
until the next time `Model.reset_column_information` is called
|
37
|
+
|
38
|
+
* In the schema dump `schema.rb`, index definitions are included within the
|
39
|
+
`create_table` statements rather than added afterwards
|
40
|
+
|
41
|
+
* When using SQLite3, makes sure that the definitions returned by
|
42
|
+
`connection.indexes` properly include the column orders (`:asc` or `:desc`)
|
43
|
+
|
44
|
+
schema_plus_indexes is part of the [SchemaPlus](https://github.com/SchemaPlus/) family of Ruby on Rails extension gems.
|
45
|
+
|
46
|
+
## Installation
|
47
|
+
|
48
|
+
In your application's Gemfile
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
gem "schema_plus_indexes"
|
52
|
+
```
|
53
|
+
## Compatibility
|
54
|
+
|
55
|
+
schema_plus_indexes is tested on
|
56
|
+
|
57
|
+
[//]: # SCHEMA_DEV: MATRIX - begin
|
58
|
+
[//]: # These lines are auto-generated by schema_dev based on schema_dev.yml
|
59
|
+
* ruby **1.9.3** with rails **4.2**, using **mysql2**, **sqlite3** or **postgresql**
|
60
|
+
* ruby **2.1.5** with rails **4.2**, using **mysql2**, **sqlite3** or **postgresql**
|
61
|
+
|
62
|
+
[//]: # SCHEMA_DEV: MATRIX - end
|
63
|
+
|
64
|
+
|
65
|
+
## History
|
66
|
+
|
67
|
+
### v0.1.0
|
68
|
+
|
69
|
+
* Initial release
|
70
|
+
|
71
|
+
## Development & Testing
|
72
|
+
|
73
|
+
Are you interested in contributing to schema_plus_indexes? Thanks! Please follow
|
74
|
+
the standard protocol: fork, feature branch, develop, push, and issue pull request.
|
75
|
+
|
76
|
+
Some things to know about to help you develop and test:
|
77
|
+
|
78
|
+
* **schema_dev**: schema_plus_indexes uses [schema_dev](https://github.com/SchemaPlus/schema_dev) to
|
79
|
+
facilitate running rspec tests on the matrix of ruby, rails, and database
|
80
|
+
versions that the gem supports, both locally and on
|
81
|
+
[travis-ci](http://travis-ci.org/SchemaPlus/schema_plus_indexes)
|
82
|
+
|
83
|
+
To to run rspec locally on the full matrix, do:
|
84
|
+
|
85
|
+
$ schema_dev bundle install
|
86
|
+
$ schema_dev rspec
|
87
|
+
|
88
|
+
You can also run on just one configuration at a time; For info, see `schema_dev --help` or the
|
89
|
+
[schema_dev](https://github.com/SchemaPlus/schema_dev) README.
|
90
|
+
|
91
|
+
The matrix of configurations is specified in `schema_dev.yml` in
|
92
|
+
the project root.
|
93
|
+
|
94
|
+
* **schema_monkey**: schema_plus_indexes extends ActiveRecord using
|
95
|
+
[schema_monkey](https://github.com/SchemaPlus/schema_monkey)'s extension
|
96
|
+
API and protocols -- see its README for details. If your contribution needs any additional monkey patching
|
97
|
+
that isn't already supported by
|
98
|
+
[schema_monkey](https://github.com/SchemaPlus/schema_monkey), please head
|
99
|
+
over there and submit a PR.
|
data/Rakefile
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
gemspec :path => File.expand_path('..', __FILE__)
|
3
|
+
|
4
|
+
platform :ruby do
|
5
|
+
gem "byebug" if RUBY_VERSION > "2"
|
6
|
+
end
|
7
|
+
|
8
|
+
File.exist?(gemfile_local = File.expand_path('../Gemfile.local', __FILE__)) and eval File.read(gemfile_local), binding, gemfile_local
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'schema_monkey'
|
2
|
+
|
3
|
+
require_relative 'schema_plus_indexes/active_record/base'
|
4
|
+
require_relative 'schema_plus_indexes/active_record/connection_adapters/abstract_adapter'
|
5
|
+
require_relative 'schema_plus_indexes/active_record/connection_adapters/index_definition'
|
6
|
+
require_relative 'schema_plus_indexes/middleware/dumper'
|
7
|
+
require_relative 'schema_plus_indexes/middleware/migration'
|
8
|
+
require_relative 'schema_plus_indexes/middleware/model'
|
9
|
+
require_relative 'schema_plus_indexes/middleware/sqlite3'
|
10
|
+
|
11
|
+
SchemaMonkey.register(SchemaPlusIndexes)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module SchemaPlusIndexes
|
2
|
+
module ActiveRecord
|
3
|
+
|
4
|
+
module Base
|
5
|
+
def self.included(base) #:nodoc:
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods #:nodoc:
|
10
|
+
|
11
|
+
public
|
12
|
+
|
13
|
+
# Returns a list of IndexDefinition objects, for each index
|
14
|
+
# defind on this model's table.
|
15
|
+
#
|
16
|
+
def indexes
|
17
|
+
@indexes ||= connection.indexes(table_name, "#{name} Indexes")
|
18
|
+
end
|
19
|
+
|
20
|
+
# (reset_index_information gets called by by Middleware::Model::ResetColumnInformation)
|
21
|
+
def reset_index_information
|
22
|
+
@indexes = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SchemaPlusIndexes
|
2
|
+
module ActiveRecord
|
3
|
+
module ConnectionAdapters
|
4
|
+
module AbstractAdapter
|
5
|
+
|
6
|
+
def self.included(base) #:nodoc:
|
7
|
+
base.alias_method_chain :remove_index, :schema_plus_indexes
|
8
|
+
end
|
9
|
+
|
10
|
+
# Extends rails' remove_index to include this options:
|
11
|
+
# :if_exists
|
12
|
+
def remove_index_with_schema_plus_indexes(table_name, *args)
|
13
|
+
options = args.extract_options!
|
14
|
+
if_exists = options.delete(:if_exists)
|
15
|
+
args << options if options.any?
|
16
|
+
return if if_exists and not index_name_exists?(table_name, options[:name] || index_name(table_name, *args), false)
|
17
|
+
remove_index_without_schema_plus_indexes(table_name, *args)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module SchemaPlusIndexes
|
2
|
+
module ActiveRecord
|
3
|
+
module ConnectionAdapters
|
4
|
+
#
|
5
|
+
# SchemaPlusIndexes extends the IndexDefinition object to return information
|
6
|
+
# about partial indexes and case sensitivity (i.e. Postgresql
|
7
|
+
# support).
|
8
|
+
module IndexDefinition
|
9
|
+
def self.included(base) #:nodoc:
|
10
|
+
base.alias_method_chain :initialize, :schema_plus_indexes
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize_with_schema_plus_indexes(*args) #:nodoc:
|
14
|
+
# same args as add_index(table_name, column_names, options)
|
15
|
+
if args.length == 3 and Hash === args.last
|
16
|
+
table_name, column_names, options = args + [{}]
|
17
|
+
initialize_without_schema_plus_indexes(table_name, options[:name], options[:unique], column_names, options[:length], options[:orders], options[:where], options[:type], options[:using])
|
18
|
+
else # backwards compatibility
|
19
|
+
initialize_without_schema_plus_indexes(*args)
|
20
|
+
end
|
21
|
+
unless orders.blank?
|
22
|
+
# fill out orders with :asc when undefined. make sure hash ordering
|
23
|
+
# follows column ordering.
|
24
|
+
self.orders = Hash[columns.map{|column| [column, orders[column] || :asc]}]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# tests if the corresponding indexes would be the same
|
29
|
+
def ==(other)
|
30
|
+
return false if other.nil?
|
31
|
+
return false unless self.name == other.name
|
32
|
+
return false unless Array.wrap(self.columns).collect(&:to_s).sort == Array.wrap(other.columns).collect(&:to_s).sort
|
33
|
+
return false unless !!self.unique == !!other.unique
|
34
|
+
return false unless Array.wrap(self.lengths).compact.sort == Array.wrap(other.lengths).compact.sort
|
35
|
+
return false unless self.where == other.where
|
36
|
+
return false unless (self.using||:btree) == (other.using||:btree)
|
37
|
+
true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module SchemaPlusIndexes
|
2
|
+
module Middleware
|
3
|
+
module Dumper
|
4
|
+
|
5
|
+
def self.insert
|
6
|
+
SchemaMonkey::Middleware::Dumper::Table.append ColumnIndexes
|
7
|
+
end
|
8
|
+
|
9
|
+
class ColumnIndexes < SchemaMonkey::Middleware::Base
|
10
|
+
def call(env)
|
11
|
+
continue env
|
12
|
+
|
13
|
+
# move each column's index to its column, and remove them from the
|
14
|
+
# list of indexes that AR would dump after the table. Any left
|
15
|
+
# over will still be dumped by AR.
|
16
|
+
env.table.columns.each do |column|
|
17
|
+
|
18
|
+
# first check for a single-column index
|
19
|
+
if (index = env.table.indexes.find(&its.columns == [column.name]))
|
20
|
+
column.add_option column_index(env, column, index)
|
21
|
+
env.table.indexes.delete(index)
|
22
|
+
|
23
|
+
# then check for the first of a multi-column index
|
24
|
+
elsif (index = env.table.indexes.find(&its.columns.first == column.name))
|
25
|
+
column.add_option column_index(env, column, index)
|
26
|
+
env.table.indexes.delete(index)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
def column_index(env, column, index)
|
34
|
+
parts = []
|
35
|
+
parts << "name: #{index.name.inspect}"
|
36
|
+
parts << "with: #{(index.columns - [column.name]).inspect}" if index.columns.length > 1
|
37
|
+
parts << index.options unless index.options.blank?
|
38
|
+
"index: {#{parts.join(', ')}}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module SchemaPlusIndexes
|
2
|
+
module Middleware
|
3
|
+
module Migration
|
4
|
+
|
5
|
+
def self.insert
|
6
|
+
SchemaMonkey::Middleware::Migration::Column.prepend Shortcuts
|
7
|
+
SchemaMonkey::Middleware::Migration::Column.append IndexOnAddColumn
|
8
|
+
SchemaMonkey::Middleware::Migration::Index.prepend NormalizeArgs
|
9
|
+
SchemaMonkey::Middleware::Migration::Index.prepend IgnoreDuplicates
|
10
|
+
end
|
11
|
+
|
12
|
+
class Shortcuts < SchemaMonkey::Middleware::Base
|
13
|
+
def call(env)
|
14
|
+
case env.options[:index]
|
15
|
+
when true then env.options[:index] = {}
|
16
|
+
when :unique then env.options[:index] = { :unique => true }
|
17
|
+
end
|
18
|
+
continue env
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class IndexOnAddColumn < SchemaMonkey::Middleware::Base
|
23
|
+
def call(env)
|
24
|
+
continue env
|
25
|
+
return unless env.options[:index]
|
26
|
+
|
27
|
+
case env.operation
|
28
|
+
when :add, :record
|
29
|
+
env.caller.add_index(env.table_name, env.column_name, env.options[:index])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class NormalizeArgs < SchemaMonkey::Middleware::Base
|
35
|
+
def call(env)
|
36
|
+
[:length, :order].each do |key|
|
37
|
+
env.options[key].stringify_keys! if env.options[key].is_a? Hash
|
38
|
+
end
|
39
|
+
env.column_names = Array.wrap(env.column_names).map(&:to_s) + Array.wrap(env.options.delete(:with)).map(&:to_s)
|
40
|
+
continue env
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class IgnoreDuplicates < SchemaMonkey::Middleware::Base
|
45
|
+
# SchemaPlusIndexes modifies SchemaStatements::add_index so that it ignores
|
46
|
+
# errors raised about add an index that already exists -- i.e. that has
|
47
|
+
# the same index name, same columns, and same options -- and writes a
|
48
|
+
# warning to the log. Some combinations of rails & DB adapter versions
|
49
|
+
# would log such a warning, others would raise an error; with
|
50
|
+
# SchemaPlusIndexes all versions log the warning and do not raise the error.
|
51
|
+
def call(env)
|
52
|
+
continue env
|
53
|
+
rescue => e
|
54
|
+
raise unless e.message.match(/["']([^"']+)["'].*already exists/)
|
55
|
+
name = $1
|
56
|
+
existing = env.caller.indexes(env.table_name).find{|i| i.name == name}
|
57
|
+
attempted = ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(env.table_name, env.column_names, env.options.merge(:name => name))
|
58
|
+
raise if attempted != existing
|
59
|
+
::ActiveRecord::Base.logger.warn "[schema_plus_indexes] Index name #{name.inspect}' on table #{env.table_name.inspect} already exists. Skipping."
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|