active_record_sql_exporter 0.2.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/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: []
|