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