Class: ActiveRecord::Base

Inherits:
Object
  • Object
show all
Extended by:
Extensions::ForeignKeys, Extensions::SqlGeneration
Includes:
Extensions::SqlGeneration
Defined in:
lib/ar-extensions/synchronize.rb,
lib/ar-extensions/fulltext.rb,
lib/ar-extensions/insert_select.rb,
lib/ar-extensions/temporary_table.rb,
lib/ar-extensions/finders.rb,
lib/ar-extensions/import.rb,
lib/ar-extensions/delete.rb,
lib/ar-extensions/union.rb

Overview

:nodoc:

Defined Under Namespace

Classes: FullTextSearchingNotSupported

Constant Summary collapse

AREXT_RAILS_COLUMNS =
{
  :create => { "created_on" => tproc ,
               "created_at" => tproc },
  :update => { "updated_on" => tproc ,
               "updated_at" => tproc }
}
AREXT_RAILS_COLUMN_NAMES =
AREXT_RAILS_COLUMNS[:create].keys + AREXT_RAILS_COLUMNS[:update].keys

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Extensions::ForeignKeys

foreign_keys

Class Method Details

.count_union(column_name, *args) ⇒ Object

Count across a union of two or more queries

Args

  • column_name - The column to count. Defaults to all (‘*’)

  • *args - Each additional argument is a hash map of options used by :find :all

including :conditions, :join, :group, :having, and :limit

In addition the following options are accepted

  • :pre_sql inserts SQL before the SELECT statement of this protion of the union

  • :post_sql appends additional SQL to the end of the statement

  • :override_select is used to override the SELECT clause of eager loaded associations

Note that distinct is implied so a record that matches more than one portion of the union is counted only once.

Global Options

To specify global options that apply to the entire union, specify a hash as the first parameter with a key :union_options. Valid options include :group, :having, :order, and :limit

Examples

Count the number of people who live in Seattle and San Francisco

