ar-extensions 0.5.1

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.
@@ -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