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
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
|
+
[](http://badge.fury.io/rb/schema_plus_indexes)
|
2
|
+
[](http://travis-ci.org/SchemaPlus/schema_plus_indexes)
|
3
|
+
[](https://coveralls.io/r/SchemaPlus/schema_plus_indexes)
|
4
|
+
[](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
|