Contact.count_union(:phone_number_id,
      {:conditions => ['zip_id = ?, 94010]'},
      {:conditions => ['zip_id = ?', 98102]})
SQL> select count(*) from ((select phone_number_id from contacts ...) UNION (select phone_number_id from contacts ...)) as counter_tbl;


77
78
79
80
81
# File 'lib/ar-extensions/union.rb', line 77

def count_union(column_name, *args)
  supports_union!
  count_val = calculate_union(:count, column_name, *args)
  (args.length == 1 && args.first[:limit] && args.first[:limit].to_i < count_val) ? args.first[:limit].to_i : count_val
end

.create_temporary_table(opts = {}) ⇒ Object

Creates a temporary table given the passed in options hash. The temporary table is created based off from another table the current model class. This method returns the constant for the new new model. This can also be used with block form (see below).

Parameters

  • options - the options hash used to define the temporary table.

Options

:table_name::the desired name of the temporary table. If not supplied

then a name of "temp_" + the current table_name of the current model 
will be used.
:like

the table model you want to base the temporary tables

structure off from. If this is not supplied then the table_name of the 
current model will be used.
:model_name

the name of the model you want to use for the temporary

table. This must be compliant with Ruby's naming conventions for 
constants. If this is not supplied a rails-generated table name will 
be created which is based off from the table_name of the temporary table. 
IE: Account.create_temporary_table creates the TempAccount model class

Example 1, using defaults

class Project < ActiveRecord::Base
end

> t = Project.create_temporary_table
> t.class
=> "TempProject"
> t.superclass
=> Project

This creates a temporary table named ‘temp_projects’ and creates a constant name TempProject. The table structure is copied from the ‘projects’ table. TempProject is a subclass of Project as you would expect.

Example 2, using :table_name and :model options

Project.create_temporary_table :table_name => 'my_projects', :model => 'MyProject'

This creates a temporary table named ‘my_projects’ and creates a constant named MyProject. The table structure is copied from the ‘projects’ table.

Example 3, using :like

ActiveRecord::Base.create_temporary_table :like => Project

This is the same as calling Project.create_temporary_table.

Example 4, using block form

Project.create_temporary_table do |t|
  # ...
end

Using the block form will automatically drop the temporary table when the block exits. t which is passed into the block is the temporary table class. In the above example t equals TempProject. The block form can be used with all of the available options.

See

  • drop



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/ar-extensions/temporary_table.rb', line 81

def self.create_temporary_table(opts={})
  opts[:temporary]  ||= !opts[:permanent]
  opts[:like]       ||= self
  opts[:table_name] ||= "temp_#{self.table_name}"
  opts[:model_name] ||= ActiveSupport::Inflector.classify(opts[:table_name])

  if Object.const_defined?(opts[:model_name])
    raise Exception, "Model #{opts[:model_name]} already exists!"
  end

  like_table_name = opts[:like].table_name || self.table_name

  connection.execute <<-SQL
    CREATE #{opts[:temporary] ? 'TEMPORARY' : ''} TABLE #{opts[:table_name]}
      LIKE #{like_table_name}
  SQL
  
  # Sample evaluation:
  #
  #   class ::TempFood < Food
  #     set_table_name :temp_food
  #
  #     def self.drop
  #       connection.execute "DROP TABLE temp_foo"
  #       Object.send(:remove_const, self.name.to_sym)
  #     end
  #   end
  class_eval(<<-RUBY, __FILE__, __LINE__)
    class ::#{opts[:model_name]} < #{self.name}
      set_table_name :#{opts[:table_name]}

      def self.drop
        connection.execute "DROP TABLE #{opts[:table_name]};"
        Object.send(:remove_const, self.name.to_sym)
      end
    end
  RUBY

  model = Object.const_get(opts[:model_name])

  if block_given?
    begin
      yield(model)
    ensure
      model.drop
    end
  else
    return model
  end
end

.delete_all_with_extension(conditions = nil, options = {}) ⇒ Object

Delete all specified records with options

Parameters

  • conditions - the conditions normally specified to delete_all

  • options - hash map of additional parameters

Options

  • :limit - the maximum number of records to delete.

  • :batch - delete in batches specified to avoid database contention

Multiple sql deletions are executed in order to avoid database contention This has no affect if used inside a transaction

Delete up to 65 red tags

Tag.delete_all ['name like ?', '%red%'], :limit => 65

Delete up to 65 red tags in batches of 20. This will execute up to 4 delete statements: 3 batches of 20 and the final batch of 5.

Tag.delete_all ['name like ?', '%red%'], :limit => 65, :batch => 20


35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/ar-extensions/delete.rb', line 35

def delete_all_with_extension(conditions = nil, options={})

  #raise an error if delete is not supported and options are specified
  supports_delete! if options.any?

  #call the base method if no options specified
  return delete_all_without_extension(conditions) unless options.any?

  #batch delete
  return delete_all_batch(conditions, options[:batch], options[:limit]) if options[:batch]

  #regular delete with limit
  connection.delete(delete_all_extension_sql(conditions, options), "#{name} Delete All")
end

.delete_duplicates(options = {}) ⇒ Object

Utility function to delete all but one of the duplicate records matching the fields specified. This method will make the records unique for the specified fields.

Options

  • :fields - the fields to match on

  • :conditions - additional conditions

  • :winner_clause - the part of the query specifying what wins. Default winner is that with the greatest id.

  • :query_field -> the field to use to determine the winner. Defaults to primary_key (id). The tables are aliased

to c1 and c2 respectively

Examples

Make all the phone numbers of contacts unique by deleting the duplicates with the highest ids

Contacts.delete_duplicates(:fields=>['phone_number_id'])

Delete all tags that are the same preserving the ones with the highest id

Tag.delete_duplicates :fields => [:name], :winner_clause => "c1.id < c2.id"

Remove duplicate invitations (those that from the same person and to the same recipient) preseving the first ones inserted

Invitation.delete_duplicates :fields=>[:event_id, :from_id, :recipient_id]


73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/ar-extensions/delete.rb', line 73

def delete_duplicates(options={})
  supports_delete!

  options[:query_field]||= primary_key

  query = "DELETE FROM"
  query << " c1 USING #{quoted_table_name} c1, #{quoted_table_name} c2"
  query << " WHERE ("
  query << options[:fields].collect{|field| "c1.#{field} = c2.#{field}" }.join(" and ")
  query << " and (#{sanitize_sql(options[:conditions])})" unless options[:conditions].blank?
  query << " and "
  query << (options[:winner_clause]||"c1.#{options[:query_field]} > c2.#{options[:query_field]}")
  query << ")"

  self.connection.execute(self.send(:sanitize_sql, query))
end

.find_union(*args) ⇒ Object

Find a union of two or more queries

Args

Each argument is a hash map of options sent to :find :all including :conditions, :join, :group, :having, and :limit

In addition the following options are accepted

  • :pre_sql inserts SQL before the SELECT statement of this protion of the union

  • :post_sql appends additional SQL to the end of the statement

  • :override_select is used to override the SELECT clause of eager loaded associations

Examples

Find the union of a San Fran zipcode with a Seattle zipcode

union_args1 = {:conditions => ['zip_id = ?', 94010], :select => :phone_number_id}
union_args2 = {:conditions => ['zip_id = ?', 98102], :select => :phone_number_id}
Contact.find_union(union_args1, union_args2, ...)

SQL>  (SELECT phone_number_id FROM contacts WHERE zip_id = 94010) UNION
      (SELECT phone_number_id FROM contacts WHERE zip_id = 98102) UNION ...

Global Options

To specify global options that apply to the entire union, specify a hash as the first parameter with a key :union_options. Valid options include :group, :having, :order, and :limit

Example:

Contact.find_union(:union_options => {:limit => 10, :order => 'created_on'},
union_args1, union_args2, ...)

SQL> ((select phone_number_id from contacts ...) UNION (select phone_number_id from contacts ...)) order by created_on limit 10


46
47
48
49
# File 'lib/ar-extensions/union.rb', line 46

def find_union(*args)
  supports_union!
  find_by_sql(find_union_sql(*args))
end

.fulltext(fulltext_key, options) ⇒ Object

Adds fulltext searching capabilities to the current model for the given fulltext key and option hash.

Parameters

  • fulltext_key - the key/attribute to be used to as the fulltext index

  • options - the options hash.

Options

  • fields - an array of field names to be used in the fulltext search

Example

class Book < ActiveRecord::Base
  fulltext :title, :fields=>%W( title publisher author_name )    
end

# To use the fulltext index
Book.find :all, :conditions=>{ :match_title => 'Zach' }


40
41
42
43
44
45
# File 'lib/ar-extensions/fulltext.rb', line 40

def fulltext( fulltext_key, options )
  connection.register_fulltext_extension( fulltext_key, options )
rescue NoMethodError
  logger.warn "FullTextSearching is not supported for adapter!"
  raise FullTextSearchingNotSupported.new
end

.import(*args) ⇒ Object

Imports a collection of values to the database.

This is more efficient than using ActiveRecord::Base#create or ActiveRecord::Base#save multiple times. This method works well if you want to create more than one record at a time and do not care about having ActiveRecord objects returned for each record inserted.

This can be used with or without validations. It does not utilize the ActiveRecord::Callbacks during creation/modification while performing the import.

Usage

Model.import array_of_models
Model.import column_names, array_of_values
Model.import column_names, array_of_values, options

Model.import array_of_models

With this form you can call import passing in an array of model objects that you want updated.

Model.import column_names, array_of_values

The first parameter column_names is an array of symbols or strings which specify the columns that you want to update.

The second parameter, array_of_values, is an array of arrays. Each subarray is a single set of values for a new record. The order of values in each subarray should match up to the order of the column_names.

Model.import column_names, array_of_values, options

The first two parameters are the same as the above form. The third parameter, options, is a hash. This is optional. Please see below for what options are available.

Options

  • validate - true|false, tells import whether or not to use \

    ActiveRecord validations. Validations are enforced by default.
    
  • on_duplicate_key_update - an Array or Hash, tells import to \

    use MySQL's ON DUPLICATE KEY UPDATE ability. See On Duplicate\
    Key Update below.
    
  • synchronize - an array of ActiveRecord instances for the model that you are currently importing data into. This synchronizes existing model instances in memory with updates from the import.

  • timestamps - true|false, tells import to not add timestamps \ (if false) even if record timestamps is disabled in ActiveRecord::Base

Examples

class BlogPost < ActiveRecord::Base ; end

# Example using array of model objects
posts = [ BlogPost.new :author_name=>'Zach Dennis', :title=>'AREXT',
          BlogPost.new :author_name=>'Zach Dennis', :title=>'AREXT2',
          BlogPost.new :author_name=>'Zach Dennis', :title=>'AREXT3' ]
BlogPost.import posts

# Example using column_names and array_of_values
columns = [ :author_name, :title ]
values = [ [ 'zdennis', 'test post' ], [ 'jdoe', 'another test post' ] ]
BlogPost.import columns, values 

# Example using column_names, array_of_value and options
columns = [ :author_name, :title ]
values = [ [ 'zdennis', 'test post' ], [ 'jdoe', 'another test post' ] ]
BlogPost.import( columns, values, :validate => false  )

# Example synchronizing existing instances in memory
post = BlogPost.find_by_author_name( 'zdennis' )
puts post.author_name # => 'zdennis'
columns = [ :author_name, :title ]
values = [ [ 'yoda', 'test post' ] ]
BlogPost.import posts, :synchronize=>[ post ]
puts post.author_name # => 'yoda'

# Example synchronizing unsaved/new instances in memory by using a uniqued imported field
posts = [BlogPost.new(:title => "Foo"), BlogPost.new(:title => "Bar")]
BlogPost.import posts, :synchronize => posts
puts posts.first.new_record? # => false

On Duplicate Key Update (MySQL only)

The :on_duplicate_key_update option can be either an Array or a Hash.

Using an Array

The :on_duplicate_key_update option can be an array of column names. The column names are the only fields that are updated if a duplicate record is found. Below is an example:

BlogPost.import columns, values, :on_duplicate_key_update=>[ :date_modified, :content, :author ]

Using A Hash

The :on_duplicate_key_update option can be a hash of column name to model attribute name mappings. This gives you finer grained control over what fields are updated with what attributes on your model. Below is an example:

BlogPost.import columns, attributes, :on_duplicate_key_update=>{ :title => :title }

Returns

This returns an object which responds to failed_instances and num_inserts.

  • failed_instances - an array of objects that fails validation and were not committed to the database. An empty array if no validation is performed.

  • num_inserts - the number of insert statements it took to import the data



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/ar-extensions/import.rb', line 156

def import( *args )
  @logger = Logger.new(STDOUT)
  @logger.level = Logger::DEBUG
  options = { :validate=>true, :timestamps=>true }
  options.merge!( args.pop ) if args.last.is_a? Hash
  
  # assume array of model objects
  if args.last.is_a?( Array ) and args.last.first.is_a? ActiveRecord::Base
    if args.length == 2
      models = args.last
      column_names = args.first
    else
      models = args.first
      column_names = self.column_names.dup
    end
    
    array_of_attributes  = []
    models.each do |model|
      # this next line breaks sqlite.so with a segmentation fault
      # if model.new_record? || options[:on_duplicate_key_update]
        attributes = []
        column_names.each do |name| 
          attributes << model.send( "#{name}_before_type_cast" ) 
        end
        array_of_attributes << attributes
      # end
    end
    # supports 2-element array and array
  elsif args.size == 2 and args.first.is_a?( Array ) and args.last.is_a?( Array )
    column_names, array_of_attributes = args
  else
    raise ArgumentError.new( "Invalid arguments!" )
  end

  # Force the primary key col into the insert if it's not
  # on the list and we are using a sequence and stuff a nil
  # value for it into each row so the sequencer will fire later
  if !column_names.include?(primary_key) && sequence_name && connection.prefetch_primary_key?
     column_names << primary_key
     array_of_attributes.each { |a| a << nil }
  end

  is_validating = options.delete( :validate )

  # dup the passed in array so we don't modify it unintentionally
  array_of_attributes = array_of_attributes.dup

  # record timestamps unless disabled in ActiveRecord::Base
  if record_timestamps && options.delete( :timestamps )
     add_special_rails_stamps column_names, array_of_attributes, options
  end

  return_obj = if is_validating
    import_with_validations( column_names, array_of_attributes, options )
  else
    num_inserts = import_without_validations_or_callbacks( column_names, array_of_attributes, options )
    OpenStruct.new :failed_instances=>[], :num_inserts=>num_inserts
  end

  if options[:synchronize]
    sync_keys = options[:synchronize_keys] || [self.primary_key]
    synchronize( options[:synchronize], sync_keys)
  end

  return_obj.num_inserts = 0 if return_obj.num_inserts.nil?
  return_obj
end

.import_from_table(options) ⇒ Object

TODO import_from_table needs to be implemented.



225
226
# File 'lib/ar-extensions/import.rb', line 225

def import_from_table( options ) # :nodoc:
end

.import_with_validations(column_names, array_of_attributes, options = {}) ⇒ Object

Imports the passed in column_names and array_of_attributes given the passed in options Hash with validations. Returns an object with the methods failed_instances and num_inserts. failed_instances is an array of instances that failed validations. num_inserts is the number of inserts it took to import the data. See ActiveRecord::Base.import for more information on column_names, array_of_attributes and options.



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/ar-extensions/import.rb', line 235

def import_with_validations( column_names, array_of_attributes, options={} )
  failed_instances = []
  
  # create instances for each of our column/value sets
  arr = validations_array_for_column_names_and_attributes( column_names, array_of_attributes )    

  # keep track of the instance and the position it is currently at. if this fails
  # validation we'll use the index to remove it from the array_of_attributes
  arr.each_with_index do |hsh,i|
    instance = new do |model|
      hsh.each_pair{ |k,v| model.send("#{k}=", v) }
    end
    if not instance.valid?
      array_of_attributes[ i ] = nil
      failed_instances << instance
    end    
  end
  array_of_attributes.compact!
  
  num_inserts = array_of_attributes.empty? ? 0 : import_without_validations_or_callbacks( column_names, array_of_attributes, options )
  OpenStruct.new :failed_instances=>failed_instances, :num_inserts => num_inserts
end

.import_without_validations_or_callbacks(column_names, array_of_attributes, options = {}) ⇒ Object

Imports the passed in column_names and array_of_attributes given the passed in options Hash. This will return the number of insert operations it took to create these records without validations or callbacks. See ActiveRecord::Base.import for more information on column_names, +array_of_attributes_ and options.



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/ar-extensions/import.rb', line 264

def import_without_validations_or_callbacks( column_names, array_of_attributes, options={} )
  escaped_column_names = quote_column_names( column_names )
  columns = []
  array_of_attributes.first.each_with_index { |arr,i| columns << columns_hash[ column_names[i] ] }
  
  if not supports_import?
    columns_sql = "(" + escaped_column_names.join( ',' ) + ")"
    insert_statements, values = [], []
    number_inserted = 0
    array_of_attributes.each do |arr|
      my_values = []
      arr.each_with_index do |val,j|
        if !sequence_name.blank? && column_names[j] == primary_key && val.nil?
           my_values << connection.next_value_for_sequence(sequence_name)
        else
           my_values << connection.quote( val, columns[j] )
        end
      end
      insert_statements << "INSERT INTO #{quoted_table_name} #{columns_sql} VALUES(" + my_values.join( ',' ) + ")"
      connection.execute( insert_statements.last )
      number_inserted += 1
    end
  else
    # generate the sql
    insert_sql = connection.multiple_value_sets_insert_sql( quoted_table_name, escaped_column_names, options )
    values_sql = connection.values_sql_for_column_names_and_attributes( columns, array_of_attributes )
    post_sql_statements = connection.post_sql_statements( quoted_table_name, options )
    
    # perform the inserts
    number_inserted = connection.insert_many( [ insert_sql, post_sql_statements ].flatten, 
                                              values_sql,
                                              "#{self.class.name} Create Many Without Validations Or Callbacks" )
  end
  
  number_inserted
end

.insert_select(options = {}) ⇒ Object

Insert records in bulk with a select statement

Parameters

  • options - the options used for the finder sql (select)

Options

Any valid finder options (options for ActiveRecord::Base.find(:all) )such as :joins, :conditions, :include, etc including:

  • :from - the symbol, class name or class used for the finder SQL (select)

  • :on_duplicate_key_update - an array of fields to update, or a custom string

  • :select - An array of fields to select or custom string. The SQL will be sanitized and ? replaced with values as with :conditions.

  • :ignore => true - will ignore any duplicates

  • :into - Specifies the columns for which data will be inserted. An array of fields to select or custom string.

Examples

Create cart items for all books for shopping cart <tt>@cart+ setting the copies field to 1, the updated_at field to Time.now and the created_at field to the database function now()

CartItem.insert_select(:from => :book,
                       :select => ['books.id, ?, ?, ?, now()', @cart.to_param, 1, Time.now],
                       :into => [:book_id, :shopping_cart_id, :copies, :updated_at, :created_at]})

