Instiki 0.17.2: Security Release

This release upgrades Instiki to Rails 2.3.4, which
patches two security holes in Rails. See

  http://weblog.rubyonrails.org/2009/9/4/ruby-on-rails-2-3-4

There are also some new features, and the usual boatload
of bugfixes. See the CHANGELOG for details.
This commit is contained in:
Jacques Distler 2009-09-05 02:01:46 -05:00
parent 34c4306867
commit 4bdf703ab2
211 changed files with 3959 additions and 1325 deletions

View file

@ -34,11 +34,13 @@ module ActiveRecord
end
end
class HasManyThroughCantAssociateThroughHasManyReflection < ActiveRecordError #:nodoc:
class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
end
end
HasManyThroughCantAssociateThroughHasManyReflection = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection', 'ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection')
class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
@ -410,6 +412,32 @@ module ActiveRecord
# @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
# @firm.invoices # selects all invoices by going through the Client join model.
#
# Similarly you can go through a +has_one+ association on the join model:
#
# class Group < ActiveRecord::Base
# has_many :users
# has_many :avatars, :through => :users
# end
#
# class User < ActiveRecord::Base
# belongs_to :group
# has_one :avatar
# end
#
# class Avatar < ActiveRecord::Base
# belongs_to :user
# end
#
# @group = Group.first
# @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group
# @group.avatars # selects all avatars by going through the User join model.
#
# An important caveat with going through +has_one+ or +has_many+ associations on the join model is that these associations are
# *read-only*. For example, the following would not work following the previous example:
#
# @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around.
# @group.avatars.delete(@group.avatars.last) # so would this
#
# === Polymorphic Associations
#
# Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
@ -759,7 +787,7 @@ module ActiveRecord
# [:through]
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
# are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
# or <tt>has_many</tt> association on the join model.
# <tt>has_one</tt> or <tt>has_many</tt> association on the join model.
# [:source]
# Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
# inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
@ -1241,7 +1269,11 @@ module ActiveRecord
if association_proxy_class == HasOneThroughAssociation
association.create_through_record(new_value)
self.send(reflection.name, new_value)
if new_record?
association_instance_set(reflection.name, new_value.nil? ? nil : association)
else
self.send(reflection.name, new_value)
end
else
association.replace(new_value)
association_instance_set(reflection.name, new_value.nil? ? nil : association)
@ -1293,7 +1325,7 @@ module ActiveRecord
define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
ids = (new_value || []).reject { |nid| nid.blank? }
send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
send("#{reflection.name}=", reflection.klass.find(ids))
end
end
end
@ -1838,7 +1870,7 @@ module ActiveRecord
descendant
end.flatten.compact
remove_duplicate_results!(reflection.class_name.constantize, parent_records, associations[name]) unless parent_records.empty?
remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
end
end
end

View file

@ -208,6 +208,7 @@ module ActiveRecord
# Note that this method will _always_ remove records from the database
# ignoring the +:dependent+ option.
def destroy(*records)
records = find(records) if records.any? {|record| record.kind_of?(Fixnum) || record.kind_of?(String)}
remove_records(records) do |records, old_records|
old_records.each { |record| record.destroy }
end

View file

@ -1,6 +1,11 @@
module ActiveRecord
module Associations
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
def initialize(owner, reflection)
super
@primary_key_list = {}
end
def create(attributes = {})
create_record(attributes) { |record| insert_record(record) }
end
@ -17,6 +22,12 @@ module ActiveRecord
@reflection.reset_column_information
end
def has_primary_key?
return @has_primary_key unless @has_primary_key.nil?
@has_primary_key = (ActiveRecord::Base.connection.supports_primary_key? &&
ActiveRecord::Base.connection.primary_key(@reflection.options[:join_table]))
end
protected
def construct_find_options!(options)
options[:joins] = @join_sql
@ -29,6 +40,11 @@ module ActiveRecord
end
def insert_record(record, force = true, validate = true)
if has_primary_key?
raise ActiveRecord::ConfigurationError,
"Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})."
end
if record.new_record?
if force
record.save!

View file

@ -74,6 +74,7 @@ module ActiveRecord
"#{@reflection.primary_key_name} = NULL",
"#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
)
@owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter?
end
end

View file

@ -17,7 +17,17 @@ module ActiveRecord
def create(attrs = nil)
transaction do
self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association)
object = if attrs
@reflection.klass.send(:with_scope, :create => attrs) {
@reflection.create_association
}
else
@reflection.create_association
end
raise_on_type_mismatch(object)
add_record_to_target_with_callbacks(object) do |r|
insert_record(object, false)
end
object
end
end
@ -44,7 +54,7 @@ module ActiveRecord
options[:select] = construct_select(options[:select])
options[:from] ||= construct_from
options[:joins] = construct_joins(options[:joins])
options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil?
options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include]
end
def insert_record(record, force = true, validate = true)
@ -96,7 +106,7 @@ module ActiveRecord
# Construct attributes for :through pointing to owner and associate.
def construct_join_attributes(associate)
# TODO: revist this to allow it for deletion, supposing dependent option is supported
raise ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection.new(@owner, @reflection) if @reflection.source_reflection.macro == :has_many
raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
if @reflection.options[:source_type]
join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)

View file

@ -9,8 +9,14 @@ module ActiveRecord
if current_object
new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy
else
@owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value))) if new_value
elsif new_value
if @owner.new_record?
self.target = new_value
through_association = @owner.send(:association_instance_get, @reflection.through_reflection.name)
through_association.build(construct_join_attributes(new_value))
else
@owner.send(@reflection.through_reflection.name, klass.create(construct_join_attributes(new_value)))
end
end
end

View file

@ -249,9 +249,10 @@ module ActiveRecord
unless valid = association.valid?
if reflection.options[:autosave]
unless association.marked_for_destruction?
association.errors.each do |attribute, message|
attribute = "#{reflection.name}_#{attribute}"
errors.add(attribute, message) unless errors.on(attribute)
association.errors.each_error do |attribute, error|
error = error.dup
error.attribute = "#{reflection.name}_#{attribute}"
errors.add(error) unless errors.on(error.attribute)
end
end
else

View file

@ -1364,7 +1364,7 @@ module ActiveRecord #:nodoc:
end
defaults << options[:default] if options[:default]
defaults.flatten!
defaults << attribute_key_name.humanize
defaults << attribute_key_name.to_s.humanize
options[:count] ||= 1
I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:activerecord, :attributes]))
end
@ -2294,20 +2294,24 @@ module ActiveRecord #:nodoc:
# And for value objects on a composed_of relationship:
# { :address => Address.new("123 abc st.", "chicago") }
# # => "address_street='123 abc st.' and address_city='chicago'"
def sanitize_sql_hash_for_conditions(attrs, table_name = quoted_table_name)
def sanitize_sql_hash_for_conditions(attrs, default_table_name = quoted_table_name)
attrs = expand_hash_conditions_for_aggregates(attrs)
conditions = attrs.map do |attr, value|
table_name = default_table_name
unless value.is_a?(Hash)
attr = attr.to_s
# Extract table name from qualified attribute names.
if attr.include?('.')
table_name, attr = attr.split('.', 2)
table_name = connection.quote_table_name(table_name)
attr_table_name, attr = attr.split('.', 2)
attr_table_name = connection.quote_table_name(attr_table_name)
else
attr_table_name = table_name
end
attribute_condition("#{table_name}.#{connection.quote_column_name(attr)}", value)
attribute_condition("#{attr_table_name}.#{connection.quote_column_name(attr)}", value)
else
sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
end
@ -3028,16 +3032,22 @@ module ActiveRecord #:nodoc:
def execute_callstack_for_multiparameter_attributes(callstack)
errors = []
callstack.each do |name, values|
callstack.each do |name, values_with_empty_parameters|
begin
klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
# in order to allow a date to be set without a year, we must keep the empty values.
# Otherwise, we wouldn't be able to distinguish it from a date with an empty day.
values = values_with_empty_parameters.reject(&:nil?)
if values.empty?
send(name + "=", nil)
else
value = if Time == klass
instantiate_time_object(name, values)
elsif Date == klass
begin
values = values_with_empty_parameters.collect do |v| v.nil? ? 1 : v end
Date.new(*values)
rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
@ -3065,10 +3075,8 @@ module ActiveRecord #:nodoc:
attribute_name = multiparameter_name.split("(").first
attributes[attribute_name] = [] unless attributes.include?(attribute_name)
unless value.empty?
attributes[attribute_name] <<
[ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ]
end
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
attributes[attribute_name] << [ find_parameter_position(multiparameter_name), parameter_value ]
end
attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }

View file

@ -190,6 +190,8 @@ module ActiveRecord
sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
if options[:from]
sql << " FROM #{options[:from]} "
elsif scope && scope[:from] && !use_workaround
sql << " FROM #{scope[:from]} "
else
sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround
sql << " FROM #{connection.quote_table_name(table_name)} "

View file

@ -277,7 +277,6 @@ module ActiveRecord
add_column_options!(column_sql, column_options) unless type.to_sym == :primary_key
column_sql
end
alias to_s :to_sql
private
@ -316,6 +315,20 @@ module ActiveRecord
@base = base
end
#Handles non supported datatypes - e.g. XML
def method_missing(symbol, *args)
if symbol.to_s == 'xml'
xml_column_fallback(args)
end
end
def xml_column_fallback(*args)
case @base.adapter_name.downcase
when 'sqlite', 'mysql'
options = args.extract_options!
column(args[0], :text, options)
end
end
# Appends a primary key definition to the table definition.
# Can be called multiple times, but this is probably not a good idea.
def primary_key(name)
@ -508,7 +521,7 @@ module ActiveRecord
# concatenated together. This string can then be prepended and appended to
# to generate the final SQL to create the table.
def to_sql
@columns * ', '
@columns.map(&:to_sql) * ', '
end
private
@ -706,3 +719,4 @@ module ActiveRecord
end
end

View file

@ -99,7 +99,7 @@ module ActiveRecord
# See also TableDefinition#column for details on how to create columns.
def create_table(table_name, options = {})
table_definition = TableDefinition.new(self)
table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name)) unless options[:id] == false
table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
yield table_definition
@ -321,7 +321,7 @@ module ActiveRecord
schema_migrations_table.column :version, :string, :null => false
end
add_index sm_table, :version, :unique => true,
:name => 'unique_schema_migrations'
:name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
# Backwards-compatibility: if we find schema_info, assume we've
# migrated up to that point:

View file

@ -54,6 +54,13 @@ module ActiveRecord
false
end
# Can this adapter determine the primary key for tables not attached
# to an ActiveRecord class, such as join tables? Backend specific, as
# the abstract adapter always returns +false+.
def supports_primary_key?
false
end
# Does this adapter support using DISTINCT within COUNT? This is +true+
# for all adapters except sqlite.
def supports_count_distinct?

View file

@ -52,12 +52,7 @@ module ActiveRecord
socket = config[:socket]
username = config[:username] ? config[:username].to_s : 'root'
password = config[:password].to_s
if config.has_key?(:database)
database = config[:database]
else
raise ArgumentError, "No database specified. Missing argument: database."
end
database = config[:database]
# Require the MySQL driver and define Mysql::Result.all_hashes
unless defined? Mysql
@ -80,7 +75,7 @@ module ActiveRecord
module ConnectionAdapters
class MysqlColumn < Column #:nodoc:
def extract_default(default)
if type == :binary || type == :text
if sql_type =~ /blob/i || type == :text
if default.blank?
return null ? nil : ''
else
@ -94,7 +89,7 @@ module ActiveRecord
end
def has_default?
return false if type == :binary || type == :text #mysql forbids defaults on blob and text columns
return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
super
end
@ -212,6 +207,10 @@ module ActiveRecord
true
end
def supports_primary_key? #:nodoc:
true
end
def supports_savepoints? #:nodoc:
true
end
@ -554,6 +553,12 @@ module ActiveRecord
keys.length == 1 ? [keys.first, nil] : nil
end
# Returns just a table's primary key
def primary_key(table)
pk_and_sequence = pk_and_sequence_for(table)
pk_and_sequence && pk_and_sequence.first
end
def case_sensitive_equality_operator
"= BINARY"
end
@ -573,6 +578,10 @@ module ActiveRecord
@connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
end
@connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
@connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
@connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
@connection.real_connect(*@connection_options)
# reconnect must be set after real_connect is called, because real_connect sets it to false internally

View file

@ -39,6 +39,12 @@ module ActiveRecord
end
module ConnectionAdapters
class TableDefinition
def xml(*args)
options = args.extract_options!
column(args[0], 'xml', options)
end
end
# PostgreSQL-specific extensions to column definitions in a table.
class PostgreSQLColumn < Column #:nodoc:
# Instantiates a new PostgreSQL column definition in a table.
@ -67,7 +73,7 @@ module ActiveRecord
# depending on the server specifics
super
end
# Maps PostgreSQL-specific data types to logical Rails types.
def simplified_type(field_type)
case field_type
@ -99,10 +105,10 @@ module ActiveRecord
:string
# XML type
when /^xml$/
:string
:xml
# Arrays
when /^\D+\[\]$/
:string
:string
# Object identifier types
when /^oid$/
:integer
@ -111,7 +117,7 @@ module ActiveRecord
super
end
end
# Extracts the value from a PostgreSQL column default definition.
def self.extract_value_from_default(default)
case default
@ -194,7 +200,8 @@ module ActiveRecord
:time => { :name => "time" },
:date => { :name => "date" },
:binary => { :name => "bytea" },
:boolean => { :name => "boolean" }
:boolean => { :name => "boolean" },
:xml => { :name => "xml" }
}
# Returns 'PostgreSQL' as adapter name for identification purposes.
@ -249,6 +256,11 @@ module ActiveRecord
true
end
# Does PostgreSQL support finding primary key on non-ActiveRecord tables?
def supports_primary_key? #:nodoc:
true
end
# Does PostgreSQL support standard conforming strings?
def supports_standard_conforming_strings?
# Temporarily set the client message level above error to prevent unintentional
@ -272,7 +284,7 @@ module ActiveRecord
def supports_ddl_transactions?
true
end
def supports_savepoints?
true
end
@ -364,7 +376,7 @@ module ActiveRecord
if value.kind_of?(String) && column && column.type == :binary
"#{quoted_string_prefix}'#{escape_bytea(value)}'"
elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/
"xml '#{quote_string(value)}'"
"xml E'#{quote_string(value)}'"
elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
# Not truly string input, so doesn't require (or allow) escape string syntax.
"'#{value.to_s}'"
@ -563,7 +575,7 @@ module ActiveRecord
def rollback_db_transaction
execute "ROLLBACK"
end
if defined?(PGconn::PQTRANS_IDLE)
# The ruby-pg driver supports inspecting the transaction status,
# while the ruby-postgres driver does not.
@ -810,6 +822,12 @@ module ActiveRecord
nil
end
# Returns just a table's primary key
def primary_key(table)
pk_and_sequence = pk_and_sequence_for(table)
pk_and_sequence && pk_and_sequence.first
end
# Renames a table.
def rename_table(name, new_name)
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
@ -908,18 +926,18 @@ module ActiveRecord
sql = "DISTINCT ON (#{columns}) #{columns}, "
sql << order_columns * ', '
end
# Returns an ORDER BY clause for the passed order option.
#
#
# PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
# by wrapping the +sql+ string as a sub-select and ordering in that query.
def add_order_by_for_association_limiting!(sql, options) #:nodoc:
return sql if options[:order].blank?
order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
order.map! { |s| 'DESC' if s =~ /\bdesc$/i }
order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ')
sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
end
@ -1032,7 +1050,7 @@ module ActiveRecord
if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID
# Because money output is formatted according to the locale, there are two
# cases to consider (note the decimal separators):
# (1) $12,345,678.12
# (1) $12,345,678.12
# (2) $12.345.678,12
case column = row[cell_index]
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
@ -1092,3 +1110,4 @@ module ActiveRecord
end
end
end

View file

@ -16,6 +16,10 @@ module ActiveRecord
db.results_as_hash = true if defined? SQLite::Version
db.type_translation = false
message = "Support for SQLite2Adapter and DeprecatedSQLiteAdapter has been removed from Rails 3. "
message << "You should migrate to SQLite 3+ or use the plugin from git://github.com/rails/sqlite2_adapter.git with Rails 3."
ActiveSupport::Deprecation.warn(message)
# "Downgrade" deprecated sqlite API
if SQLite.const_defined?(:Version)
ConnectionAdapters::SQLite2Adapter.new(db, logger, config)
@ -27,6 +31,10 @@ module ActiveRecord
private
def parse_sqlite_config!(config)
if config.include?(:dbfile)
ActiveSupport::Deprecation.warn "Please update config/database.yml to use 'database' instead of 'dbfile'"
end
config[:database] ||= config[:dbfile]
# Require database.
unless config[:database]
@ -104,6 +112,10 @@ module ActiveRecord
true
end
def supports_primary_key? #:nodoc:
true
end
def requires_reloading?
true
end

View file

@ -143,7 +143,7 @@ module ActiveRecord
if partial_updates?
# Serialized attributes should always be written in case they've been
# changed in place.
update_without_dirty(changed | self.class.serialized_attributes.keys)
update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys))
else
update_without_dirty
end

View file

@ -621,7 +621,8 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
targets.each do |target|
join_fixtures["#{label}_#{target}"] = Fixture.new(
{ association.primary_key_name => row[primary_key_name],
association.association_foreign_key => Fixtures.identify(target) }, nil)
association.association_foreign_key => Fixtures.identify(target) },
nil, @connection)
end
end
end
@ -705,12 +706,12 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
yaml_value.each do |fixture|
raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
fixture.each do |name, data|
fixture.each do |name, data|
unless data
raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
end
self[name] = Fixture.new(data, model_class)
self[name] = Fixture.new(data, model_class, @connection)
end
end
end
@ -723,7 +724,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
reader.each do |row|
data = {}
row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class)
self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class, @connection)
end
end
@ -761,7 +762,8 @@ class Fixture #:nodoc:
attr_reader :model_class
def initialize(fixture, model_class)
def initialize(fixture, model_class, connection = ActiveRecord::Base.connection)
@connection = connection
@fixture = fixture
@model_class = model_class.is_a?(Class) ? model_class : model_class.constantize rescue nil
end
@ -783,14 +785,14 @@ class Fixture #:nodoc:
end
def key_list
columns = @fixture.keys.collect{ |column_name| ActiveRecord::Base.connection.quote_column_name(column_name) }
columns = @fixture.keys.collect{ |column_name| @connection.quote_column_name(column_name) }
columns.join(", ")
end
def value_list
list = @fixture.inject([]) do |fixtures, (key, value)|
col = model_class.columns_hash[key] if model_class.respond_to?(:ancestors) && model_class.ancestors.include?(ActiveRecord::Base)
fixtures << ActiveRecord::Base.connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
fixtures << @connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
end
list * ', '
end

View file

@ -10,7 +10,7 @@ module I18n
protected
def interpolate_with_deprecated_syntax(locale, string, values = {})
return string unless string.is_a?(String)
return string unless string.is_a?(String) && !values.empty?
string = string.gsub(/%d|%s/) do |s|
instead = DEPRECATED_INTERPOLATORS[s]

View file

@ -23,8 +23,12 @@ en:
less_than_or_equal_to: "must be less than or equal to {{count}}"
odd: "must be odd"
even: "must be even"
record_invalid: "Validation failed: {{errors}}"
# Append your own errors here or at the model/attributes scope.
full_messages:
format: "{{attribute}} {{message}}"
# You can define own errors for models or model attributes.
# The values :model, :attribute and :value are always available for interpolation.
#

View file

@ -89,12 +89,7 @@ module ActiveRecord
when Hash
options
when Proc
case parent_scope
when Scope
with_scope(:find => parent_scope.proxy_options) { options.call(*args) }
else
options.call(*args)
end
options.call(*args)
end, &block)
end
(class << self; self end).instance_eval do

View file

@ -297,7 +297,7 @@ module ActiveRecord
raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
end
unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil?
unless [:belongs_to, :has_many, :has_one].include?(source_reflection.macro) && source_reflection.options[:through].nil?
raise HasManyThroughSourceAssociationMacroError.new(self)
end
end

View file

@ -84,7 +84,6 @@ HEADER
elsif @connection.respond_to?(:primary_key)
pk = @connection.primary_key(table)
end
pk ||= 'id'
tbl.print " create_table #{table.inspect}"
if columns.detect { |c| c.name == pk }
@ -180,4 +179,4 @@ HEADER
end
end
end
end
end

View file

@ -74,12 +74,14 @@ module ActiveRecord #:nodoc:
# {"comments": [{"body": "Don't think too hard"}],
# "title": "So I was thinking"}]}
def to_json(options = {})
hash = Serializer.new(self, options).serializable_record
hash = { self.class.model_name.element => hash } if include_root_in_json
ActiveSupport::JSON.encode(hash)
super
end
def as_json(options = nil) self end #:nodoc:
def as_json(options = nil) #:nodoc:
hash = Serializer.new(self, options).serializable_record
hash = { self.class.model_name.element => hash } if include_root_in_json
hash
end
def from_json(json)
self.attributes = ActiveSupport::JSON.decode(json)

View file

@ -178,7 +178,7 @@ module ActiveRecord #:nodoc:
end
def root
root = (options[:root] || @record.class.to_s.underscore).to_s
root = (options[:root] || @record.class.model_name.singular).to_s
reformat_name(root)
end
@ -320,7 +320,11 @@ module ActiveRecord #:nodoc:
protected
def compute_type
type = @record.class.serialized_attributes.has_key?(name) ? :yaml : @record.class.columns_hash[name].type
type = if @record.class.serialized_attributes.has_key?(name)
:yaml
else
@record.class.columns_hash[name].try(:type)
end
case type
when :text

View file

@ -10,15 +10,122 @@ module ActiveRecord
attr_reader :record
def initialize(record)
@record = record
super("Validation failed: #{@record.errors.full_messages.join(", ")}")
errors = @record.errors.full_messages.join(I18n.t('support.array.words_connector', :default => ', '))
super(I18n.t('activerecord.errors.messages.record_invalid', :errors => errors))
end
end
class Error
attr_accessor :base, :attribute, :type, :message, :options
def initialize(base, attribute, type = nil, options = {})
self.base = base
self.attribute = attribute
self.type = type || :invalid
self.options = options
self.message = options.delete(:message) || self.type
end
def message
generate_message(@message, options.dup)
end
def full_message
attribute.to_s == 'base' ? message : generate_full_message(message, options.dup)
end
alias :to_s :message
def value
@base.respond_to?(attribute) ? @base.send(attribute) : nil
end
protected
# Translates an error message in it's default scope (<tt>activerecord.errrors.messages</tt>).
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, if it's not there,
# it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not there it returns the translation of the
# default message (e.g. <tt>activerecord.errors.messages.MESSAGE</tt>). The translated model name,
# translated attribute name and the value are available for interpolation.
#
# When using inheritence in your models, it will check all the inherited models too, but only if the model itself
# hasn't been found. Say you have <tt>class Admin < User; end</tt> and you wanted the translation for the <tt>:blank</tt>
# error +message+ for the <tt>title</tt> +attribute+, it looks for these translations:
#
# <ol>
# <li><tt>activerecord.errors.models.admin.attributes.title.blank</tt></li>
# <li><tt>activerecord.errors.models.admin.blank</tt></li>
# <li><tt>activerecord.errors.models.user.attributes.title.blank</tt></li>
# <li><tt>activerecord.errors.models.user.blank</tt></li>
# <li><tt>activerecord.errors.messages.blank</tt></li>
# <li>any default you provided through the +options+ hash (in the activerecord.errors scope)</li>
# </ol>
def generate_message(message, options = {})
keys = @base.class.self_and_descendants_from_active_record.map do |klass|
[ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
:"models.#{klass.name.underscore}.#{message}" ]
end.flatten
keys << options.delete(:default)
keys << :"messages.#{message}"
keys << message if message.is_a?(String)
keys << @type unless @type == message
keys.compact!
options.reverse_merge! :default => keys,
:scope => [:activerecord, :errors],
:model => @base.class.human_name,
:attribute => @base.class.human_attribute_name(attribute.to_s),
:value => value
I18n.translate(keys.shift, options)
end
# Wraps an error message into a full_message format.
#
# The default full_message format for any locale is <tt>"{{attribute}} {{message}}"</tt>.
# One can specify locale specific default full_message format by storing it as a
# translation for the key <tt>:"activerecord.errors.full_messages.format"</tt>.
#
# Additionally one can specify a validation specific error message format by
# storing a translation for <tt>:"activerecord.errors.full_messages.[message_key]"</tt>.
# E.g. the full_message format for any validation that uses :blank as a message
# key (such as validates_presence_of) can be stored to <tt>:"activerecord.errors.full_messages.blank".</tt>
#
# Because the message key used by a validation can be overwritten on the
# <tt>validates_*</tt> class macro level one can customize the full_message format for
# any particular validation:
#
# # app/models/article.rb
# class Article < ActiveRecord::Base
# validates_presence_of :title, :message => :"title.blank"
# end
#
# # config/locales/en.yml
# en:
# activerecord:
# errors:
# full_messages:
# title:
# blank: This title is screwed!
def generate_full_message(message, options = {})
options.reverse_merge! :message => self.message,
:model => @base.class.human_name,
:attribute => @base.class.human_attribute_name(attribute.to_s),
:value => value
key = :"full_messages.#{@message}"
defaults = [:'full_messages.format', '{{attribute}} {{message}}']
I18n.t(key, options.merge(:default => defaults, :scope => [:activerecord, :errors]))
end
end
# Active Record validation is reported to and from this object, which is used by Base#save to
# determine whether the object is in a valid state to be saved. See usage example in Validations.
class Errors
include Enumerable
class << self
def default_error_messages
ActiveSupport::Deprecation.warn("ActiveRecord::Errors.default_error_messages has been deprecated. Please use I18n.translate('activerecord.errors.messages').")
@ -43,11 +150,19 @@ module ActiveRecord
# error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
# If no +messsage+ is supplied, :invalid is assumed.
# If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error).
def add(attribute, message = nil, options = {})
message ||= :invalid
message = generate_message(attribute, message, options) if message.is_a?(Symbol)
# def add(attribute, message = nil, options = {})
# message ||= :invalid
# message = generate_message(attribute, message, options)) if message.is_a?(Symbol)
# @errors[attribute.to_s] ||= []
# @errors[attribute.to_s] << message
# end
def add(error_or_attr, message = nil, options = {})
error, attribute = error_or_attr.is_a?(Error) ? [error_or_attr, error_or_attr.attribute] : [nil, error_or_attr]
options[:message] = options.delete(:default) if options.has_key?(:default)
@errors[attribute.to_s] ||= []
@errors[attribute.to_s] << message
@errors[attribute.to_s] << (error || Error.new(@base, attribute, message, options))
end
# Will add an error message to each of the attributes in +attributes+ that is empty.
@ -66,49 +181,6 @@ module ActiveRecord
add(attr, :blank, :default => custom_message) if value.blank?
end
end
# Translates an error message in it's default scope (<tt>activerecord.errrors.messages</tt>).
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, if it's not there,
# it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not there it returns the translation of the
# default message (e.g. <tt>activerecord.errors.messages.MESSAGE</tt>). The translated model name,
# translated attribute name and the value are available for interpolation.
#
# When using inheritence in your models, it will check all the inherited models too, but only if the model itself
# hasn't been found. Say you have <tt>class Admin < User; end</tt> and you wanted the translation for the <tt>:blank</tt>
# error +message+ for the <tt>title</tt> +attribute+, it looks for these translations:
#
# <ol>
# <li><tt>activerecord.errors.models.admin.attributes.title.blank</tt></li>
# <li><tt>activerecord.errors.models.admin.blank</tt></li>
# <li><tt>activerecord.errors.models.user.attributes.title.blank</tt></li>
# <li><tt>activerecord.errors.models.user.blank</tt></li>
# <li><tt>activerecord.errors.messages.blank</tt></li>
# <li>any default you provided through the +options+ hash (in the activerecord.errors scope)</li>
# </ol>
def generate_message(attribute, message = :invalid, options = {})
message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)
defaults = @base.class.self_and_descendants_from_active_record.map do |klass|
[ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
:"models.#{klass.name.underscore}.#{message}" ]
end
defaults << options.delete(:default)
defaults = defaults.compact.flatten << :"messages.#{message}"
key = defaults.shift
value = @base.respond_to?(attribute) ? @base.send(attribute) : nil
options = { :default => defaults,
:model => @base.class.human_name,
:attribute => @base.class.human_attribute_name(attribute.to_s),
:value => value,
:scope => [:activerecord, :errors]
}.merge(options)
I18n.translate(key, options)
end
# Returns true if the specified +attribute+ has errors associated with it.
#
@ -138,8 +210,9 @@ module ActiveRecord
# company.errors.on(:email) # => "can't be blank"
# company.errors.on(:address) # => nil
def on(attribute)
errors = @errors[attribute.to_s]
return nil if errors.nil?
attribute = attribute.to_s
return nil unless @errors.has_key?(attribute)
errors = @errors[attribute].map(&:to_s)
errors.size == 1 ? errors.first : errors
end
@ -163,7 +236,11 @@ module ActiveRecord
# # name - can't be blank
# # address - can't be blank
def each
@errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
@errors.each_key { |attr| @errors[attr].each { |error| yield attr, error.message } }
end
def each_error
@errors.each_key { |attr| @errors[attr].each { |error| yield attr, error } }
end
# Yields each full error message added. So <tt>Person.errors.add("first_name", "can't be empty")</tt> will be returned
@ -194,22 +271,10 @@ module ActiveRecord
# company.errors.full_messages # =>
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
def full_messages(options = {})
full_messages = []
@errors.each_key do |attr|
@errors[attr].each do |message|
next unless message
if attr == "base"
full_messages << message
else
attr_name = @base.class.human_attribute_name(attr)
full_messages << attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ') + message
end
end
@errors.values.inject([]) do |full_messages, errors|
full_messages + errors.map { |error| error.full_message }
end
full_messages
end
end
# Returns true if no errors have been added.
def empty?
@ -254,7 +319,11 @@ module ActiveRecord
full_messages.each { |msg| e.error(msg) }
end
end
def generate_message(attribute, message = :invalid, options = {})
ActiveSupport::Deprecation.warn("ActiveRecord::Errors#generate_message has been deprecated. Please use ActiveRecord::Error#generate_message.")
Error.new(@base, attribute, message, options).to_s
end
end
@ -437,7 +506,7 @@ module ActiveRecord
validates_each(attr_names, configuration) do |record, attr_name, value|
unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
record.errors.add(attr_name, :confirmation, :default => configuration[:message])
record.errors.add(attr_name, :confirmation, :default => configuration[:message])
end
end
end
@ -479,7 +548,7 @@ module ActiveRecord
validates_each(attr_names,configuration) do |record, attr_name, value|
unless value == configuration[:accept]
record.errors.add(attr_name, :accepted, :default => configuration[:message])
record.errors.add(attr_name, :accepted, :default => configuration[:message])
end
end
end
@ -499,7 +568,7 @@ module ActiveRecord
#
# Configuration options:
# * <tt>message</tt> - A custom error message (default is: "can't be blank").
# * <tt>on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>,
# * <tt>on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>,
# <tt>:update</tt>).
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
@ -599,7 +668,7 @@ module ActiveRecord
validates_each(attrs, options) do |record, attr, value|
value = options[:tokenizer].call(value) if value.kind_of?(String)
unless !value.nil? and value.size.method(validity_checks[option])[option_value]
record.errors.add(attr, key, :default => custom_message, :count => option_value)
record.errors.add(attr, key, :default => custom_message, :count => option_value)
end
end
end
@ -687,7 +756,7 @@ module ActiveRecord
# ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the
# rare case that a race condition occurs, the database will guarantee
# the field's uniqueness.
#
#
# When the database catches such a duplicate insertion,
# ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid
# exception. You can either choose to let this error propagate (which
@ -696,7 +765,7 @@ module ActiveRecord
# that the title already exists, and asking him to re-enter the title).
# This technique is also known as optimistic concurrency control:
# http://en.wikipedia.org/wiki/Optimistic_concurrency_control
#
#
# Active Record currently provides no way to distinguish unique
# index constraint errors from other types of database errors, so you
# will have to parse the (database-specific) exception message to detect
@ -726,7 +795,7 @@ module ActiveRecord
comparison_operator = "IS ?"
elsif column.text?
comparison_operator = "#{connection.case_sensitive_equality_operator} ?"
value = column.limit ? value.to_s[0, column.limit] : value.to_s
value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s
else
comparison_operator = "= ?"
end
@ -794,7 +863,7 @@ module ActiveRecord
validates_each(attr_names, configuration) do |record, attr_name, value|
unless value.to_s =~ configuration[:with]
record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value)
record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value)
end
end
end
@ -828,7 +897,7 @@ module ActiveRecord
validates_each(attr_names, configuration) do |record, attr_name, value|
unless enum.include?(value)
record.errors.add(attr_name, :inclusion, :default => configuration[:message], :value => value)
record.errors.add(attr_name, :inclusion, :default => configuration[:message], :value => value)
end
end
end
@ -862,7 +931,7 @@ module ActiveRecord
validates_each(attr_names, configuration) do |record, attr_name, value|
if enum.include?(value)
record.errors.add(attr_name, :exclusion, :default => configuration[:message], :value => value)
record.errors.add(attr_name, :exclusion, :default => configuration[:message], :value => value)
end
end
end
@ -970,7 +1039,7 @@ module ActiveRecord
case option
when :odd, :even
unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[]
record.errors.add(attr_name, option, :value => raw_value, :default => configuration[:message])
record.errors.add(attr_name, option, :value => raw_value, :default => configuration[:message])
end
else
record.errors.add(attr_name, option, :default => configuration[:message], :value => raw_value, :count => configuration[option]) unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]]

View file

@ -2,7 +2,7 @@ module ActiveRecord
module VERSION #:nodoc:
MAJOR = 2
MINOR = 3
TINY = 3
TINY = 4
STRING = [MAJOR, MINOR, TINY].join('.')
end