diff options
Diffstat (limited to 'activerecord/lib/active_record')
12 files changed, 156 insertions, 80 deletions
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 9a01d793f9..04c12f86b6 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1621,11 +1621,11 @@ end # Allows you to set all the attributes at once by passing in a hash with keys # matching the attribute names (which again matches the column names). # - # If +guard_protected_attributes+ is true (the default), then sensitive - # attributes can be protected from this form of mass-assignment by using - # the +attr_protected+ macro. Or you can alternatively specify which - # attributes *can* be accessed with the +attr_accessible+ macro. Then all the - # attributes not included in that won't be allowed to be mass-assigned. + # If any attributes are protected by either +attr_protected+ or + # +attr_accessible+ then only settable attributes will be assigned. + # + # The +guard_protected_attributes+ argument is now deprecated, use + # the +assign_attributes+ method if you want to bypass mass-assignment security. # # class User < ActiveRecord::Base # attr_protected :is_admin @@ -1635,15 +1635,59 @@ end # user.attributes = { :username => 'Phusion', :is_admin => true } # user.username # => "Phusion" # user.is_admin? # => false + def attributes=(new_attributes, guard_protected_attributes = nil) + unless guard_protected_attributes.nil? + message = "the use of 'guard_protected_attributes' will be removed from the next major release of rails, " + + "if you want to bypass mass-assignment security then look into using assign_attributes" + ActiveSupport::Deprecation.warn(message) + end + + return unless new_attributes.is_a?(Hash) + + guard_protected_attributes ||= true + if guard_protected_attributes + assign_attributes(new_attributes) + else + assign_attributes(new_attributes, :without_protection => true) + end + end + + # Allows you to set all the attributes for a particular mass-assignment + # security scope by passing in a hash of attributes with keys matching + # the attribute names (which again matches the column names) and the scope + # name using the :as option. # - # user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false) + # To bypass mass-assignment security you can use the :without_protection => true + # option. + # + # class User < ActiveRecord::Base + # attr_accessible :name + # attr_accessible :name, :is_admin, :as => :admin + # end + # + # user = User.new + # user.assign_attributes({ :name => 'Josh', :is_admin => true }) + # user.name # => "Josh" + # user.is_admin? # => false + # + # user = User.new + # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin) + # user.name # => "Josh" # user.is_admin? # => true - def attributes=(new_attributes, guard_protected_attributes = true) - return unless new_attributes.is_a?(Hash) + # + # user = User.new + # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true) + # user.name # => "Josh" + # user.is_admin? # => true + def assign_attributes(new_attributes, options = {}) attributes = new_attributes.stringify_keys + scope = options[:as] || :default multi_parameter_attributes = [] - attributes = sanitize_for_mass_assignment(attributes) if guard_protected_attributes + + unless options[:without_protection] + attributes = sanitize_for_mass_assignment(attributes, scope) + end attributes.each do |k, v| if k.include?("(") diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 6d9b5c7b32..70da9d5f1e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -55,6 +55,13 @@ module ActiveRecord def exec_query(sql, name = 'SQL', binds = []) end + # Executes insert +sql+ statement in the context of this connection using + # +binds+ as the bind substitutes. +name+ is the logged along with + # the executed +sql+ statement. + def exec_insert(sql, name, binds) + exec_query(sql, name, binds) + end + # Returns the last auto-generated ID from the affected table. # # +id_value+ will be returned unless the value is nil, in @@ -280,10 +287,6 @@ module ActiveRecord execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert' end - def null_insert_value - Arel.sql 'DEFAULT' - end - def empty_insert_statement_value "VALUES(DEFAULT)" end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index d24cce0a3c..468a2b106b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -223,7 +223,9 @@ module ActiveRecord rescue Exception => e message = "#{e.class.name}: #{e.message}: #{sql}" @logger.debug message if @logger - raise translate_exception(e, message) + exception = translate_exception(e, message) + exception.set_backtrace e.backtrace + raise exception end def translate_exception(e, message) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index c2e75acb9a..2c05ff21f9 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -208,16 +208,18 @@ module ActiveRecord true end - # Returns +true+ when the connection adapter supports prepared statement - # caching, otherwise returns +false+ + # Returns +true+, since this connection adapter supports prepared statement + # caching. def supports_statement_cache? true end + # Returns true. def supports_migrations? #:nodoc: true end + # Returns true. def supports_primary_key? #:nodoc: true end @@ -308,6 +310,8 @@ module ActiveRecord connect end + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. def disconnect! @connection.close rescue nil end @@ -330,6 +334,7 @@ module ActiveRecord rows end + # Clears the prepared statements cache. def clear_cache! @statements.values.each do |cache| cache[:stmt].close @@ -427,10 +432,6 @@ module ActiveRecord end end - def exec_insert(sql, name, binds) - exec_query(sql, name, binds) - end - def last_inserted_id(result) @connection.insert_id end @@ -558,6 +559,10 @@ module ActiveRecord end end + # Drops a MySQL database. + # + # Example: + # drop_database 'sebastian_development' def drop_database(name) #:nodoc: execute "DROP DATABASE IF EXISTS `#{name}`" end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 4e6d9ae087..0c2afc180b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1,6 +1,9 @@ require 'active_record/connection_adapters/abstract_adapter' require 'active_support/core_ext/kernel/requires' require 'active_support/core_ext/object/blank' + +# Make sure we're using pg high enough for PGResult#values +gem 'pg', '~> 0.11' require 'pg' module ActiveRecord @@ -220,8 +223,8 @@ module ActiveRecord ADAPTER_NAME end - # Returns +true+ when the connection adapter supports prepared statement - # caching, otherwise returns +false+ + # Returns +true+, since this connection adapter supports prepared statement + # caching. def supports_statement_cache? true end @@ -246,6 +249,7 @@ module ActiveRecord @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"] end + # Clears the prepared statements cache. def clear_cache! @statements.each_value do |value| @connection.query "DEALLOCATE #{value}" @@ -284,7 +288,8 @@ module ActiveRecord super end - # Close the connection. + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. def disconnect! clear_cache! @connection.close rescue nil @@ -469,42 +474,43 @@ module ActiveRecord # create a 2D array representing the result set def result_as_array(res) #:nodoc: # check if we have any binary column and if they need escaping - unescape_col = [] - res.nfields.times do |j| - unescape_col << res.ftype(j) + ftypes = Array.new(res.nfields) do |i| + [i, res.ftype(i)] end - ary = [] - res.ntuples.times do |i| - ary << [] - res.nfields.times do |j| - data = res.getvalue(i,j) - case unescape_col[j] - - # unescape string passed BYTEA field (OID == 17) - when BYTEA_COLUMN_TYPE_OID - data = unescape_bytea(data) if String === data - - # If this is a money type column and there are any currency symbols, - # then strip them off. Indeed it would be prettier to do this in - # PostgreSQLColumn.string_to_decimal but would break form input - # fields that call value_before_type_cast. - when 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 - # (2) $12.345.678,12 - case data - when /^-?\D+[\d,]+\.\d{2}$/ # (1) - data.gsub!(/[^-\d.]/, '') - when /^-?\D+[\d.]+,\d{2}$/ # (2) - data.gsub!(/[^-\d,]/, '').sub!(/,/, '.') - end + rows = res.values + return rows unless ftypes.any? { |_, x| + x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID + } + + typehash = ftypes.group_by { |_, type| type } + binaries = typehash[BYTEA_COLUMN_TYPE_OID] || [] + monies = typehash[MONEY_COLUMN_TYPE_OID] || [] + + rows.each do |row| + # unescape string passed BYTEA field (OID == 17) + binaries.each do |index, _| + row[index] = unescape_bytea(row[index]) + end + + # If this is a money type column and there are any currency symbols, + # then strip them off. Indeed it would be prettier to do this in + # PostgreSQLColumn.string_to_decimal but would break form input + # fields that call value_before_type_cast. + monies.each do |index, _| + data = row[index] + # Because money output is formatted according to the locale, there are two + # cases to consider (note the decimal separators): + # (1) $12,345,678.12 + # (2) $12.345.678,12 + case data + when /^-?\D+[\d,]+\.\d{2}$/ # (1) + data.gsub!(/[^-\d.]/, '') + when /^-?\D+[\d.]+,\d{2}$/ # (2) + data.gsub!(/[^-\d,]/, '').sub!(/,/, '.') end - ary[i] << data end end - return ary end @@ -561,10 +567,6 @@ module ActiveRecord end end - def exec_insert(sql, name, binds) - exec_query(sql, name, binds) - end - def sql_for_insert(sql, pk, id_value, sequence_name, binds) unless pk _, table = extract_schema_and_table(sql.split(" ", 4)[2]) @@ -650,7 +652,7 @@ module ActiveRecord execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}" end - # Drops a PostgreSQL database + # Drops a PostgreSQL database. # # Example: # drop_database 'matt_development' @@ -942,10 +944,7 @@ module ActiveRecord order_columns.delete_if { |c| c.blank? } order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" } - # Return a DISTINCT ON() clause that's distinct on the columns we want but includes - # all the required columns for the ORDER BY to work properly. - sql = "DISTINCT #{columns}, " - sql << order_columns * ', ' + "DISTINCT #{columns}, #{order_columns * ', '}" end protected diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 9e7f874f4b..ed5006dcec 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -66,16 +66,18 @@ module ActiveRecord sqlite_version >= '3.6.8' end - # Returns +true+ when the connection adapter supports prepared statement - # caching, otherwise returns +false+ + # Returns true, since this connection adapter supports prepared statement + # caching. def supports_statement_cache? true end + # Returns true. def supports_migrations? #:nodoc: true end + # Returns true. def supports_primary_key? #:nodoc: true end @@ -88,12 +90,15 @@ module ActiveRecord sqlite_version >= '3.1.6' end + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. def disconnect! super clear_cache! @connection.close rescue nil end + # Clears the prepared statements cache. def clear_cache! @statements.clear end @@ -173,10 +178,6 @@ module ActiveRecord end end - def exec_insert(sql, name, binds) - exec_query(sql, name, binds) - end - def last_inserted_id(result) @connection.last_insert_row_id end @@ -345,10 +346,6 @@ module ActiveRecord alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s}) end - def null_insert_value - Arel.sql 'NULL' - end - def empty_insert_statement_value "VALUES(NULL)" end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index cace6f0cc0..d38588519b 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -50,6 +50,9 @@ module ActiveRecord initializer "active_record.set_configs" do |app| ActiveSupport.on_load(:active_record) do + if app.config.active_record.delete(:whitelist_attributes) + attr_accessible(nil) + end app.config.active_record.each do |k,v| send "#{k}=", v end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 6b3c38cb58..a49f940e5b 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -70,7 +70,13 @@ db_namespace = namespace :db do @charset = ENV['CHARSET'] || 'utf8' @collation = ENV['COLLATION'] || 'utf8_unicode_ci' creation_options = {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)} - error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error + if config['adapter'] =~ /jdbc/ + #FIXME After Jdbcmysql gives this class + require 'active_record/railties/jdbcmysql_error' + error_class = ArJdbcMySQL::Error + else + error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error + end access_denied_error = 1045 begin ActiveRecord::Base.establish_connection(config.merge('database' => nil)) diff --git a/activerecord/lib/active_record/railties/jdbcmysql_error.rb b/activerecord/lib/active_record/railties/jdbcmysql_error.rb new file mode 100644 index 0000000000..6b9af2a0cb --- /dev/null +++ b/activerecord/lib/active_record/railties/jdbcmysql_error.rb @@ -0,0 +1,16 @@ +#FIXME Remove if ArJdbcMysql will give. +module ArJdbcMySQL + class Error < StandardError + attr_accessor :error_number, :sql_state + + def initialize msg + super + @error_number = nil + @sql_state = nil + end + + # Mysql gem compatibility + alias_method :errno, :error_number + alias_method :error, :message + end +end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 359f9d8a66..8e5f66ec1d 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -56,11 +56,11 @@ module ActiveRecord end substitutes.each_with_index do |tuple, i| - tuple[1] = conn.substitute_at(tuple.first, i) + tuple[1] = conn.substitute_at(binds[i][0], i) end if values.empty? # empty insert - im.values = im.create_values [connection.null_insert_value], [] + im.values = Arel.sql(connection.empty_insert_statement_value) else im.insert substitutes end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index a3d4b7f45a..57c9921ea8 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -279,8 +279,8 @@ module ActiveRecord unless record record = @klass.new do |r| - r.send(:attributes=, protected_attributes_for_create, true) unless protected_attributes_for_create.empty? - r.send(:attributes=, unprotected_attributes_for_create, false) unless unprotected_attributes_for_create.empty? + r.assign_attributes(protected_attributes_for_create) + r.assign_attributes(unprotected_attributes_for_create, :without_protection => true) end yield(record) if block_given? record.save if match.instantiator == :create diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index d1225a9ed9..4db4105389 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -56,8 +56,9 @@ module ActiveRecord column = klass.columns_hash[attribute.to_s] value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if column.text? - if !options[:case_sensitive] && column.text? - relation = table[attribute].matches(value) + if !options[:case_sensitive] && value && column.text? + # will use SQL LOWER function before comparison + relation = table[attribute].lower.eq(table.lower(value)) else value = klass.connection.case_sensitive_modifier(value) relation = table[attribute].eq(value) |