GENERATED SQL example (MySQL):

INSERT INTO `cart_items` ( `book_id`, `shopping_cart_id`, `copies`, `updated_at`, `created_at` )
SELECT books.id, '134', 1, '2009-03-02 18:28:25', now() FROM `books`

A similar example that

  • uses the class Book instead of symbol :book

  • a custom string (instead of an Array) for the :select of the insert_options

  • Updates the updated_at field of all existing cart item. This assumes there is a unique composite index on the book_id and shopping_cart_id fields

CartItem.insert_select(:from => Book,
                       :select => ['books.id, ?, ?, ?, now()', @cart.to_param, 1, Time.now],
                       :into => 'cart_items.book_id, shopping_cart_id, copies, updated_at, created_at',
                       :on_duplicate_key_update => [:updated_at])

GENERATED SQL example (MySQL):

INSERT INTO `cart_items` ( cart_items.book_id, shopping_cart_id, copies, updated_at, created_at )
SELECT books.id, '138', 1, '2009-03-02 18:32:34', now() FROM `books`
       ON DUPLICATE KEY UPDATE `cart_items`.`updated_at`=VALUES(`updated_at`)

Similar example ignoring duplicates

CartItem.insert_select(:from => :book,
                       :select => ['books.id, ?, ?, ?, now()', @cart.to_param, 1, Time.now],
                       :into => [:book_id, :shopping_cart_id, :copies, :updated_at, :created_at],
                       :ignore => true)


112
113
114
115
116
117
118
# File 'lib/ar-extensions/insert_select.rb', line 112

def insert_select(options={})
  select_obj = options.delete(:from).to_s.classify.constantize
  #TODO: add batch support for high volume inserts
  #return insert_select_batch(select_obj, select_options, insert_options) if insert_options[:batch]
  sql = construct_insert_select_sql(select_obj, options)
  connection.insert(sql, "#{name} Insert Select #{select_obj}")
end

.quote_column_names(names) ⇒ Object

Returns an array of quoted column names



302
303
304
# File 'lib/ar-extensions/import.rb', line 302

def quote_column_names( names ) 
  names.map{ |name| connection.quote_column_name( name ) }
end

.supports_full_text_searching?Boolean

Returns true if the current connection adapter supports full text searching, otherwise returns false.

Returns:

  • (Boolean)


49
50
51
52
53
# File 'lib/ar-extensions/fulltext.rb', line 49

def supports_full_text_searching?
  connection.supports_full_text_searching?
rescue NoMethodError
  false
end

.supports_import?Boolean

Returns true if the current database connection adapter supports import functionality, otherwise returns false.

Returns:

  • (Boolean)


34
35
36
37
38
# File 'lib/ar-extensions/import.rb', line 34

def supports_import?
  connection.supports_import?
