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 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: []