ar-extensions 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,59 @@
1
+ ActiveRecord::Schema.define do
2
+
3
+ create_table :schema_info, :force=>true do |t|
4
+ t.column :version, :integer, :unique=>true
5
+ end
6
+ SchemaInfo.create :version=>SchemaInfo::VERSION
7
+
8
+ create_table :topics, :force=>true do |t|
9
+ t.column :title, :string, :null=>false
10
+ t.column :author_name, :string
11
+ t.column :author_email_address, :string
12
+ t.column :written_on, :datetime
13
+ t.column :bonus_time, :time
14
+ t.column :last_read, :datetime
15
+ t.column :content, :text
16
+ t.column :approved, :boolean, :default=>'1'
17
+ t.column :replies_count, :integer
18
+ t.column :parent_id, :integer
19
+ t.column :type, :string
20
+ end
21
+
22
+ create_table :projects, :force=>true do |t|
23
+ t.column :name, :string
24
+ t.column :type, :string
25
+ end
26
+
27
+ create_table :developers, :force=>true do |t|
28
+ t.column :name, :string
29
+ t.column :salary, :integer, :default=>'70000'
30
+ t.column :created_at, :datetime
31
+ t.column :team_id, :integer
32
+ t.column :updated_at, :datetime
33
+ end
34
+
35
+ create_table :addresses, :force=>true do |t|
36
+ t.column :address, :string
37
+ t.column :city, :string
38
+ t.column :state, :string
39
+ t.column :zip, :string
40
+ t.column :developer_id, :integer
41
+ end
42
+
43
+ create_table :teams, :force=>true do |t|
44
+ t.column :name, :string
45
+ end
46
+
47
+ create_table :books, :force=>true do |t|
48
+ t.column :title, :string, :null=>false
49
+ t.column :publisher, :string, :null=>false
50
+ t.column :author_name, :string, :null=>false
51
+ t.column :created_at, :time
52
+ end
53
+
54
+ create_table :languages, :force=>true do |t|
55
+ t.column :name, :string
56
+ t.column :developer_id, :integer
57
+ end
58
+
59
+ end
@@ -0,0 +1,26 @@
1
+ ActiveRecord::Schema.define do
2
+
3
+ create_table :test_myisam, :options=>'ENGINE=MyISAM', :force=>true do |t|
4
+ t.column :my_name, :string, :null=>false
5
+ t.column :description, :string
6
+ end
7
+
8
+ create_table :test_innodb, :options=>'ENGINE=InnoDb', :force=>true do |t|
9
+ t.column :my_name, :string, :null=>false
10
+ t.column :description, :string
11
+ end
12
+
13
+ create_table :test_memory, :options=>'ENGINE=Memory', :force=>true do |t|
14
+ t.column :my_name, :string, :null=>false
15
+ t.column :description, :string
16
+ end
17
+
18
+ create_table :books, :options=>'ENGINE=MyISAM', :force=>true do |t|
19
+ t.column :title, :string, :null=>false
20
+ t.column :publisher, :string, :null=>false
21
+ t.column :author_name, :string, :null=>false
22
+ t.column :created_at, :datetime
23
+ end
24
+ execute "ALTER TABLE books ADD FULLTEXT( `title`, `publisher`, `author_name` )"
25
+
26
+ end
@@ -0,0 +1,4 @@
1
+ class SchemaInfo < ActiveRecord::Base
2
+ set_table_name 'schema_info'
3
+ VERSION = 5
4
+ end
data/init.rb ADDED
@@ -0,0 +1,32 @@
1
+ require 'ostruct'
2
+ begin ; require 'active_record' ; rescue LoadError; require 'rubygems'; require 'active_record'; end
3
+
4
+ dir = File.dirname( __FILE__ )
5
+ require File.expand_path( File.join( dir, 'lib/ar-extensions', 'version' ) )
6
+ require File.expand_path( File.join( dir, 'lib/ar-extensions', 'extensions' ) )
7
+
8
+ begin
9
+ require 'faster_csv'
10
+ require File.expand_path( File.join( dir, 'lib/ar-extensions', 'csv' ) )
11
+ rescue LoadError
12
+ STDERR.puts "FasterCSV is not installed. CSV functionality will not be included."
13
+ end
14
+
15
+ require File.expand_path( File.join( dir, 'lib/ar-extensions', 'foreign_keys' ) )
16
+ require File.expand_path( File.join( dir, 'lib/ar-extensions', 'fulltext' ) )
17
+ require File.expand_path( File.join( dir, 'lib/ar-extensions', 'fulltext', 'mysql' ) )
18
+
19
+ db_adapters_path = File.expand_path( File.join( dir, 'lib/ar-extensions', 'adapters' ) )
20
+
21
+ require File.expand_path( File.join( dir, 'lib/ar-extensions', 'import' ) )
22
+ require File.expand_path( File.join( dir, 'lib/ar-extensions', 'import', 'mysql' ) )
23
+ require File.expand_path( File.join( dir, 'lib/ar-extensions', 'import', 'postgresql' ) )
24
+
25
+ require File.expand_path( File.join( dir, 'lib/ar-extensions', 'finders' ) )
26
+
27
+ require File.expand_path( File.join( dir, 'lib/ar-extensions', 'temporary_table' ) )
28
+ require File.expand_path( File.join( dir, 'lib/ar-extensions', 'temporary_table', 'mysql' ) )
29
+
30
+ require File.expand_path( File.join( db_adapters_path, 'abstract_adapter' ) )
31
+ require File.expand_path( File.join( db_adapters_path,'mysql_adapter' ) )
32
+
@@ -0,0 +1,4 @@
1
+ begin ; require 'rubygems' rescue LoadError; end
2
+ require 'active_record' # ActiveRecord loads the Benchmark library automatically
3
+
4
+ require File.expand_path(File.join( File.dirname( __FILE__ ), '..', 'init.rb' ))
@@ -0,0 +1,83 @@
1
+ module ActiveRecord # :nodoc:
2
+ module ConnectionAdapters # :nodoc:
3
+ class AbstractAdapter # :nodoc:
4
+ NO_MAX_PACKET = 0
5
+
6
+ # +sql+ can be a single string or an array. If it is an array all
7
+ # elements that are in position >= 1 will be appended to the final SQL.
8
+ def insert_many( sql, values, *args ) # :nodoc:
9
+ # the number of inserts default
10
+ number_of_inserts = 0
11
+
12
+ base_sql,post_sql = if sql.is_a?( String )
13
+ [ sql, '' ]
14
+ elsif sql.is_a?( Array )
15
+ [ sql.shift, sql.join( ' ' ) ]
16
+ end
17
+
18
+ sql_size = base_sql.size + post_sql.size
19
+
20
+ # the number of bytes the requested insert statement values will take up
21
+ values_in_bytes = self.class.sum_sizes( *values )
22
+
23
+ # the number of bytes (commas) it will take to comma separate our values
24
+ comma_separated_bytes = values.size-1
25
+
26
+ # the total number of bytes required if this statement is one statement
27
+ total_bytes = sql_size + values_in_bytes + comma_separated_bytes
28
+
29
+ max = max_allowed_packet
30
+
31
+ # if we can insert it all as one statement
32
+ if NO_MAX_PACKET == max or total_bytes < max
33
+ number_of_inserts += 1
34
+ sql2insert = base_sql + values.join( ',' ) + post_sql
35
+ insert( sql2insert, *args )
36
+ else
37
+ value_sets = self.class.get_insert_value_sets( values, sql_size, max )
38
+ value_sets.each do |values|
39
+ number_of_inserts += 1
40
+ sql2insert = base_sql + values.join( ',' ) + post_sql
41
+ insert( sql2insert, *args )
42
+ end
43
+ end
44
+
45
+ number_of_inserts
46
+ end
47
+
48
+ # Returns the sum of the sizes of the passed in objects. This should
49
+ # probably be moved outside this class, but to where?
50
+ def self.sum_sizes( *objects ) # :nodoc:
51
+ objects.inject( 0 ){|sum,o| sum += o.size }
52
+ end
53
+
54
+ # Returns the maximum number of bytes that the server will allow
55
+ # in a single packet
56
+ def max_allowed_packet
57
+ NO_MAX_PACKET
58
+ end
59
+
60
+ def self.get_insert_value_sets( values, sql_size, max_bytes ) # :nodoc:
61
+ value_sets = []
62
+ arr, current_arr_values_size, current_size = [], 0, 0
63
+ values.each_with_index do |val,i|
64
+ comma_bytes = arr.size
65
+ sql_size_thus_far = sql_size + current_size + val.size + comma_bytes
66
+ if NO_MAX_PACKET == max_bytes or sql_size_thus_far <= max_bytes
67
+ current_size += val.size
68
+ arr << val
69
+ else
70
+ value_sets << arr
71
+ arr = [ val ]
72
+ current_size = val.size
73
+ end
74
+
75
+ # if we're on the last iteration push whatever we have in arr to value_sets
76
+ value_sets << arr if i == (values.size-1)
77
+ end
78
+ [ *value_sets ]
79
+ end
80
+
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,14 @@
1
+ module ActiveRecord # :nodoc:
2
+ module ConnectionAdapters # :nodoc:
3
+ class MysqlAdapter # :nodoc:
4
+
5
+ # Returns the maximum number of bytes that the server will allow
6
+ # in a single packet
7
+ def max_allowed_packet # :nodoc:
8
+ result = execute( "SHOW VARIABLES like 'max_allowed_packet';" )
9
+ result.fetch_row[1].to_i
10
+ end
11
+
12
+ end #end MysqlAdapter
13
+ end #end ConnectionAdapters
14
+ end #end ActiveRecord
@@ -0,0 +1,7 @@
1
+ module ActiveRecord # :nodoc:
2
+ module ConnectionAdapters # :nodoc:
3
+ class PostgreSQLAdapter # :nodoc:
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,302 @@
1
+
2
+ require 'faster_csv'
3
+ require 'ostruct'
4
+
5
+ # Adds CSV export options to ActiveRecord::Base models.
6
+ #
7
+ # === Example 1, exporting all fields
8
+ # class Book < ActiveRecord::Base ; end
9
+ #
10
+ # book = Book.find( 1 )
11
+ # book.to_csv
12
+ #
13
+ # === Example 2, only exporting certain fields
14
+ # class Book < ActiveRecord::Base ; end
15
+ #
16
+ # book = Book.find( 1 )
17
+ # book.to_csv( :only=>%W( title isbn )
18
+ #
19
+ # === Example 3, exporting a model including a belongs_to association
20
+ # class Book < ActiveRecord::Base
21
+ # belongs_to :author
22
+ # end
23
+ #
24
+ # book = Book.find( 1 )
25
+ # book.to_csv( :include=>:author )
26
+ #
27
+ # This also works for a has_one relationship. The :include
28
+ # option can also be an array of has_one/belongs_to
29
+ # associations. This by default includes all fields
30
+ # on the belongs_to association.
31
+ #
32
+ # === Example 4, exporting a model including a has_many association
33
+ # class Book < ActiveRecord::Base
34
+ # has_many :tags
35
+ # end
36
+ #
37
+ # book = Book.find( 1 )
38
+ # book.to_csv( :include=>:tags )
39
+ #
40
+ # This by default includes all fields on the has_many assocaition.
41
+ # This can also be an array of multiple has_many relationships. The
42
+ # array can be mixed with has_one/belongs_to associations array
43
+ # as well. IE: :include=>[ :author, :sales ]
44
+ #
45
+ # === Example 5, nesting associations
46
+ # class Book < ActiveRecord::Base
47
+ # belongs_to :author
48
+ # has_many :tags
49
+ # end
50
+ #
51
+ # book = Book.find( 1 )
52
+ # book.to_csv( :includes=>{
53
+ # :author => { :only=>%W( name ) },
54
+ # :tags => { :only=>%W( tagname ) } )
55
+ #
56
+ # Each included association can receive an options Hash. This
57
+ # allows you to nest the associations as deep as you want
58
+ # for your CSV export.
59
+ #
60
+ # It is not recommended to nest multiple has_many associations,
61
+ # although nesting multiple has_one/belongs_to associations.
62
+ #
63
+ module ActiveRecord::Extensions::FindToCSV
64
+ ALIAS_FOR_FIND = :_original_find_before_arext
65
+
66
+ def self.included( cl ) # :nodoc:
67
+ virtual_class = class << cl ; self ; end
68
+ if not virtual_class.ancestors.include?( self::ClassMethods )
69
+ cl.instance_eval "alias #{ALIAS_FOR_FIND} :find"
70
+ cl.extend( ClassMethods )
71
+ cl.send( :include, InstanceMethods )
72
+ end
73
+ end
74
+
75
+ class FieldMap# :nodoc:
76
+ attr_reader :fields, :fields_to_headers
77
+
78
+ def initialize( fields, fields_to_headers ) # :nodoc:
79
+ @fields, @fields_to_headers = fields, fields_to_headers
80
+ end
81
+
82
+ def headers # :nodoc:
83
+ @headers ||= fields.inject( [] ){ |arr,field| arr << fields_to_headers[ field ] }
84
+ end
85
+
86
+ end
87
+
88
+ module ClassMethods # :nodoc:
89
+ private
90
+
91
+ def to_csv_fields_for_nil # :nodoc:
92
+ self.columns.map{ |column| column.name }.sort
93
+ end
94
+
95
+ def to_csv_headers_for_included_associations( includes ) # :nodoc:
96
+ get_class = proc { |str| Object.const_get( self.reflections[ str.to_sym ].class_name ) }
97
+
98
+ case includes
99
+ when Symbol
100
+ [ get_class.call( includes ).to_csv_headers( :headers=>true, :naming=>":model[:header]" ) ]
101
+ when Array
102
+ includes.map do |association|
103
+ clazz = get_class.call( association )
104
+ clazz.to_csv_headers( :headers=>true, :naming=>":model[:header]" )
105
+ end
106
+ when Hash
107
+ includes.sort_by{ |k| k.to_s }.inject( [] ) do |arr,(association,options)|
108
+ clazz = get_class.call( association )
109
+ if options[:headers].is_a?( Hash )
110
+ options.merge!( :naming=>":header" )
111
+ else
112
+ options.merge!( :naming=>":model[:header]" )
113
+ end
114
+ arr << clazz.to_csv_headers( options )
115
+ end
116
+ else
117
+ []
118
+ end
119
+ end
120
+
121
+ public
122
+
123
+ def find( *args ) # :nodoc:
124
+ results = self.send( ALIAS_FOR_FIND, *args )
125
+ results.extend( ArrayInstanceMethods ) if results.is_a?( Array )
126
+ results
127
+ end
128
+
129
+ def to_csv_fields( options={} ) # :nodoc:
130
+ fields_to_headers, fields = {}, []
131
+
132
+ headers = options[:headers]
133
+ case headers
134
+ when Array
135
+ fields = headers.map{ |e| e.to_s }
136
+ when Hash
137
+ headers = headers.inject( {} ){ |hsh,(k,v)| hsh[k.to_s] = v ; hsh }
138
+ fields = headers.keys.sort
139
+ fields.each { |field| fields_to_headers[field] = headers[field] }
140
+ else
141
+ fields = to_csv_fields_for_nil
142
+ end
143
+
144
+ if options[:only]
145
+ specified_fields = options[:only].map{ |e| e.to_s }
146
+ fields.delete_if{ |field| not specified_fields.include?( field ) }
147
+ elsif options[:except]
148
+ excluded_fields = options[:except].map{ |e| e.to_s }
149
+ fields.delete_if{ |field| excluded_fields.include?( field ) }
150
+ end
151
+
152
+ fields.each{ |field| fields_to_headers[field] = field } if fields_to_headers.empty?
153
+
154
+ FieldMap.new( fields, fields_to_headers )
155
+ end
156
+
157
+ # Returns an array of CSV headers passed in the array of +options+.
158
+ def to_csv_headers( options={} )
159
+ options = { :headers=>true, :naming=>":header" }.merge( options )
160
+ return nil if not options[:headers]
161
+
162
+ fieldmap = to_csv_fields( options )
163
+ headers = fieldmap.headers
164
+ headers.push( *to_csv_headers_for_included_associations( options[ :include ] ).flatten )
165
+ headers.map{ |header| options[:naming].gsub( /:header/, header ).gsub( /:model/, self.name.downcase ) }
166
+ end
167
+
168
+ end
169
+
170
+
171
+ module InstanceMethods
172
+
173
+ private
174
+
175
+ def to_csv_data_for_included_associations( includes ) # :nodoc:
176
+ get_class = proc { |str| Object.const_get( self.class.reflections[ str.to_sym ].class_name ) }
177
+
178
+ case includes
179
+ when Symbol
180
+ association = self.send( includes )
181
+ association.send( :extend, ArrayInstanceMethods ) if association.is_a?( Array )
182
+ if association.nil? or (association.respond_to?( :empty? ) and association.empty?)
183
+ [ get_class.call( includes ).columns.map{ '' } ]
184
+ else
185
+ [ *association.to_csv_data ]
186
+ end
187
+ when Array
188
+ siblings = []
189
+ includes.each do |association_name|
190
+ association = self.send( association_name )
191
+ association.send( :extend, ArrayInstanceMethods ) if association.is_a?( Array )
192
+ if association.nil? or (association.respond_to?( :empty? ) and association.empty?)
193
+ association_data = [ get_class.call( association_name ).columns.map{ '' } ]
194
+ else
195
+ association_data = association.to_csv_data
196
+ end
197
+
198
+ if siblings.empty?
199
+ siblings.push( *association_data )
200
+ else
201
+ temp = []
202
+ association_data.each do |assoc_csv|
203
+ siblings.each do |sibling|
204
+ temp.push( sibling + assoc_csv )
205
+ end
206
+ end
207
+ siblings = temp
208
+ end
209
+ end
210
+ siblings
211
+ when Hash
212
+ sorted_includes = includes.sort_by{ |k| k.to_s }
213
+ siblings = []
214
+ sorted_includes.each do |(association_name,options)|
215
+ association = self.send( association_name )
216
+ association.send( :extend, ArrayInstanceMethods ) if association.is_a?( Array )
217
+ if association.nil? or (association.respond_to?( :empty ) and association.empty?)
218
+ association_data = [ get_class.call( association_name ).columns.map{ '' } ]
219
+ else
220
+ association_data = association.to_csv_data( options )
221
+ end
222
+
223
+ if siblings.empty?
224
+ siblings.push( *association_data )
225
+ else
226
+ temp = []
227
+ association_data.each do |assoc_csv|
228
+ siblings.each do |sibling|
229
+ temp.push( sibling + assoc_csv )
230
+ end
231
+ end
232
+ siblings = temp
233
+ end
234
+ end
235
+ siblings
236
+ else
237
+ []
238
+ end
239
+ end
240
+
241
+ public
242
+
243
+ # Returns CSV data without any header rows for the passed in +options+.
244
+ def to_csv_data( options={} )
245
+ fields = self.class.to_csv_fields( options ).fields
246
+ data, model_data = [], fields.inject( [] ) { |arr,field| arr << attributes[field].to_s }
247
+ if options[:include]
248
+ to_csv_data_for_included_associations( options[:include ] ).map do |assoc_csv_data|
249
+ data << model_data + assoc_csv_data
250
+ end
251
+ else
252
+ data << model_data
253
+ end
254
+ data
255
+ end
256
+
257
+ # Returns CSV data including header rows for the passed in +options+.
258
+ def to_csv( options={} )
259
+ FasterCSV.generate do |csv|
260
+ headers = self.class.to_csv_headers( options )
261
+ csv << headers if headers
262
+ to_csv_data( options ).each{ |data| csv << data }
263
+ end
264
+ end
265
+
266
+ end
267
+
268
+ module ArrayInstanceMethods # :nodoc:
269
+ class NoRecordsError < StandardError ; end #:nodoc:
270
+
271
+ # Returns CSV headers for an array of ActiveRecord::Base
272
+ # model objects by calling to_csv_headers on the first
273
+ # element.
274
+ def to_csv_headers( options={} )
275
+ first.class.to_csv_headers( options )
276
+ end
277
+
278
+ # Returns CSV data without headers for an array of
279
+ # ActiveRecord::Base model objects by iterating over them and
280
+ # calling to_csv_data with the passed in +options+.
281
+ def to_csv_data( options={} )
282
+ inject( [] ) do |arr,model_instance|
283
+ arr.push( *model_instance.to_csv_data( options ) )
284
+ end
285
+ end
286
+
287
+ # Returns CSV data with headers for an array of ActiveRecord::Base
288
+ # model objects by iterating over them and calling to_csv with
289
+ # the passed in +options+.
290
+ def to_csv( options={} )
291
+ FasterCSV.generate do |csv|
292
+ headers = to_csv_headers( options )
293
+ csv << headers if headers
294
+ each do |model_instance|
295
+ model_instance.to_csv_data( options ).each{ |data| csv << data }
296
+ end
297
+ end
298
+ end
299
+
300
+ end
301
+
302
+ end