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 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
@@ -0,0 +1,8 @@
1
+ /coverage
2
+ /tmp
3
+ /pkg
4
+
5
+ *.lock
6
+ *.log
7
+ *.sqlite3
8
+ !gemfiles/**/*.sqlite3
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
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
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,9 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'schema_dev/tasks'
5
+
6
+ task :default => :spec
7
+
8
+ require 'rspec/core/rake_task'
9
+ RSpec::Core::RakeTask.new(:spec)
@@ -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,3 @@
1
+ eval File.read File.expand_path('../../Gemfile.base', __FILE__)
2
+
3
+ gem "rails", "~> 4.2.0"
@@ -0,0 +1,10 @@
1
+ require "pathname"
2
+ eval(Pathname.new(__FILE__).dirname.join("Gemfile.base").read, binding)
3
+
4
+ platform :ruby do
5
+ gem "mysql2"
6
+ end
7
+
8
+ platform :jruby do
9
+ gem 'activerecord-jdbcmysql-adapter'
10
+ end
@@ -0,0 +1,10 @@
1
+ require "pathname"
2
+ eval(Pathname.new(__FILE__).dirname.join("Gemfile.base").read, binding)
3
+
4
+ platform :ruby do
5
+ gem "pg"
6
+ end
7
+
8
+ platform :jruby do
9
+ gem 'activerecord-jdbcpostgresql-adapter'
10
+ end
@@ -0,0 +1,10 @@
1
+ require "pathname"
2
+ eval(Pathname.new(__FILE__).dirname.join("Gemfile.base").read, binding)
3
+
4
+ platform :ruby do
5
+ gem "sqlite3"
6
+ end
7
+
8
+ platform :jruby do
9
+ gem 'activerecord-jdbcsqlite3-adapter', '>=1.3.0.beta2'
10
+ end
@@ -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