rescue NoMethodError
  false
end

.supports_on_duplicate_key_update?Boolean

Returns true if the current database connection adapter supports on duplicate key update functionality, otherwise returns false.

Returns:

  • (Boolean)


43
44
45
46
47
# File 'lib/ar-extensions/import.rb', line 43

def supports_on_duplicate_key_update?
  connection.supports_on_duplicate_key_update?
rescue NoMethodError
  false
end

.supports_temporary_tables?Boolean

Returns true if the underlying database connection supports temporary tables

Returns:

  • (Boolean)


9
10
11
12
13
# File 'lib/ar-extensions/temporary_table.rb', line 9

def self.supports_temporary_tables?
  connection.supports_temporary_tables?
rescue NoMethodError
  false
end

.synchronize(instances, keys = [self.primary_key]) ⇒ Object

Synchronizes the passed in ActiveRecord instances with data from the database. This is like calling reload on an individual ActiveRecord instance but it is intended for use on multiple instances.

This uses one query for all instance updates and then updates existing instances rather sending one query for each instance

Examples

# Synchronizing existing models (ie: models with an id) posts = Post.find_by_author(“Zach”) Post.import [:id, :author], [[posts.first.id, “Zachary”]], :synchronize => posts posts.first.author # => “Zachary” instead of Zach

