table_renamable 0.0.1
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/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +40 -0
- data/app/assets/javascripts/table_renamable/application.js +15 -0
- data/app/assets/stylesheets/table_renamable/application.css +13 -0
- data/app/controllers/table_renamable/application_controller.rb +4 -0
- data/app/helpers/table_renamable/application_helper.rb +4 -0
- data/app/views/layouts/table_renamable/application.html.erb +14 -0
- data/config/routes.rb +2 -0
- data/lib/table_renamable/connection_adapters/mysql2_adapter.rb +31 -0
- data/lib/table_renamable/connection_adapters/sqlite3_adapter.rb +62 -0
- data/lib/table_renamable/connection_adapters.rb +13 -0
- data/lib/table_renamable/deprecated_table.rb +106 -0
- data/lib/table_renamable/engine.rb +22 -0
- data/lib/table_renamable/model.rb +95 -0
- data/lib/table_renamable/version.rb +3 -0
- data/lib/table_renamable.rb +11 -0
- data/lib/tasks/table_renamable_tasks.rake +4 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/post.rb +3 -0
- data/spec/dummy/app/models/user.rb +5 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config/application.rb +65 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +11 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/test.log +1158 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/lib/table_renamable/model_spec.rb +82 -0
- data/spec/spec_helper.rb +40 -0
- metadata +157 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 295d31b7d7ae975d082ac5d807465cd1eafad278
|
4
|
+
data.tar.gz: 1af6841d11a014733e54832144587c531a795013
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 83340587d906f332996104c16470cf33f59c41cceeea0297956ba371901e459565132335a23406abf245ac4e554bbdc1f5b232ad4678f95efb9a264001d5215f
|
7
|
+
data.tar.gz: 6e5bc7a75d85aef2fcb27f2d5e0cff43f9fca58ae2bc07fb9209d8ad2bcb7417e3ae015594c1e808e2a9caf2c0637f4eb43d8beeae3932bb6dbf87e3fc2db54d
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2013 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'TableRenamable'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
24
|
+
load 'rails/tasks/engine.rake'
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
Bundler::GemHelper.install_tasks
|
29
|
+
|
30
|
+
require 'rake/testtask'
|
31
|
+
|
32
|
+
Rake::TestTask.new(:test) do |t|
|
33
|
+
t.libs << 'lib'
|
34
|
+
t.libs << 'test'
|
35
|
+
t.pattern = 'test/**/*_test.rb'
|
36
|
+
t.verbose = false
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
task :default => :test
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// the compiled file.
|
9
|
+
//
|
10
|
+
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
|
11
|
+
// GO AFTER THE REQUIRES BELOW.
|
12
|
+
//
|
13
|
+
//= require jquery
|
14
|
+
//= require jquery_ujs
|
15
|
+
//= require_tree .
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
9
|
+
* compiled file, but it's generally better to create a new file per style scope.
|
10
|
+
*
|
11
|
+
*= require_self
|
12
|
+
*= require_tree .
|
13
|
+
*/
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>TableRenamable</title>
|
5
|
+
<%= stylesheet_link_tag "table_renamable/application", :media => "all" %>
|
6
|
+
<%= javascript_include_tag "table_renamable/application" %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module TableRenamable
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Mysql2Adapter
|
4
|
+
|
5
|
+
#
|
6
|
+
# Override execute to reload database info
|
7
|
+
# @param *args [Array<Mixed>] Just here so we can call super
|
8
|
+
#
|
9
|
+
# @return [type] [description]
|
10
|
+
def execute(sql, name = nil)
|
11
|
+
# set up tries so we don't keep retrying
|
12
|
+
tries = 0
|
13
|
+
begin
|
14
|
+
tries += 1
|
15
|
+
# call the actual execut behavior
|
16
|
+
super(sql, name)
|
17
|
+
rescue ActiveRecord::StatementInvalid => e
|
18
|
+
# only try once
|
19
|
+
raise e if tries > 1
|
20
|
+
# re-raise if it's not an error we care about
|
21
|
+
raise e unless e.message =~ /Table.*doesn't exist/
|
22
|
+
# otherwise we reload and retry
|
23
|
+
TableRenamable::Model.reload_tables
|
24
|
+
sql = TableRenamable::Model.process_sql(sql)
|
25
|
+
retry
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module TableRenamable
|
2
|
+
module ConnectionAdapters
|
3
|
+
module SQLite3Adapter
|
4
|
+
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# when we are included we add our behavior
|
8
|
+
self.included do |klass|
|
9
|
+
klass.alias_method_chain(:exec_query, :table_renamable)
|
10
|
+
klass.alias_method_chain(:execute, :table_renamable)
|
11
|
+
klass.alias_method_chain(:table_structure, :table_renamable)
|
12
|
+
end
|
13
|
+
|
14
|
+
def exec_query_with_table_renamable(*args, &block)
|
15
|
+
self.with_retry do
|
16
|
+
self.exec_query_without_table_renamable(*args, &block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Override execute to reload database info
|
22
|
+
# @param *args [Array<Mixed>] Just here so we can call super
|
23
|
+
#
|
24
|
+
# @return [type] [description]
|
25
|
+
def execute_with_table_renamable(*args, &block)
|
26
|
+
self.with_retry do
|
27
|
+
self.execute_without_table_renamable(*args, &block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def table_structure_with_table_renamable(table_name)
|
32
|
+
self.with_retry do
|
33
|
+
# get the correct table name to check - otherwise this will fail
|
34
|
+
# on retry
|
35
|
+
current_table_name = TableRenamable::Model.get_current_table_name(
|
36
|
+
table_name
|
37
|
+
)
|
38
|
+
self.table_structure_without_table_renamable(current_table_name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def with_retry(&block)
|
44
|
+
# set up tries so we don't keep retrying
|
45
|
+
tries = 0
|
46
|
+
begin
|
47
|
+
tries += 1
|
48
|
+
# call the actual execute behavior
|
49
|
+
yield
|
50
|
+
rescue ActiveRecord::StatementInvalid => e
|
51
|
+
# only try once
|
52
|
+
raise e if tries > 1
|
53
|
+
# re-raise if it's not an error we care about
|
54
|
+
raise e unless e.message =~ /Could not find table/
|
55
|
+
# otherwise we reload and retry
|
56
|
+
TableRenamable::Model.reload_tables
|
57
|
+
retry
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module TableRenamable
|
2
|
+
#
|
3
|
+
# Class that handles the renaming of tables. This sets the table name
|
4
|
+
# of the class based on whether or not the table exists in the database
|
5
|
+
#
|
6
|
+
# @author [dlangevin]
|
7
|
+
#
|
8
|
+
class DeprecatedTable
|
9
|
+
|
10
|
+
class NoTableError < Exception; end;
|
11
|
+
|
12
|
+
# @!attribute klass
|
13
|
+
# @return [Class] The class whose table we are renaming
|
14
|
+
attr_reader :klass
|
15
|
+
|
16
|
+
# @!attribute old_name
|
17
|
+
# @return [Symbol] The old table name
|
18
|
+
attr_reader :old_name
|
19
|
+
|
20
|
+
# @!attribute new_name
|
21
|
+
# @return [Symbol] The new table name
|
22
|
+
attr_reader :new_name
|
23
|
+
|
24
|
+
#
|
25
|
+
# Constructor - sets up the record and tries to connect to
|
26
|
+
# the correct database
|
27
|
+
#
|
28
|
+
# @param klass [Class] Class whose table we are renaming
|
29
|
+
# @param old_name [String, Symbol] The old table name
|
30
|
+
# @param new_name [String, Symbol] The new table name
|
31
|
+
#
|
32
|
+
def initialize(klass, old_name, new_name)
|
33
|
+
@klass = klass
|
34
|
+
@old_name = old_name
|
35
|
+
@new_name = new_name
|
36
|
+
# call to set the correct table name
|
37
|
+
self.set_table_name
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Returns the name of the table that currently exists of our
|
42
|
+
# two options (old or new)
|
43
|
+
#
|
44
|
+
# @return [String] The name of the existing table
|
45
|
+
def get_current_table_name
|
46
|
+
[self.old_name, self.new_name].each do |name|
|
47
|
+
return name.to_s if self.table_exists?(name)
|
48
|
+
end
|
49
|
+
# raise exception if we don't have a valid table
|
50
|
+
self.raise_no_table_error
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Is this table name part of our DeprecatedTable definition
|
55
|
+
# @param table_name [String, Symbol] Table name to chck
|
56
|
+
#
|
57
|
+
# @return [Boolean] Whether or not we have it in our definition
|
58
|
+
def has_table?(table_name)
|
59
|
+
[self.old_name, self.new_name].include?(table_name.to_sym)
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# Set the correct table name for the Class we are controlling
|
64
|
+
#
|
65
|
+
# @raise [TableRenamable::NoTableError] Error if neither name works
|
66
|
+
#
|
67
|
+
# @return [Boolean] True if we set the table name
|
68
|
+
def set_table_name
|
69
|
+
[self.old_name, self.new_name].each do |name|
|
70
|
+
# make sure this table exists
|
71
|
+
if self.table_exists?(name)
|
72
|
+
# return true if we are already using this table
|
73
|
+
return true if self.klass.table_name == name.to_s
|
74
|
+
# otherwise we can change the table name
|
75
|
+
self.klass.table_name = name
|
76
|
+
return true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
self.raise_no_table_error
|
80
|
+
end
|
81
|
+
|
82
|
+
protected
|
83
|
+
|
84
|
+
#
|
85
|
+
# Wrapper to raise an error
|
86
|
+
#
|
87
|
+
# @raise [DeprecatedTable::NoTableError]
|
88
|
+
def raise_no_table_error
|
89
|
+
raise NoTableError.new(
|
90
|
+
"No table for #{self.klass}. " +
|
91
|
+
"Tried #{self.old_name} and #{self.new_name}"
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
# Does a given table exist?
|
97
|
+
# @param name [String, Symbol] Table name
|
98
|
+
#
|
99
|
+
# @return [Boolean] Whether or not it exists
|
100
|
+
def table_exists?(name)
|
101
|
+
self.klass.connection.tables.include?(name.to_s)
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module TableRenamable
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace TableRenamable
|
4
|
+
|
5
|
+
config.after_initialize do
|
6
|
+
# set up our reload behavior for when table names change for MySQL
|
7
|
+
if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
|
8
|
+
ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(
|
9
|
+
:include,
|
10
|
+
TableRenamable::ConnectionAdapters::Mysql2Adapter
|
11
|
+
)
|
12
|
+
# same thing for SQLite
|
13
|
+
elsif defined?(ActiveRecord::ConnectionAdapters::SQLite3Adapter)
|
14
|
+
ActiveRecord::ConnectionAdapters::SQLite3Adapter.send(
|
15
|
+
:include,
|
16
|
+
TableRenamable::ConnectionAdapters::SQLite3Adapter
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module TableRenamable
|
2
|
+
#
|
3
|
+
# Model that is included in ActiveRecord to enable our behavior
|
4
|
+
#
|
5
|
+
# @author [dlangevin]
|
6
|
+
#
|
7
|
+
module Model
|
8
|
+
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
class NoTableError < Exception; end;
|
12
|
+
|
13
|
+
|
14
|
+
included do |klass|
|
15
|
+
klass.class_attribute :deprecated_columns
|
16
|
+
klass.deprecated_columns = []
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
#
|
21
|
+
# Return our list of deprecated tables
|
22
|
+
#
|
23
|
+
# @return [Array<DeprecatedTable>] Our list of deprecated tables
|
24
|
+
def self.deprecated_tables
|
25
|
+
@deprecated_tables ||= []
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# Update a string of SQL to replace deprecated tables
|
30
|
+
# @param sql [String] SQL TO update
|
31
|
+
#
|
32
|
+
# @return [String] Updated SQL
|
33
|
+
def self.process_sql(sql)
|
34
|
+
self.deprecated_tables.each do |deprecated_table|
|
35
|
+
# our current table name
|
36
|
+
current_table_name = deprecated_table.get_current_table_name
|
37
|
+
old_table_name = deprecated_table.old_name
|
38
|
+
sql = sql.gsub(/#{old_table_name}/, current_table_name.to_s)
|
39
|
+
end
|
40
|
+
sql
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Reload our table names so we pick up any changes
|
45
|
+
#
|
46
|
+
# @return [Boolean] Always true
|
47
|
+
def self.reload_tables
|
48
|
+
self.deprecated_tables.each(&:set_table_name)
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# ClassMethods to be mixed in to the model using this behavior
|
54
|
+
#
|
55
|
+
# @author [dlangevin]
|
56
|
+
#
|
57
|
+
module ClassMethods
|
58
|
+
|
59
|
+
#
|
60
|
+
# Overrides columns to remove deprecated columns
|
61
|
+
#
|
62
|
+
# @return [Array<>] Filtered array of columns
|
63
|
+
def columns
|
64
|
+
super.reject { |column|
|
65
|
+
self.deprecated_columns.include?(column.name)
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Mark columns as deprecated
|
71
|
+
# @param *column_names [Array<String, Symbol>] Column names
|
72
|
+
#
|
73
|
+
# @return [Array<String>] List of deprecated columns
|
74
|
+
def deprecate_columns(*column_names)
|
75
|
+
self.deprecated_columns =
|
76
|
+
self.deprecated_columns + Array.wrap(column_names).collect(&:to_s)
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# Mark this class as having a potentially new table name
|
81
|
+
# @param old_name [String, Symbol] The old table name
|
82
|
+
# @param new_name [String, Symbol] The new table name
|
83
|
+
#
|
84
|
+
# @return [TableRenamable::DeprecatedTable] The newly
|
85
|
+
# created TableRenamable::DeprecatedTable records
|
86
|
+
def deprecate_table_name(old_name, new_name)
|
87
|
+
deprecated_table = DeprecatedTable.new(self, old_name, new_name)
|
88
|
+
TableRenamable::Model.deprecated_tables << deprecated_table
|
89
|
+
deprecated_table
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|