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 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
@@ -0,0 +1,4 @@
1
+ # ActiveRecordSqlExporter
2
+ require 'active_record/sql_exporter'
3
+ ActiveRecord::Base.send( :include, ActiveRecord::SqlExporter::InstanceMethods )
4
+ ActiveRecord::Base.send( :extend, ActiveRecord::SqlExporter::ClassMethods )
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: []