arperftoolkit_base 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2006 Dr.-Ing. Stefan Kaes
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,109 @@
1
+ AR Perf Toolkit Base
2
+ ActiveRecord enhancements to optimize queries and insertion functionality.
3
+ Includes support for finder index hints, ignore and duplicate update,
4
+ insert select, record duplicate deletion, bulk insert, and eager loading of
5
+ selected columns.
6
+
7
+ This has only been tested with MySQL.
8
+
9
+
10
+ #########################################################################################
11
+ Single Active Record Operations
12
+ #########################################################################################
13
+ The following methods are overwritten to accept a hash map parameter of options
14
+
15
+ * save
16
+ * create
17
+ * create!
18
+ * save!
19
+ * create_or_update
20
+
21
+ Options:
22
+
23
+ * duplicate - On duplicate key update update old rows on duplicate
24
+ Example: updates fields birthdate and email_address_id if a duplicate exists when the contact is created or updated
25
+
26
+ contact.save :duplicate => [:birthdate, :email_address_id ]
27
+ Set the existing duplicate email address id to 5
28
+
29
+
30
+ contact.save :duplicate => 'email_address_id = 5'
31
+
32
+ * keyword(ignore) adds a keyword after mysql command (example INSERT IGNORE)
33
+ Example. Ignore a duplicate record.
34
+ contact.save :keywords => :ignore
35
+
36
+ command. The sql command to use. Example: uses replace insead of insert
37
+ contact.save :command => :replace
38
+
39
+
40
+
41
+ #########################################################################################
42
+ Bulk Insert Operations
43
+ #########################################################################################
44
+ * insert_all
45
+ * insert_select
46
+
47
+ insert_all(objs, options={})
48
+ *Note* use instead of former upsert.
49
+
50
+ Insert a group of records in one transaction.
51
+
52
+ objs can be:
53
+
54
+ * Array of hash maps {:column_name => :value} where column_names are specified in the options[:select]
55
+
56
+ EmailAddress.insert_all(
57
+ [{:text => 'blythe@spongecell.com', :name =>'Blythe Dunham'},
58
+ {:text => 'blythedunham@gmail.com', :name =>'Blythe Dunham'}],
59
+ {:select => [:text, :name] })
60
+
61
+ * Array of active records (slower)
62
+
63
+ upsert_taggings << Tagging.new(:tag_id=>tag_id, :taggable_type=>child.to_s, :taggable_id=>record.id)
64
+
65
+ Tagging.insert_all(upsert_taggings, :keywords => :ignore)
66
+
67
+ Options
68
+
69
+ * skip_validation does not run valid? on each object
70
+ * duplicate
71
+ * keywords
72
+ * post_sql - sql appended to the end (indexes, rollup, etc)
73
+ * pre_sql - sql appended to the beginning (ex, priorities)
74
+ * command - choose the columns you wish to insert
75
+
76
+ insert_select(select_obj, select_options, insert_options={})
77
+ Insert records with a select statement
78
+ add all the books written by the author into the cart
79
+ Cart.insert_select Book, {:select => ['books.id, ?', Time.now], :conditions => ['books.author_id = ?', 1 }, {:select => [:book_id, :added-on]}
80
+ generated sql
81
+ INSERT INTO carts (`book_id`, `added_on`) SELECT books.id, '2007-05-01 01:24:28' FROM books where books.author_id = 1
82
+
83
+
84
+ select_options
85
+
86
+ * select => select porition of query. like conditions (santizes sql)
87
+
88
+ insert_options - See options of insert_all
89
+
90
+ #########################################################################################
91
+ Finder Operations
92
+ #########################################################################################
93
+ * finder_sql_to_string - prints out the query to string
94
+ * find - enhanced to accept additional params for sql customization
95
+
96
+ finder_sql_to_string
97
+ Print out the sql that would be executed. Same signature as ActiveRecord::Base.find
98
+
99
+ >> Contact.finder_sql_to_string :piggy => :primary_email_address
100
+
101
+ => "SELECT contacts.*, email_addresses.crypted_text AS primary_email_address_crypted_text FROM contacts LEFT JOIN email_addresses ON email_addresses.id=contacts.email_address_id "
102
+
103
+ find
104
+
105
+ Adds these options:
106
+
107
+ * post_sql - sql appended to the end (indexes, rollup, etc)
108
+ * pre_sql - sql appended to the beginning (ex, priorities)
109
+ * keywords
@@ -0,0 +1,71 @@
1
+ require 'rake/testtask'
2
+ require 'rake/rdoctask'
3
+ require 'rake/gempackagetask'
4
+ require 'rake/contrib/sshpublisher'
5
+
6
+ PKG_NAME = "arperftoolkit_base"
7
+ PKG_VERSION = "0.0.1"
8
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
9
+ RUBY_FORGE_PROJECT = "eload_select"
10
+
11
+ spec = Gem::Specification.new do |s|
12
+ s.name = PKG_NAME
13
+ s.version = PKG_VERSION
14
+ s.platform = Gem::Platform::RUBY
15
+ s.summary = "ActiveRecord enhancements to optimize queries and insertion functionality. Includes support for finder index hints, ignore and duplicate update, insert select, record duplicate deletion, bulk insert, and eager loading of selected columns."
16
+ s.files = FileList["{lib,tasks}/**/*"].to_a + %w(init.rb LICENSE Rakefile README)
17
+ s.require_path = "lib"
18
+ s.autorequire = PKG_NAME
19
+ s.has_rdoc = true
20
+ s.test_files = nil
21
+ s.add_dependency "rails", ">= 1.2.0"
22
+
23
+ s.author = "Blythe Dunham"
24
+ s.email = "blythe@spongecell.com"
25
+ s.homepage = "http://spongetech.wordpress.com/"
26
+ end
27
+
28
+ desc 'Default: run unit tests.'
29
+ task :default => :test
30
+
31
+ desc 'Test the ar_test plugin.'
32
+ Rake::TestTask.new(:test) do |t|
33
+ t.libs << '.\lib'
34
+ t.pattern = 'test/**/*_test.rb'
35
+ t.verbose = true
36
+ end
37
+
38
+ desc 'Generate documentation for the ar_test plugin.'
39
+ Rake::RDocTask.new(:rdoc) do |rdoc|
40
+ rdoc.rdoc_dir = 'rdoc'
41
+ rdoc.title = 'ArP'
42
+ rdoc.options << '--line-numbers' << '--inline-source'
43
+ rdoc.rdoc_files.include('README')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
46
+
47
+ Rake::GemPackageTask.new(spec) do |p|
48
+ p.gem_spec = spec
49
+ p.need_tar = true
50
+ p.need_zip = true
51
+ end
52
+
53
+ desc "Publish the API docs and gem"
54
+ task :publish => [:pdoc, :release]
55
+
56
+ desc "Publish the release files to RubyForge."
57
+ task :release => [:gem, :package] do
58
+ require 'rubyforge'
59
+
60
+ options = {"cookie_jar" => RubyForge::COOKIE_F}
61
+ options["password"] = ENV["RUBY_FORGE_PASSWORD"] if ENV["RUBY_FORGE_PASSWORD"]
62
+ ruby_forge = RubyForge.new
63
+ ruby_forge.login
64
+
65
+ %w( gem tgz zip ).each do |ext|
66
+ file = "pkg/#{PKG_FILE_NAME}.#{ext}"
67
+ puts "Releasing #{File.basename(file)}..."
68
+
69
+ ruby_forge.add_release(RUBY_FORGE_PROJECT, PKG_NAME, PKG_VERSION, file)
70
+ end
71
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'ar_perf_toolkit'
@@ -0,0 +1,8 @@
1
+
2
+ require File.dirname(__FILE__) + '/base'
3
+ require File.dirname(__FILE__) + '/insert'
4
+ require File.dirname(__FILE__) + '/insert_bulk'
5
+
6
+ ActiveRecord::Base.send(:include, ArPerfToolkit::Base)
7
+ ActiveRecord::Base.send(:include, ArPerfToolkit::Insert)
8
+ ActiveRecord::Base.send(:include, ArPerfToolkit::InsertBulk)
@@ -0,0 +1,138 @@
1
+
2
+ # MONKEY MONKEY!!!! Move me to a better place and make me an optional param
3
+ #
4
+
5
+ module ArPerfToolkit
6
+ module Base
7
+ def self.included(base) #:nodoc:
8
+ base.extend(ClassMethods)
9
+ base.class_eval do
10
+ class << self
11
+ VALID_FIND_OPTIONS.concat([:pre_sql, :post_sql, :keywords])
12
+ alias_method :construct_finder_sql, :construct_finder_sql_x
13
+ alias_method_chain :construct_finder_sql_with_included_associations, :perf_options unless method_defined?(:construct_finder_sql_with_included_associations_with_options)
14
+ end
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+
20
+ # expose the finder sql to a string with the same options that
21
+ # the find method takes.
22
+ #
23
+ # sql = Contact.finder_sql_to_string(:include => :primary_email_address)
24
+ # Contact.find_by_sql(sql + 'USE_INDEX(blah)')
25
+ def finder_sql_to_string(options)
26
+ select_sql = self.send(((scoped?(:find, :include) || options[:include]) ?
27
+ :finder_sql_with_included_associations :
28
+ :construct_finder_sql), options)
29
+ end
30
+
31
+
32
+ #Construct keywords around sql
33
+ def construct_sql(options={}, valid_options = [], &block)
34
+ options.assert_valid_keys(:keywords, :command, :duplicate, :post_sql, :pre_sql, :select, *valid_options)
35
+ sql = ''
36
+ sql << (options[:pre_sql] + ' ') if options[:pre_sql]
37
+ sql << "#{options[:command]} "
38
+ add_keywords!(sql, options[:keywords]) if options[:keywords]
39
+ yield sql, options
40
+ add_duplicate!(sql, options[:duplicate]) if options[:duplicate]
41
+ sql << options[:post_sql] if options[:post_sql]
42
+ sql
43
+ end
44
+
45
+ def construct_finder_sql_x(options)
46
+ #add piggy back option if plugin is installed
47
+ add_piggy_back!(options) if self.respond_to? :add_piggy_back!
48
+ scope = scope(:find)
49
+ sql = "#{options[:pre_sql]} SELECT".strip
50
+ sql << " #{options[:keywords]}" if options[:keywords]
51
+ sql << " #{(scope && scope[:select]) || options[:select] || '*'} "
52
+ sql << "FROM #{(scope && scope[:from]) || options[:from] || table_name} "
53
+
54
+ add_joins!(sql, options, scope)
55
+ add_conditions!(sql, options[:conditions], scope)
56
+
57
+ sql << " GROUP BY #{options[:group]} " if options[:group]
58
+ sql << " ORDER BY #{options[:order]} " if options[:order]
59
+
60
+ add_limit!(sql, options, scope)
61
+ sql << "#{options[:post_sql]}" if options[:post_sql]
62
+ sql
63
+ end
64
+
65
+
66
+
67
+
68
+ #add a util method here until I find a better home for it.
69
+ #delete all the dups
70
+ #
71
+ #ex.
72
+ # Make all the phone numbers of contacts unique by deleting the
73
+ # >>Contacts.delete_duplicates(:fields=>['phone_number_id'])
74
+ #
75
+ # :fields -> the fields to match on
76
+ # :conditions-> specify this to do it yourself.
77
+ # :additional_clause -> append something to the query, tho you could just use scope
78
+ # :winner_clause-> the part of the query specifying what wins. Default is that with the greatest id.
79
+ # :query_field -> if you dont like id as the winner, specify another field
80
+ def delete_duplicates options={}
81
+
82
+ query = options[:conditions] || "delete from " + table_name+" c1 using "+table_name+" c1, " +table_name+" c2 where (" +
83
+ options[:fields].collect{|field| ('c1.'+field.to_s+'=c2.'+field.to_s) }.join(" and ") +
84
+ (options[:additional_clause] ? 'and ('+options[:additional_clause]+') ' : '')+') and '+
85
+ (options[:winner_clause] || ('c1.'+(qf=(options[:query_field] || 'id').to_s) + '> c2.'+qf))
86
+
87
+ self.connection.execute(self.sanitize_sql(query))
88
+ end
89
+
90
+ protected
91
+ #add keywords like IGNORE or DELAYED
92
+ def add_keywords!(sql, keywords)
93
+ sql << (keywords.kind_of?(Array) ? keywords.collect{|kw| " #{kw} "}.join('') : " #{keywords} ")
94
+ end
95
+
96
+ #duplicate can be expressed as a string: "field_name = 5"
97
+ #or an array of field names to update with new value
98
+ #[last_name, first_name] would update the dupe with the new last and first name
99
+ def add_duplicate!(sql, duplicate)
100
+ if duplicate
101
+ duplicate = [duplicate] if (duplicate.kind_of?(String) || duplicate.kind_of?(Symbol)) && !duplicate.to_s.include?('=')
102
+ sql << " ON DUPLICATE KEY UPDATE "
103
+ sql << (duplicate.kind_of?(Array) ? duplicate.collect{|val|
104
+ val = self.connection.quote_column_name val.to_s
105
+ "#{val}=VALUES(#{val})"
106
+ }.join(', ') : duplicate.to_s)
107
+ end
108
+ sql
109
+ end
110
+
111
+ #generate the finder sql for use with associations (:include => :something)
112
+ def finder_sql_with_included_associations(options = {})
113
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
114
+ sql = construct_finder_sql_with_included_associations(options, join_dependency)
115
+ end
116
+
117
+ def construct_finder_sql_with_included_associations_x options, join_dependency
118
+ construct_finder_sql_block(options) do
119
+ construct_finder_sql_with_included_associations(options, join_dependency)
120
+ end
121
+ end
122
+
123
+ def construct_finder_sql_with_included_associations_with_perf_options(options, join_dependency)
124
+ #use eager loading select if available
125
+ method = respond_to?(:construct_finder_sql_with_included_associations_with_eager_select) ? :construct_finder_sql_with_included_associations_with_eager_select : :construct_finder_sql_with_included_associations_without_perf_options
126
+ sql = send(method, options, join_dependency)
127
+
128
+ #substitute pre_sql and keywords
129
+ sql.gsub!(/^\s*SELECT/, "#{options[:pre_sql]} SELECT #{options[:keywords]}" ) if options[:pre_sql] || options[:keywords]
130
+
131
+ #substitue post sql
132
+ sql << " #{options[:post_sql]}" if options[:post_sql]
133
+ sql
134
+ end
135
+
136
+ end#ClassMethods
137
+ end
138
+ end
@@ -0,0 +1,170 @@
1
+
2
+ class Module
3
+ #support deprecated method_x commands
4
+ #alias method to method_without_arperf_options
5
+ #alias method_x to method
6
+ def alias_method_chain_arperf(methods)
7
+ methods = Array.new(methods)
8
+ methods.each do |target|
9
+ unless method_defined?("#{target}_without_arperf_options")
10
+ aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
11
+ alias_method "#{aliased_target}_without_arperf_options#{punctuation}", target
12
+ alias_method target, "#{aliased_target}_x#{punctuation}"
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ module ArPerfToolkit
19
+ module Insert
20
+ def self.included(base) #:nodoc:
21
+ base.extend(ClassMethods)
22
+
23
+ #alias methods
24
+ base.class_eval do
25
+ alias_method_chain_arperf(%w(save update save! create_or_update))
26
+ class << self
27
+ alias_method_chain_arperf(%w(create create!))
28
+ end
29
+ end
30
+ end
31
+
32
+ module ClassMethods
33
+ def create_ignore attributes = nil
34
+ create_x!(attributes, :keywords => 'IGNORE')
35
+ end
36
+
37
+ # Creates an object, instantly saves it as a record (if the validation permits it), and returns it. If the save
38
+ # fails under validations, the unsaved object is still returned.
39
+ def create_x(attributes = nil, options={})
40
+ if attributes.is_a?(Array)
41
+ attributes.collect { |attr| create(attr) }
42
+ else
43
+ object = new(attributes)
44
+ scope(:create).each { |att,value| object.send("#{att}=", value) } if scoped?(:create)
45
+ object.save_x options
46
+ object
47
+ end
48
+ end
49
+
50
+ # Creates an object just like Base.create but calls save! instead of save
51
+ # so an exception is raised if the record is invalid.
52
+ def create_x!(attributes = nil,options={})
53
+ if attributes.is_a?(Array)
54
+ attributes.collect { |attr| create!(attr) }
55
+ else
56
+ attributes.reverse_merge!(scope(:create)) if scoped?(:create)
57
+
58
+ object = new(attributes)
59
+ object.save_x!(options)
60
+ object
61
+ end
62
+ end
63
+
64
+ end#ClassMethods
65
+
66
+ def save_ignore
67
+ save_x :keywords => 'IGNORE'
68
+ end
69
+
70
+ def save_ignore!
71
+ save_ignore || raise(ActiveRecord::RecordNotSaved)
72
+ end
73
+
74
+ def save_x(options={})
75
+ #invoke save_with_validation if the argument is not a hash
76
+ return save_without_arperf_options(options) unless options.is_a?(Hash)
77
+
78
+ perform_validation = options.delete(:perform_validation)
79
+ if (perform_validation.is_a?(FalseClass)) || valid?
80
+ raise ActiveRecord::ReadOnlyRecord if readonly?
81
+ create_or_update_x options
82
+ else
83
+ raise ActiveRecord::RecordInvalid.new(self) if perform_validation == :exception
84
+ false
85
+ end
86
+ end
87
+
88
+ def save_x!(options={})
89
+ options[:perform_validation] = :exception
90
+ save_x(options) || raise(ActiveRecord::RecordNotSaved)
91
+ end
92
+
93
+ def create_or_update_x options={}
94
+ return false if callback(:before_save) == false
95
+ options[:keywords] = 'IGNORE' if options.delete(:ignore).kind_of?(TrueClass)
96
+ result =
97
+ if new_record? then
98
+ create_x(options)
99
+ else
100
+ options.delete(:duplicate)
101
+ update_x(options)
102
+ end
103
+ callback(:after_save)
104
+ result
105
+ end
106
+
107
+ #END _X STUFF. Remove this if we end up overwriting update and create directly
108
+
109
+ # Updates the associated record with values matching those of the instance attributes.
110
+ def update_x options={}
111
+ return false if callback(:before_update) == false
112
+ insert_with_timestamps(false)
113
+ options[:command]||='UPDATE'
114
+ raise(ArgumentError, "Unknown key: duplicate") if options[:duplicate]
115
+ sql = self.class.construct_sql(options) do |sql, options|
116
+ sql << "#{self.class.table_name} "
117
+ sql << "SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))} " +
118
+ "WHERE (#{self.class.primary_key} = #{quote_value(id)}) "
119
+ end
120
+ connection.update(sql, "#{self.class.name} UpdateX")
121
+ callback(:after_update)
122
+ return true
123
+ end
124
+
125
+ # Creates a new record with values matching those of the instance attributes.
126
+ def create_x options={}
127
+ return false if callback(:before_create) == false
128
+ insert_with_timestamps(true)
129
+
130
+ if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name)
131
+ self.id = connection.next_sequence_value(self.class.sequence_name)
132
+ end
133
+
134
+ options[:command]||='INSERT'
135
+ sql = self.class.construct_sql(options) do |sql, options|
136
+ sql << "INTO #{self.class.table_name} (#{quoted_column_names.join(', ')}) "
137
+ sql << "VALUES(#{attributes_with_quotes.values.join(', ')})"
138
+ end
139
+
140
+ self.id = connection.insert(sql, "#{self.class.name} CreateX ",
141
+ self.class.primary_key, self.id, self.class.sequence_name)
142
+
143
+ @new_record = false
144
+
145
+ callback(:after_create)
146
+ return self.id != 0
147
+ end
148
+
149
+ #Replace deletes the existing duplicate if it exists then adds
150
+ #this
151
+ def replace_x options={}
152
+ create_x(options.merge(:command => 'REPLACE'))
153
+ end
154
+
155
+ protected
156
+
157
+ #update timestamps
158
+ def insert_with_timestamps(bCreate=true)
159
+ if record_timestamps
160
+ t = ( self.class.default_timezone == :utc ? Time.now.utc : Time.now )
161
+ write_attribute('created_at', t) if bCreate && respond_to?(:created_at) && created_at.nil?
162
+ write_attribute('created_on', t) if bCreate && respond_to?(:created_on) && created_on.nil?
163
+
164
+ write_attribute('updated_at', t) if respond_to?(:updated_at)
165
+ write_attribute('updated_on', t) if respond_to?(:updated_on)
166
+ end
167
+ end
168
+
169
+ end
170
+ end
@@ -0,0 +1,131 @@
1
+
2
+ module ArPerfToolkit
3
+ module InsertBulk
4
+ def self.included(base) #:nodoc:
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+
10
+
11
+ #Insert records with a select statement
12
+ # add all the books written by the author into the cart
13
+ #Cart.insert_select Book, {:select => ['books.id, ?', Time.now], :conditions => ['books.author_id = ?', 1 }, {:select => [:book_id, :added-on]}
14
+ # generated sql
15
+ #INSERT INTO carts (`book_id`, `added_on`) SELECT books.id, '2007-05-01 01:24:28' FROM books where books.author_id = 1
16
+ def insert_select(select_obj, select_options, insert_options={})
17
+
18
+ #santize and clean up the select columns. Cannot do * here because we need the columns in
19
+ #a particular order
20
+ select_options[:select] = select_obj.send :select_columns, select_options[:select]
21
+
22
+ #build the sql
23
+ sql = construct_sql({:command => 'INSERT'}.merge(insert_options)) do |sql, options|
24
+ sql << " INTO #{table_name} "
25
+ sql << "(%s) " % select_columns(insert_options[:select])
26
+
27
+ sql << select_obj.send(:finder_sql_to_string, select_options)
28
+
29
+ end
30
+ connection.insert(sql, "#{name} Insert Select #{select_obj}")
31
+ end
32
+
33
+ #the raw sql for bulk insert sql
34
+ def insert_all_sql(objs, options={})
35
+ value_sql = options.delete(:value_sql)
36
+ sql = construct_sql({:command => 'INSERT'}.merge(options), [:skip_validation]) do |sql, options|
37
+ sql << " INTO #{table_name} "
38
+ sql << "(%s) " % select_columns(options[:select])
39
+ sql << "VALUES "
40
+ sql << (value_sql || insert_value_pairs(objs, options[:select], options[:skip_validation].kind_of?(TrueClass)))
41
+ end
42
+ end
43
+
44
+ #Insert a group of records
45
+ #insert_all, Insert a whole bunch of records in one transaction
46
+ # EmailAddress.insert_all([{:text => 'blythe@spongecell.com', :name =>'Blythe Dunham'},{:text => 'blythedunham@gmail.com', :name =>'Blythe Dunham'}], {:select => [:text, :name] })
47
+ # options
48
+ # <tt>:skip_validation</tt> => does not run valid? on each object
49
+ # <tt>:duplicate</tt>
50
+ # <tt>:keywords</tt>
51
+ # <tt>:post_sql</tt>
52
+ # <tt>:pre_sql</tt>
53
+ # <tt>:command</tt>
54
+ # <tt>:select => </tt>choose the columns you wish to insert
55
+
56
+ def insert_all(objs, options={})
57
+ connection.insert(insert_all_sql(objs, options), "#{name} Insert All")
58
+ end
59
+
60
+ def update_all_x(updates, conditions = nil, options={})
61
+ raise(ArgumentError, "Unknown key: duplicate") if options[:duplicate]
62
+
63
+ sql = construct_sql({:command => 'UPDATE'}.merge(options)) do |sql, options|
64
+ sql << "#{table_name} SET #{sanitize_sql(updates)} "
65
+ add_conditions!(sql, conditions, scope(:find))
66
+ end
67
+
68
+ connection.update(sql, "#{name} Update #{options.inspect}")
69
+ end
70
+
71
+ def update_all_ignore updates, conditions = nil, options={}
72
+ update_all_x updates, conditions, options.update(:keywords => 'IGNORE')
73
+ end
74
+
75
+ protected
76
+ #return a list of the column names quoted accordingly
77
+ # nil => All columns except primary key (auto update)
78
+ # String => Exact String
79
+ # Array
80
+ # needs sanitation ["?, ?", 5, 'test'] => "5, 'test'" or [":date", {:date => Date.today}] => "12-30-2006"]
81
+ # list of strings or symbols returns quoted values [:start, :name] => `start`, `name` or ['abc'] => `start`
82
+ def select_columns(field_list=nil)
83
+ if field_list.kind_of?(String)
84
+ field_list
85
+ elsif field_list.kind_of?(Array) && field_list.first.is_a?(String) && (field_list.last.is_a?(Hash) || field_list.first.include?('?'))
86
+ sanitize_sql(field_list)
87
+ else
88
+ field_list = field_list.blank? ? self.column_names - [self.primary_key] : [field_list].flatten
89
+ field_list.collect{|field| self.connection.quote_column_name(field.to_s) }.join(", ")
90
+ end
91
+ end
92
+
93
+ def quoted_list(list)
94
+ list.collect{|item| connection.quote(item)}.join(', ')
95
+ end
96
+
97
+ #return a comma delimited list of value pairs
98
+ # ex. (`p1_val_attrib_1`, `p1_val_attrib_2 ...), (`p2_val_attrib_1`, `p2_val_attrib_2 ...),
99
+ #objs can be either a hash map or an ActiveRecord derived object o442r an array of a hash map or ActiveRecord
100
+ def insert_value_pairs(objs, field_list, skip_validation=false)
101
+ raise Exception.new("No values to insert") if objs.blank?
102
+
103
+ if objs.is_a?(Array) && objs.first.is_a?(Array) && !field_list.blank?
104
+ objs.collect{|obj| "(#{quoted_list(obj)})" }
105
+ else
106
+ [objs].flatten.inject([]){|list, obj|
107
+ obj= self.new(obj) if (obj.kind_of?(Hash))
108
+ raise ActiveRecord::RecordInvalid.new(obj) if !skip_validation && !obj.valid?
109
+ list << "(%s)" % obj.send(:attributes_with_quotes_x, field_list)
110
+ }
111
+ end.join(", ")
112
+ end
113
+
114
+ end#ClassMethods
115
+ protected
116
+
117
+ #get all the attributes with quotes delimited by commas minus the ones in the included_attributes list
118
+ def attributes_with_quotes_x(included_attributes = nil)
119
+ insert_with_timestamps
120
+
121
+ included_attributes = self.class.column_names - [self.class.primary_key] if included_attributes.blank?
122
+
123
+ included_attributes.collect{ |column_name|
124
+ #don't call attributes() here, it is slow and this method gets called a lot
125
+ quote_value(self.send(:read_attribute,column_name), column_for_attribute(column_name))
126
+ }.join(", ")
127
+
128
+ end
129
+
130
+ end
131
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
4
+ name: arperftoolkit_base
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.1
7
+ date: 2007-08-15 00:00:00 -03:00
8
+ summary: ActiveRecord enhancements to optimize queries and insertion functionality. Includes support for finder index hints, ignore and duplicate update, insert select, record duplicate deletion, bulk insert, and eager loading of selected columns.
9
+ require_paths:
10
+ - lib
11
+ email: blythe@spongecell.com
12
+ homepage: http://spongetech.wordpress.com/
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: arperftoolkit_base
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Blythe Dunham
31
+ files:
32
+ - lib/ar_perf_toolkit.rb
33
+ - lib/base.rb
34
+ - lib/insert.rb
35
+ - lib/insert_bulk.rb
36
+ - init.rb
37
+ - LICENSE
38
+ - Rakefile
39
+ - README
40
+ test_files: []
41
+
42
+ rdoc_options: []
43
+
44
+ extra_rdoc_files: []
45
+
46
+ executables: []
47
+
48
+ extensions: []
49
+
50
+ requirements: []
51
+
52
+ dependencies:
53
+ - !ruby/object:Gem::Dependency
54
+ name: rails
55
+ version_requirement:
56
+ version_requirements: !ruby/object:Gem::Version::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 1.2.0
61
+ version: