aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rwxr-xr-xactiverecord/lib/active_record/associations.rb68
-rwxr-xr-xactiverecord/lib/active_record/base.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb31
-rw-r--r--activerecord/lib/active_record/session_store.rb4
-rw-r--r--activerecord/lib/active_record/timestamp.rb48
-rw-r--r--activerecord/lib/active_record/transactions.rb2
6 files changed, 131 insertions, 45 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 6d25b36aea..53a710537f 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -981,6 +981,9 @@ module ActiveRecord
# If false, don't validate the associated objects when saving the parent object. +false+ by default.
# [:autosave]
# If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default.
+ # [:touch]
+ # If true, the associated object will be touched (the updated_at/on attributes set to now) when this record is either saved or
+ # destroyed. If you specify a symbol, that attribute will be updated with the current time instead of the updated_at/on attribute.
#
# Option examples:
# belongs_to :firm, :foreign_key => "client_of"
@@ -990,6 +993,8 @@ module ActiveRecord
# belongs_to :attachable, :polymorphic => true
# belongs_to :project, :readonly => true
# belongs_to :post, :counter_cache => true
+ # belongs_to :company, :touch => true
+ # belongs_to :company, :touch => :employees_last_updated_at
def belongs_to(association_id, options = {})
reflection = create_belongs_to_reflection(association_id, options)
@@ -1001,28 +1006,8 @@ module ActiveRecord
association_constructor_method(:create, reflection, BelongsToAssociation)
end
- # Create the callbacks to update counter cache
- if options[:counter_cache]
- cache_column = reflection.counter_cache_column
-
- method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym
- define_method(method_name) do
- association = send(reflection.name)
- association.class.increment_counter(cache_column, send(reflection.primary_key_name)) unless association.nil?
- end
- after_create method_name
-
- method_name = "belongs_to_counter_cache_before_destroy_for_#{reflection.name}".to_sym
- define_method(method_name) do
- association = send(reflection.name)
- association.class.decrement_counter(cache_column, send(reflection.primary_key_name)) unless association.nil?
- end
- before_destroy method_name
-
- module_eval(
- "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)"
- )
- end
+ add_counter_cache_callbacks(reflection) if options[:counter_cache]
+ add_touch_callbacks(reflection, options[:touch]) if options[:touch]
configure_dependency_for_belongs_to(reflection)
end
@@ -1329,6 +1314,43 @@ module ActiveRecord
end
end
+ def add_counter_cache_callbacks(reflection)
+ cache_column = reflection.counter_cache_column
+
+ method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym
+ define_method(method_name) do
+ association = send(reflection.name)
+ association.class.increment_counter(cache_column, send(reflection.primary_key_name)) unless association.nil?
+ end
+ after_create(method_name)
+
+ method_name = "belongs_to_counter_cache_before_destroy_for_#{reflection.name}".to_sym
+ define_method(method_name) do
+ association = send(reflection.name)
+ association.class.decrement_counter(cache_column, send(reflection.primary_key_name)) unless association.nil?
+ end
+ before_destroy(method_name)
+
+ module_eval(
+ "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)"
+ )
+ end
+
+ def add_touch_callbacks(reflection, touch_attribute)
+ method_name = "belongs_to_touch_after_save_or_destroy_for_#{reflection.name}".to_sym
+ define_method(method_name) do
+ association = send(reflection.name)
+
+ if touch_attribute == true
+ association.touch unless association.nil?
+ else
+ association.touch(touch_attribute) unless association.nil?
+ end
+ end
+ after_save(method_name)
+ after_destroy(method_name)
+ end
+
def find_with_associations(options = {})
catch :invalid_query do
join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
@@ -1499,7 +1521,7 @@ module ActiveRecord
@@valid_keys_for_belongs_to_association = [
:class_name, :foreign_key, :foreign_type, :remote, :select, :conditions,
:include, :dependent, :counter_cache, :extend, :polymorphic, :readonly,
- :validate
+ :validate, :touch
]
def create_belongs_to_reflection(association_id, options)
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 2a5385119d..9943a7014a 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -810,25 +810,28 @@ module ActiveRecord #:nodoc:
# Updates all records with details given if they match a set of conditions supplied, limits and order can
# also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
- # database. It does not instantiate the involved models and it does not trigger Active Record callbacks.
+ # database. It does not instantiate the involved models and it does not trigger Active Record callbacks
+ # or validations.
#
# ==== Parameters
#
- # * +updates+ - A string of column and value pairs that will be set on any records that match conditions. This creates the SET clause of the generated SQL.
- # * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro for more info.
+ # * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
+ # * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement. See conditions in the intro.
# * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage.
#
# ==== Examples
#
- # # Update all billing objects with the 3 different attributes given
- # Billing.update_all( "category = 'authorized', approved = 1, author = 'David'" )
+ # # Update all customers with the given attributes
+ # Customer.update_all :wants_email => true
#
- # # Update records that match our conditions
- # Billing.update_all( "author = 'David'", "title LIKE '%Rails%'" )
+ # # Update all books with 'Rails' in their title
+ # Book.update_all "author = 'David'", "title LIKE '%Rails%'"
#
- # # Update records that match our conditions but limit it to 5 ordered by date
- # Billing.update_all( "author = 'David'", "title LIKE '%Rails%'",
- # :order => 'created_at', :limit => 5 )
+ # # Update all avatars migrated more than a week ago
+ # Avatar.update_all ['migrated_at = ?, Time.now.utc], ['migrated_at > ?', 1.week.ago]
+ #
+ # # Update all books that match our conditions, but limit it to 5 ordered by date
+ # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5
def update_all(updates, conditions = nil, options = {})
sql = "UPDATE #{quoted_table_name} SET #{sanitize_sql_for_assignment(updates)} "
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 913bb521ca..ec204d0f03 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -392,9 +392,28 @@ module ActiveRecord
quote_string(s)
end
+ # Checks the following cases:
+ #
+ # - table_name
+ # - "table.name"
+ # - schema_name.table_name
+ # - schema_name."table.name"
+ # - "schema.name".table_name
+ # - "schema.name"."table.name"
+ def quote_table_name(name)
+ schema, name_part = extract_pg_identifier_from_name(name.to_s)
+
+ unless name_part
+ quote_column_name(schema)
+ else
+ table_name, name_part = extract_pg_identifier_from_name(name_part)
+ "#{quote_column_name(schema)}.#{quote_column_name(table_name)}"
+ end
+ end
+
# Quotes column names for use in SQL queries.
def quote_column_name(name) #:nodoc:
- %("#{name}")
+ PGconn.quote_ident(name.to_s)
end
# Quote date/time values for use in SQL input. Includes microseconds
@@ -1045,6 +1064,16 @@ module ActiveRecord
ORDER BY a.attnum
end_sql
end
+
+ def extract_pg_identifier_from_name(name)
+ match_data = name[0,1] == '"' ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
+
+ if match_data
+ rest = name[match_data[0].length..-1]
+ rest = rest[1..-1] if rest[0,1] == "."
+ [match_data[1], (rest.length > 0 ? rest : nil)]
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index 3cc4640f42..21471da419 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -40,7 +40,7 @@ module ActiveRecord
#
# The example SqlBypass class is a generic SQL session store. You may
# use it as a basis for high-performance database-specific stores.
- class SessionStore < ActionController::Session::AbstractStore
+ class SessionStore < ActionDispatch::Session::AbstractStore
# The default Active Record class.
class Session < ActiveRecord::Base
##
@@ -184,7 +184,7 @@ module ActiveRecord
# Look up a session by id and unmarshal its data if found.
def find_by_session_id(session_id)
- if record = @@connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{@@connection.quote(session_id)}")
+ if record = connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{connection.quote(session_id)}")
new(:session_id => session_id, :marshaled_data => record['data'])
end
end
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 8dbe80a01a..d9e1ef351f 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -15,27 +15,57 @@ module ActiveRecord
base.class_inheritable_accessor :record_timestamps, :instance_writer => false
base.record_timestamps = true
end
+
+ # Saves the record with the updated_at/on attributes set to the current time.
+ # If the save fails because of validation errors, an ActiveRecord::RecordInvalid exception is raised.
+ # If an attribute name is passed, that attribute is used for the touch instead of the updated_at/on attributes.
+ #
+ # Examples:
+ #
+ # product.touch # updates updated_at
+ # product.touch(:designed_at) # updates the designed_at attribute
+ def touch(attribute = nil)
+ current_time = current_time_from_proper_timezone
+
+ if attribute
+ write_attribute(attribute, current_time)
+ else
+ write_attribute('updated_at', current_time) if respond_to?(:updated_at)
+ write_attribute('updated_on', current_time) if respond_to?(:updated_on)
+ end
+
+ save!
+ end
+
private
def create_with_timestamps #:nodoc:
if record_timestamps
- t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
- write_attribute('created_at', t) if respond_to?(:created_at) && created_at.nil?
- write_attribute('created_on', t) if respond_to?(:created_on) && created_on.nil?
+ current_time = current_time_from_proper_timezone
- write_attribute('updated_at', t) if respond_to?(:updated_at) && updated_at.nil?
- write_attribute('updated_on', t) if respond_to?(:updated_on) && updated_on.nil?
+ write_attribute('created_at', current_time) if respond_to?(:created_at) && created_at.nil?
+ write_attribute('created_on', current_time) if respond_to?(:created_on) && created_on.nil?
+
+ write_attribute('updated_at', current_time) if respond_to?(:updated_at) && updated_at.nil?
+ write_attribute('updated_on', current_time) if respond_to?(:updated_on) && updated_on.nil?
end
+
create_without_timestamps
end
def update_with_timestamps(*args) #:nodoc:
if record_timestamps && (!partial_updates? || changed?)
- t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
- write_attribute('updated_at', t) if respond_to?(:updated_at)
- write_attribute('updated_on', t) if respond_to?(:updated_on)
+ current_time = current_time_from_proper_timezone
+
+ write_attribute('updated_at', current_time) if respond_to?(:updated_at)
+ write_attribute('updated_on', current_time) if respond_to?(:updated_on)
end
+
update_without_timestamps(*args)
end
+
+ def current_time_from_proper_timezone
+ self.class.default_timezone == :utc ? Time.now.utc : Time.now
+ end
end
-end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 0b6e52c79b..b059eb7f6f 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -175,6 +175,8 @@ module ActiveRecord
# end # RELEASE savepoint active_record_1
# # ^^^^ BOOM! database error!
# end
+ #
+ # Note that "TRUNCATE" is also a MySQL DDL statement!
module ClassMethods
# See ActiveRecord::Transactions::ClassMethods for detailed documentation.
def transaction(options = {}, &block)