Permalink
Browse files

Merge branch '2-3-stable' of git@github.com:rails/rails into 2-3-stable

  • Loading branch information...
2 parents c9a3d99 + 09a976a commit 617d7eb57ba0581c61ae6bc2f0d49758466665e1 @jeremy jeremy committed Apr 22, 2009
@@ -1,27 +1,18 @@
module ActionController
class RewindableInput
- class RewindableIO < ActiveSupport::BasicObject
- def initialize(io)
- @io = io
- @rewindable = io.is_a?(::StringIO)
- end
-
- def method_missing(method, *args, &block)
- unless @rewindable
- @io = ::StringIO.new(@io.read)
- @rewindable = true
- end
-
- @io.__send__(method, *args, &block)
- end
- end
-
def initialize(app)
@app = app
end
def call(env)
- env['rack.input'] = RewindableIO.new(env['rack.input'])
+ begin
+ env['rack.input'].rewind
+ rescue NoMethodError, Errno::ESPIPE
+ # Handles exceptions raised by input streams that cannot be rewound
+ # such as when using plain CGI under Apache
+ env['rack.input'] = StringIO.new(env['rack.input'].read)
+ end
+
@app.call(env)
end
end
@@ -94,7 +94,7 @@ def test_to_prepare_with_identifier_replaces
def dispatch(cache_classes = true)
ActionController::Routing::RouteSet.any_instance.stubs(:call).returns([200, {}, 'response'])
Dispatcher.define_dispatcher_callbacks(cache_classes)
- Dispatcher.new.call({})
+ Dispatcher.new.call({'rack.input' => StringIO.new('')})
end
def assert_subclasses(howmany, klass, message = klass.subclasses.inspect)
@@ -206,8 +206,8 @@ def initialize(app)
end
def call(env)
- req = Rack::Request.new(env)
- req.params # Parse params
+ env['rack.input'].read
+ env['rack.input'].rewind
@app.call(env)
end
end
@@ -150,8 +150,8 @@ def initialize(app)
end
def call(env)
- req = Rack::Request.new(env)
- req.params # Parse params
+ env['rack.input'].read
+ env['rack.input'].rewind
@app.call(env)
end
end
@@ -1,3 +1,10 @@
+*Edge*
+
+* Added :touch option to belongs_to associations that will touch the parent record when the current record is saved or destroyed [DHH]
+
+* Added ActiveRecord::Base#touch to update the updated_at/on attributes (or another specified timestamp) with the current time [DHH]
+
+
*2.3.2 [Final] (March 15, 2009)*
* Added ActiveRecord::Base.find_each and ActiveRecord::Base.find_in_batches for batch processing [DHH/Jamis Buck]
@@ -981,6 +981,9 @@ def has_one(association_id, options = {})
# 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 @@ def has_one(association_id, options = {})
# 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 @@ def belongs_to(association_id, options = {})
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 @@ def association_constructor_method(constructor, reflection, association_proxy_cl
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])
@@ -1353,7 +1375,7 @@ def configure_dependency_for_has_many(reflection, extra_conditions = nil)
dependent_conditions = []
dependent_conditions << "#{reflection.primary_key_name} = \#{record.quoted_id}"
dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as]
- dependent_conditions << sanitize_sql(reflection.options[:conditions]) if reflection.options[:conditions]
+ dependent_conditions << sanitize_sql(reflection.options[:conditions], reflection.quoted_table_name) if reflection.options[:conditions]
dependent_conditions << extra_conditions if extra_conditions
dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
dependent_conditions = dependent_conditions.gsub('@', '\@')
@@ -1499,7 +1521,7 @@ def create_has_one_through_reflection(association_id, options)
@@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)
@@ -2101,7 +2123,7 @@ def association_join
klass.send(:type_condition, aliased_table_name)] unless klass.descends_from_active_record?
[through_reflection, reflection].each do |ref|
- join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions]))} " if ref && ref.options[:conditions]
+ join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name))} " if ref && ref.options[:conditions]
end
join
@@ -169,8 +169,8 @@ def interpolate_sql(sql, record = nil)
end
# Forwards the call to the reflection class.
- def sanitize_sql(sql)
- @reflection.klass.send(:sanitize_sql, sql)
+ def sanitize_sql(sql, table_name = @reflection.klass.quoted_table_name)
+ @reflection.klass.send(:sanitize_sql, sql, table_name)
end
# Assigns the ID of the owner to the corresponding foreign key in +record+.
@@ -2228,12 +2228,12 @@ def class_name_of_active_record_descendant(klass) #:nodoc:
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
# { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
# "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
- def sanitize_sql_for_conditions(condition)
+ def sanitize_sql_for_conditions(condition, table_name = quoted_table_name)
return nil if condition.blank?
case condition
when Array; sanitize_sql_array(condition)
- when Hash; sanitize_sql_hash_for_conditions(condition)
+ when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
else condition
end
end
@@ -392,9 +392,28 @@ def quote_string(s) #:nodoc:
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
@@ -621,33 +640,36 @@ def tables(name = nil)
def indexes(table_name, name = nil)
schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
result = query(<<-SQL, name)
- SELECT distinct i.relname, d.indisunique, a.attname
- FROM pg_class t, pg_class i, pg_index d, pg_attribute a
+ SELECT distinct i.relname, d.indisunique, d.indkey, t.oid
+ FROM pg_class t, pg_class i, pg_index d
WHERE i.relkind = 'i'
AND d.indexrelid = i.oid
AND d.indisprimary = 'f'
AND t.oid = d.indrelid
AND t.relname = '#{table_name}'
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
- AND a.attrelid = t.oid
- AND ( d.indkey[0]=a.attnum OR d.indkey[1]=a.attnum
- OR d.indkey[2]=a.attnum OR d.indkey[3]=a.attnum
- OR d.indkey[4]=a.attnum OR d.indkey[5]=a.attnum
- OR d.indkey[6]=a.attnum OR d.indkey[7]=a.attnum
- OR d.indkey[8]=a.attnum OR d.indkey[9]=a.attnum )
ORDER BY i.relname
SQL
- current_index = nil
+
indexes = []
- result.each do |row|
- if current_index != row[0]
- indexes << IndexDefinition.new(table_name, row[0], row[1] == "t", [])
- current_index = row[0]
- end
+ indexes = result.map do |row|
+ index_name = row[0]
+ unique = row[1] == 't'
+ indkey = row[2].split(" ")
+ oid = row[3]
+
+ columns = query(<<-SQL, "Columns for index #{row[0]} on #{table_name}").inject({}) {|attlist, r| attlist[r[1]] = r[0]; attlist}
+ SELECT a.attname, a.attnum
+ FROM pg_attribute a
+ WHERE a.attrelid = #{oid}
+ AND a.attnum IN (#{indkey.join(",")})
+ SQL
+
+ column_names = indkey.map {|attnum| columns[attnum] }
+ IndexDefinition.new(table_name, index_name, unique, column_names)
- indexes.last.columns << row[2]
end
indexes
@@ -745,7 +767,7 @@ def pk_and_sequence_for(table) #:nodoc:
AND attr.attrelid = cons.conrelid
AND attr.attnum = cons.conkey[1]
AND cons.contype = 'p'
- AND dep.refobjid = '#{table}'::regclass
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
end_sql
if result.nil? or result.empty?
@@ -764,7 +786,7 @@ def pk_and_sequence_for(table) #:nodoc:
JOIN pg_attribute attr ON (t.oid = attrelid)
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
- WHERE t.oid = '#{table}'::regclass
+ WHERE t.oid = '#{quote_table_name(table)}'::regclass
AND cons.contype = 'p'
AND def.adsrc ~* 'nextval'
end_sql
@@ -839,7 +861,7 @@ def rename_column(table_name, column_name, new_column_name)
# Drops an index from a table.
def remove_index(table_name, options = {})
- execute "DROP INDEX #{index_name(table_name, options)}"
+ execute "DROP INDEX #{quote_table_name(index_name(table_name, options))}"
end
# Maps logical Rails types to PostgreSQL-specific data types.
@@ -1040,11 +1062,21 @@ def column_definitions(table_name) #:nodoc:
SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
- WHERE a.attrelid = '#{table_name}'::regclass
+ WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
AND a.attnum > 0 AND NOT a.attisdropped
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
Oops, something went wrong.

0 comments on commit 617d7eb

Please sign in to comment.