# Synchronizing new/unsaved models by using a unique column to perform the sync posts = [Post.new(:author => “Zach”)] posts.first.new_record? # => true posts.first.id # => nil Post.import posts, :synchronize => posts posts.first.new_record? # => false posts.first.id # => 1



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/ar-extensions/synchronize.rb', line 25

def self.synchronize(instances, keys=[self.primary_key])
  return if instances.empty?

  conditions = {}
  order = ""
  
  key_values = keys.map { |key| instances.map(&"#{key}".to_sym) }
  keys.zip(key_values).each { |key, values| conditions[key] = values }
  order = keys.map{ |key| "#{key} ASC" }.join(",")
  
  klass = instances.first.class

  fresh_instances = klass.find( :all, :conditions=>conditions, :order=>order )
  instances.each do |instance|
    matched_instance = fresh_instances.detect do |fresh_instance|
      keys.all?{ |key| fresh_instance.send(key) == instance.send(key) }
    end
    
    if matched_instance
      instance.clear_aggregation_cache
      instance.clear_association_cache
      instance.instance_variable_set '@attributes', matched_instance.attributes
    end
  end
end

Instance Method Details

#synchronize(instances, key = [ActiveRecord::Base.primary_key]) ⇒ Object

See ActiveRecord::ConnectionAdapters::AbstractAdapter.synchronize



52
53
54
# File 'lib/ar-extensions/synchronize.rb', line 52

def synchronize(instances, key=[ActiveRecord::Base.primary_key])
  self.class.synchronize(instances, key)
end