diff options
author | Jeremy Kemper <jeremy@bitsweat.net> | 2009-04-20 18:31:22 -0700 |
---|---|---|
committer | Jeremy Kemper <jeremy@bitsweat.net> | 2009-04-20 18:31:40 -0700 |
commit | 685a53a360d0d8fc4a9f3b49f900621c940a71f2 (patch) | |
tree | fa6b38f75a873a21c3e380f1acbab3d019293363 /activerecord/lib | |
parent | db05c73fb6e2294c576ef9889c70940891682c32 (diff) | |
parent | 164a94d0bc8c9124ab820506e5ad79496395c026 (diff) | |
download | rails-685a53a360d0d8fc4a9f3b49f900621c940a71f2.tar.gz rails-685a53a360d0d8fc4a9f3b49f900621c940a71f2.tar.bz2 rails-685a53a360d0d8fc4a9f3b49f900621c940a71f2.zip |
Merge branch 'master' into cherry
Conflicts:
activesupport/CHANGELOG
activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
activesupport/lib/active_support/core_ext/hash/conversions.rb
activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
activesupport/lib/active_support/core_ext/string/multibyte.rb
activesupport/lib/active_support/core_ext/time/calculations.rb
activesupport/lib/active_support/deprecation.rb
Diffstat (limited to 'activerecord/lib')
-rwxr-xr-x | activerecord/lib/active_record/associations.rb | 68 | ||||
-rwxr-xr-x | activerecord/lib/active_record/base.rb | 23 | ||||
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb | 31 | ||||
-rw-r--r-- | activerecord/lib/active_record/session_store.rb | 4 | ||||
-rw-r--r-- | activerecord/lib/active_record/timestamp.rb | 48 | ||||
-rw-r--r-- | activerecord/lib/active_record/transactions.rb | 2 |
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) |