active_record_sql_exporter 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README +42 -0
- data/lib/active_record/sql_exporter.rb +238 -0
- data/lib/active_record_sql_exporter.rb +4 -0
- metadata +63 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3010d854876fc641281a9a5a18e061fbadf581f4
|
4
|
+
data.tar.gz: 9ce358b90dcc53660bbf44d619f1805be0672bde
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9047c67b2d656acc37b0df892f513bb4b81e0fc4b0608ea180314474ea0dbcf8a97466ddce19284aeb68c0d494caeaa14b74e90370c3618f117fd0cc708bc9ba
|
7
|
+
data.tar.gz: d1c85cf635ebedb84ad2a1de7d59a0994888cbc7cfc5ca90dd70fe6393c7d13f9cc334932569c83242f480ea1c28dc500cc5f44ee5fb2dcb854f466a788062b5
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 [name of plugin creator]
|
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
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
ActiveRecordSqlExporter
|
2
|
+
=======================
|
3
|
+
|
4
|
+
This plugin takes a record in your database and converts it into SQL statements
|
5
|
+
that can be used to insert it.
|
6
|
+
|
7
|
+
This was written after I had to restore some specific data for a user (a number
|
8
|
+
of rows, across a number of tables.)
|
9
|
+
|
10
|
+
It might be useful as a simple backup utility - for example, store the restoration
|
11
|
+
sql after something gets removed.
|
12
|
+
|
13
|
+
This moves down through relations along has_many, has_one relations, but will
|
14
|
+
not move up along belongs_to relations.
|
15
|
+
|
16
|
+
However, this doesn't make you any less likely to screw up and shoot yourself
|
17
|
+
in the proverbial memory usage foot with a massive swath of data, so often
|
18
|
+
you'll want to use the second argument to to_sql, which is an array of classes
|
19
|
+
to ignore.
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
Example
|
24
|
+
=======
|
25
|
+
|
26
|
+
Obj.find( obj_id ).to_sql
|
27
|
+
|
28
|
+
For a blog_post, that belongs to a user, that might have many posts:
|
29
|
+
Post.find( id ).to_sql( {}, [User] )
|
30
|
+
|
31
|
+
Export an object to a file:
|
32
|
+
Post.find( id ).to_sql( {:file => a_file_object} )
|
33
|
+
|
34
|
+
Copyright (c) 2010 Adam Palmblad (adam.palmblad@teampages.com), released under the MIT license
|
35
|
+
|
36
|
+
|
37
|
+
To Do
|
38
|
+
=====
|
39
|
+
* Watch dependent => nullify and write restoration query
|
40
|
+
* The generation the SQL that checks for the presence of a value is broken!
|
41
|
+
* Add ability to toggle sql options, such as the ON DUPLICATE KEY stuff
|
42
|
+
* Support for non MySQL databases
|
@@ -0,0 +1,238 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module ActiveRecord::SqlExporter
|
3
|
+
class NestedException < ArgumentError
|
4
|
+
attr_accessor :old_exception, :klass
|
5
|
+
def initialize( old_exception, klass, key )
|
6
|
+
@old_exception = old_exception
|
7
|
+
@klass = klass
|
8
|
+
@key = key
|
9
|
+
end
|
10
|
+
|
11
|
+
# --------------------------------------------------------------------- to_s
|
12
|
+
def to_s
|
13
|
+
"Unexpected error on #{@klass}.#{@key} / #{@old_exception.to_s}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
# ------------------------------------------------------------------ included?
|
17
|
+
def included?( klass )
|
18
|
+
klass.include( InstanceMethods )
|
19
|
+
klass.extend( ClassMethods )
|
20
|
+
end
|
21
|
+
##############################################################################
|
22
|
+
# FileWriter
|
23
|
+
##############################################################################
|
24
|
+
class FileWriter
|
25
|
+
# --------------------------------------------------------------- initialize
|
26
|
+
def initialize( file )
|
27
|
+
@file = file
|
28
|
+
end
|
29
|
+
# ------------------------------------------------------------------------ +
|
30
|
+
def +( s )
|
31
|
+
@file.write( s )
|
32
|
+
return self
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module ClassMethods
|
37
|
+
# ---------------------------------------------------------- build_check_sql
|
38
|
+
def build_check_sql( id )
|
39
|
+
"IF( NOT EXISTS( SELECT * FROM #{quoted_table_name} WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(id)} ) THEN ROLLBACK; END IF;\n"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module InstanceMethods
|
44
|
+
CREATION_NODE = 1
|
45
|
+
EXISTENCE_CHECK_NODE = 2
|
46
|
+
UPDATE_NODE = 3
|
47
|
+
# --------------------- pretend_to_sql( args = {}, classes_to_ignore = [] )
|
48
|
+
def print_relation_tree( args = {}, classes_to_ignore = [] )
|
49
|
+
tree = {}
|
50
|
+
_print_relation( tree, classes_to_ignore )
|
51
|
+
return if classes_to_ignore.include?( self.class )
|
52
|
+
end
|
53
|
+
# ------------------------------------------------------------------- to_sql
|
54
|
+
def to_backup_sql( args = {}, classes_to_ignore = [] )
|
55
|
+
tree = {}
|
56
|
+
build_export_tree( tree, classes_to_ignore )
|
57
|
+
sql = args[:file] ? ActiveRecord::SqlExporter::FileWriter.new( args[:file] ) : ''
|
58
|
+
unless args[:no_transaction]
|
59
|
+
sql += "BEGIN;"
|
60
|
+
end
|
61
|
+
tree.keys.each do |klass|
|
62
|
+
tree[klass].keys.each do |id|
|
63
|
+
node = tree[klass][id]
|
64
|
+
begin
|
65
|
+
if node[:type] == EXISTENCE_CHECK_NODE
|
66
|
+
sql += klass.constantize.build_check_sql( id )
|
67
|
+
elsif node[:type] == CREATION_NODE
|
68
|
+
object = klass.constantize.find( id )
|
69
|
+
sql += object.sql_restore_string
|
70
|
+
elsif node[:type] == UPDATE_NODE
|
71
|
+
object = klass.constantize.find( id )
|
72
|
+
sql += object.update_sql_string( node[:key] )
|
73
|
+
end
|
74
|
+
rescue Encoding::UndefinedConversionError
|
75
|
+
raise "Error with encoding of #{object.inspect}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
sql += "COMMIT;" unless args[:no_transaction]
|
80
|
+
return sql
|
81
|
+
end
|
82
|
+
################################################################################
|
83
|
+
protected
|
84
|
+
################################################################################
|
85
|
+
# -------------------------------------------------------- update_sql_string
|
86
|
+
def update_sql_string( key_name )
|
87
|
+
data = []
|
88
|
+
self.class.columns.map do |x|
|
89
|
+
next if x.primary
|
90
|
+
data << "#{connection.quote_column_name( x.name )}=#{connection.quote( read_attribute(x.name))}"
|
91
|
+
end
|
92
|
+
"UPDATE #{self.class.quoted_table_name} SET #{data.join(',')} WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quote_value(id)};\n"
|
93
|
+
end
|
94
|
+
# ------------------------------------------------------- sql_restore_string
|
95
|
+
def sql_restore_string( args = {} )
|
96
|
+
columns = self.class.columns.map do |x|
|
97
|
+
connection.quote_column_name( x.name )
|
98
|
+
end
|
99
|
+
values = self.class.columns.map do |x|
|
100
|
+
connection.quote( read_attribute( x.name ) )
|
101
|
+
end
|
102
|
+
|
103
|
+
sql = "\nINSERT INTO #{self.class.quoted_table_name} (#{columns.join(',')}) VALUES (#{values.join(',')})"
|
104
|
+
unless args[:no_update]
|
105
|
+
pairs = []
|
106
|
+
columns.each_with_index do |c,i|
|
107
|
+
pairs << "#{c}=#{values[i]}"
|
108
|
+
end
|
109
|
+
sql += " ON DUPLICATE KEY UPDATE #{pairs.join(',')}"
|
110
|
+
end
|
111
|
+
sql += ";\n"
|
112
|
+
return sql
|
113
|
+
end
|
114
|
+
# -------------------------------------------------------- build_export_tree
|
115
|
+
def build_export_tree( tree = {}, classes_to_ignore = [] )
|
116
|
+
return if classes_to_ignore.include?( self.class )
|
117
|
+
if tree[self.class.name].nil? || ( tree[self.class.name] && ( tree[self.class.name][id].nil? || tree[self.class.name][id][:type] == EXISTENCE_CHECK_NODE ) )
|
118
|
+
self.add_to_tree( tree, CREATION_NODE )
|
119
|
+
expand_tree_with_relations( tree, self.class.reflections, classes_to_ignore )
|
120
|
+
end
|
121
|
+
return tree
|
122
|
+
end
|
123
|
+
# ------------------------------------------------------------- add_to_tree
|
124
|
+
def add_to_tree( tree, type, options = {} )
|
125
|
+
tree[self.class.name] ||= {}
|
126
|
+
node = tree[self.class.name][self.id]
|
127
|
+
if node.nil? || node[:type] == EXISTENCE_CHECK_NODE
|
128
|
+
options[:type] = type
|
129
|
+
tree[self.class.name][self.id] = options
|
130
|
+
end
|
131
|
+
return tree
|
132
|
+
end
|
133
|
+
# ---------------------------------------------------------- build_check_sql
|
134
|
+
def build_check_sql
|
135
|
+
"IF( NOT EXISTS( SELECT * FROM #{self.class.quoted_table_name} WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quote_value(id)} ) THEN ROLLBACK; END IF;\n"
|
136
|
+
end
|
137
|
+
# ---------------------------------------- convert_has_many_relations_to_sql
|
138
|
+
def expand_tree_with_relations( tree, reflections, classes_to_ignore )
|
139
|
+
reflections.each_pair do |key, value|
|
140
|
+
next if value.options[:dependent] && ![:destroy, :nullify].include?( value.options[:dependent] )
|
141
|
+
if value.options[:polymorphic]
|
142
|
+
next if classes_to_ignore.include?( send( key ).class )
|
143
|
+
else
|
144
|
+
begin
|
145
|
+
next if classes_to_ignore.include?( value.klass )
|
146
|
+
rescue
|
147
|
+
raise "Problem in a #{self.class.name} with #{key} = #{value}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
case value.macro
|
151
|
+
when :has_one
|
152
|
+
begin
|
153
|
+
singleton_method( key ) do |e|
|
154
|
+
e.build_export_tree( tree, classes_to_ignore )
|
155
|
+
end
|
156
|
+
rescue NestedException => ex
|
157
|
+
raise NestedException.new( ex.old_exception, "#{self.class.name}.#{ex.klass}", key )
|
158
|
+
rescue Exception => ex
|
159
|
+
raise NestedException.new( ex, self.class.name, key )
|
160
|
+
end
|
161
|
+
when :has_many, :has_and_belongs_to_many
|
162
|
+
begin
|
163
|
+
records = send( key )
|
164
|
+
if value.options[:dependent] == :nullify
|
165
|
+
records.each do |record|
|
166
|
+
record.add_to_tree( tree, UPDATE_NODE, key: value.association_primary_key )
|
167
|
+
end
|
168
|
+
else
|
169
|
+
records.each{ |x| x.build_export_tree( tree, classes_to_ignore ) }
|
170
|
+
end
|
171
|
+
rescue NestedException => ex
|
172
|
+
raise NestedException.new( ex.old_exception, "#{self.class.name}.#{ex.klass}", key )
|
173
|
+
rescue Exception => ex
|
174
|
+
raise ex
|
175
|
+
raise NestedException.new( ex, self.class.name, key )
|
176
|
+
end
|
177
|
+
when :belongs_to
|
178
|
+
singleton_method( key ) do |e|
|
179
|
+
e.add_to_tree( tree, EXISTENCE_CHECK_NODE )
|
180
|
+
end
|
181
|
+
else
|
182
|
+
raise "Unhandled reflection: #{value.macro}"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
return tree
|
186
|
+
end
|
187
|
+
# ----------------------------------------------------------- print_relation
|
188
|
+
def _print_relation( tree, classes_to_ignore, indent_depth = 0 )
|
189
|
+
if tree[self.class.name].nil? || ( tree[self.class.name] && ( tree[self.class.name][id].nil? || tree[self.class.name][id][:type] == EXISTENCE_CHECK_NODE ) )
|
190
|
+
puts "%s%s - %d" % ["\t" * indent_depth, self.class.name, self.id]
|
191
|
+
self.add_to_tree( tree, CREATION_NODE )
|
192
|
+
_print_reflection_relations( tree, self.class.reflections, classes_to_ignore, indent_depth + 1 )
|
193
|
+
end
|
194
|
+
end
|
195
|
+
# ---------------------------------------- convert_has_many_relations_to_sql
|
196
|
+
def _print_reflection_relations( tree, reflections, classes_to_ignore, indent_level = 1 )
|
197
|
+
reflections.each_pair do |key, value|
|
198
|
+
next if value.options[:dependent] && ![:destroy, :nullify].include?( value.options[:dependent] )
|
199
|
+
if value.options[:polymorphic]
|
200
|
+
next if classes_to_ignore.include?( send( key ).class )
|
201
|
+
else
|
202
|
+
begin
|
203
|
+
next if classes_to_ignore.include?( value.klass )
|
204
|
+
rescue
|
205
|
+
raise "Problem in a #{self.class.name} with #{key} = #{value}"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
case value.macro
|
209
|
+
when :has_one
|
210
|
+
singleton_method( key ) do |e|
|
211
|
+
e._print_relation( tree, classes_to_ignore, indent_level )
|
212
|
+
end
|
213
|
+
when :has_many, :has_and_belongs_to_many
|
214
|
+
records = send( key )
|
215
|
+
if value.options[:dependent] == :nullify
|
216
|
+
records.each do |record|
|
217
|
+
record.add_to_tree( tree, UPDATE_NODE, key: value.primary_key_name )
|
218
|
+
puts "%s%s [UPDATE] - %d" % ["\t" * indent_level, record.class.name, record.id]
|
219
|
+
end
|
220
|
+
else
|
221
|
+
records.each do |x|
|
222
|
+
x._print_relation( tree, classes_to_ignore, indent_level )
|
223
|
+
end
|
224
|
+
end
|
225
|
+
when :belongs_to
|
226
|
+
else
|
227
|
+
raise "Unhandled reflection: #{value.macro}"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
# --------------------------------------------------------- singleton_method
|
232
|
+
def singleton_method( key )
|
233
|
+
if v = self.send( key )
|
234
|
+
yield v
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_record_sql_exporter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Palmblad
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "<"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "<"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4'
|
27
|
+
description: Quickly export an active record object to raw sql, useful for restoring
|
28
|
+
partial data from a backup
|
29
|
+
email:
|
30
|
+
- Adam Palmblad
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- MIT-LICENSE
|
36
|
+
- README
|
37
|
+
- lib/active_record/sql_exporter.rb
|
38
|
+
- lib/active_record_sql_exporter.rb
|
39
|
+
homepage: http://github.com/apalmblad/active_record_sql_exporter
|
40
|
+
licenses:
|
41
|
+
- MIT
|
42
|
+
metadata: {}
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 1.3.6
|
57
|
+
requirements: []
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 2.2.1
|
60
|
+
signing_key:
|
61
|
+
specification_version: 4
|
62
|
+
summary: Export active record objects to raw sql.
|
63
|
+
test_files: []
|