diff options
Diffstat (limited to 'activerecord/lib')
30 files changed, 596 insertions, 405 deletions
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 33a184d48d..6cdec8c487 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -93,20 +93,20 @@ module ActiveRecord first_or_last(:last, *args) end - def build(attributes = {}, &block) - build_or_create(attributes, :build, &block) + def build(attributes = {}, options = {}, &block) + build_or_create(:build, attributes, options, &block) end - def create(attributes = {}, &block) + def create(attributes = {}, options = {}, &block) unless owner.persisted? raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved" end - build_or_create(attributes, :create, &block) + build_or_create(:create, attributes, options, &block) end - def create!(attrs = {}, &block) - record = create(attrs, &block) + def create!(attrs = {}, options = {}, &block) + record = create(attrs, options, &block) Array.wrap(record).each(&:save!) record end @@ -403,9 +403,9 @@ module ActiveRecord end + existing end - def build_or_create(attributes, method) + def build_or_create(method, attributes, options) records = Array.wrap(attributes).map do |attrs| - record = build_record(attrs) + record = build_record(attrs, options) add_to_target(record) do yield(record) if block_given? @@ -421,8 +421,8 @@ module ActiveRecord raise NotImplementedError end - def build_record(attributes) - reflection.build_association(scoped.scope_for_create.merge(attributes)) + def build_record(attributes, options) + reflection.build_association(scoped.scope_for_create.merge(attributes), options) end def delete_or_destroy(records, method) diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 388173c1fb..adfc71d435 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -64,9 +64,12 @@ module ActiveRecord def method_missing(method, *args, &block) match = DynamicFinderMatch.match(method) - if match && match.creator? - attributes = match.attribute_names - return send(:"find_by_#{attributes.join('_and_')}", *args) || create(Hash[attributes.zip(args)]) + if match && match.instantiator? + record = send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r| + @association.send :set_owner_attributes, r + @association.send :add_to_target, r + yield(r) if block_given? + end end if target.respond_to?(method) || (!@association.klass.respond_to?(method) && Class.respond_to?(method)) diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 9d2b29685b..7708228d23 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -60,10 +60,10 @@ module ActiveRecord through_record end - def build_record(attributes) + def build_record(attributes, options = {}) ensure_not_nested - record = super(attributes) + record = super(attributes, options) inverse = source_reflection.inverse_of if inverse diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index 0a666598ed..c32753782f 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -91,12 +91,12 @@ module ActiveRecord constraint = build_constraint(reflection, table, key, foreign_table, foreign_key) - relation.from(join(table, constraint)) - unless conditions[i].empty? - relation.where(sanitize(conditions[i], table)) + constraint = constraint.and(sanitize(conditions[i], table)) end + relation.from(join(table, constraint)) + # The current table in this iteration becomes the foreign table in the next foreign_table = table end diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index 4edbe216be..ea4d73d414 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -17,16 +17,16 @@ module ActiveRecord replace(record) end - def create(attributes = {}) - new_record(:create, attributes) + def create(attributes = {}, options = {}) + new_record(:create, attributes, options) end - def create!(attributes = {}) - build(attributes).tap { |record| record.save! } + def create!(attributes = {}, options = {}) + build(attributes, options).tap { |record| record.save! } end - def build(attributes = {}) - new_record(:build, attributes) + def build(attributes = {}, options = {}) + new_record(:build, attributes, options) end private @@ -44,9 +44,9 @@ module ActiveRecord replace(record) end - def new_record(method, attributes) + def new_record(method, attributes, options) attributes = scoped.scope_for_create.merge(attributes || {}) - record = reflection.send("#{method}_association", attributes) + record = reflection.send("#{method}_association", attributes, options) set_new_record(record) record end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 04c12f86b6..6149865f80 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -83,7 +83,7 @@ module ActiveRecord #:nodoc: # # The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query # and is thus susceptible to SQL-injection attacks if the <tt>user_name</tt> and +password+ - # parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and + # parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and # <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+ # before inserting them in the query, which will ensure that an attacker can't escape the # query and fake the login (or worse). @@ -406,10 +406,10 @@ module ActiveRecord #:nodoc: ## # :singleton-method: # Specifies the format to use when dumping the database schema with Rails' - # Rakefile. If :sql, the schema is dumped as (potentially database- - # specific) SQL statements. If :ruby, the schema is dumped as an + # Rakefile. If :sql, the schema is dumped as (potentially database- + # specific) SQL statements. If :ruby, the schema is dumped as an # ActiveRecord::Schema file which can be loaded into any database that - # supports migrations. Use :ruby if you want to have different database + # supports migrations. Use :ruby if you want to have different database # adapters for, e.g., your development and test environments. cattr_accessor :schema_format , :instance_writer => false @@schema_format = :ruby @@ -443,17 +443,17 @@ module ActiveRecord #:nodoc: delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped - # Executes a custom SQL query against your database and returns all the results. The results will + # Executes a custom SQL query against your database and returns all the results. The results will # be returned as an array with columns requested encapsulated as attributes of the model you call - # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in + # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in # a Product object with the attributes you specified in the SQL query. # # If you call a complicated SQL query which spans multiple tables the columns specified by the # SELECT will be attributes of the model, whether or not they are columns of the corresponding # table. # - # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be - # no database agnostic conversions performed. This should be a last resort because using, for example, + # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be + # no database agnostic conversions performed. This should be a last resort because using, for example, # MySQL specific terms will lock you to using that particular database engine or require you to # change your call if you switch engines. # @@ -472,13 +472,22 @@ module ActiveRecord #:nodoc: # Creates an object (or multiple objects) and saves it to the database, if validations pass. # The resulting object is returned whether the object was saved successfully to the database or not. # - # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the + # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the # attributes on the objects that are to be created. # + # +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options + # in the +options+ parameter. + # # ==== Examples # # Create a single new object # User.create(:first_name => 'Jamie') # + # # Create a single new object using the :admin mass-assignment security scope + # User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin) + # + # # Create a single new object bypassing mass-assignment security + # User.create({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true) + # # # Create an Array of new objects # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) # @@ -491,11 +500,11 @@ module ActiveRecord #:nodoc: # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u| # u.is_admin = false # end - def create(attributes = nil, &block) + def create(attributes = nil, options = {}, &block) if attributes.is_a?(Array) - attributes.collect { |attr| create(attr, &block) } + attributes.collect { |attr| create(attr, options, &block) } else - object = new(attributes) + object = new(attributes, options) yield(object) if block_given? object.save object @@ -504,7 +513,7 @@ module ActiveRecord #:nodoc: # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part. # The use of this method should be restricted to complicated SQL queries that can't be executed - # using the ActiveRecord::Calculations class methods. Look into those before using this. + # using the ActiveRecord::Calculations class methods. Look into those before using this. # # ==== Parameters # @@ -581,7 +590,7 @@ module ActiveRecord #:nodoc: # invoice/lineitem.rb Invoice::Lineitem lineitems # # Additionally, the class-level +table_name_prefix+ is prepended and the - # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix, + # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix, # the table name guess for an Invoice class becomes "myapp_invoices". # Invoice::Lineitem becomes "myapp_invoice_lineitems". # @@ -615,7 +624,7 @@ module ActiveRecord #:nodoc: @inheritance_column ||= "type" end - # Lazy-set the sequence name to the connection's default. This method + # Lazy-set the sequence name to the connection's default. This method # is only ever called once since set_sequence_name overrides it. def sequence_name #:nodoc: reset_sequence_name @@ -627,7 +636,7 @@ module ActiveRecord #:nodoc: default end - # Sets the table name. If the value is nil or false then the value returned by the given + # Sets the table name. If the value is nil or false then the value returned by the given # block is used. # # class Project < ActiveRecord::Base @@ -1077,7 +1086,7 @@ module ActiveRecord #:nodoc: # <tt>where</tt>, <tt>includes</tt>, and <tt>joins</tt> operations in <tt>Relation</tt>, which are merged. # # <tt>joins</tt> operations are uniqued so multiple scopes can join in the same table without table aliasing - # problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the + # problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the # array of strings format for your joins. # # class Article < ActiveRecord::Base @@ -1180,19 +1189,15 @@ MSG # Use this macro in your model to set a default scope for all operations on # the model. # - # class Person < ActiveRecord::Base - # default_scope order('last_name, first_name') + # class Article < ActiveRecord::Base + # default_scope where(:published => true) # end # - # Person.all # => SELECT * FROM people ORDER BY last_name, first_name + # Article.all # => SELECT * FROM articles WHERE published = true # # The <tt>default_scope</tt> is also applied while creating/building a record. It is not # applied while updating a record. # - # class Article < ActiveRecord::Base - # default_scope where(:published => true) - # end - # # Article.new.published # => true # Article.create.published # => true # @@ -1205,6 +1210,19 @@ MSG # (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt> # macro, and it will be called when building the default scope.) # + # If you use multiple <tt>default_scope</tt> declarations in your model then they will + # be merged together: + # + # class Article < ActiveRecord::Base + # default_scope where(:published => true) + # default_scope where(:rating => 'G') + # end + # + # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G' + # + # This is also the case with inheritance and module includes where the parent or module + # defines a <tt>default_scope</tt> and the child or including class defines a second one. + # # If you need to do more complex things with a default scope, you can alternatively # define it as a class method: # @@ -1214,36 +1232,8 @@ MSG # end # end def default_scope(scope = {}) - if default_scopes.length != 0 - ActiveSupport::Deprecation.warn <<-WARN -Calling 'default_scope' multiple times in a class (including when a superclass calls 'default_scope') is deprecated. The current behavior is that this will merge the default scopes together: - -class Post < ActiveRecord::Base # Rails 3.1 - default_scope where(:published => true) - default_scope where(:hidden => false) - # The default scope is now: where(:published => true, :hidden => false) -end - -In Rails 3.2, the behavior will be changed to overwrite previous scopes: - -class Post < ActiveRecord::Base # Rails 3.2 - default_scope where(:published => true) - default_scope where(:hidden => false) - # The default scope is now: where(:hidden => false) -end - -If you wish to merge default scopes in special ways, it is recommended to define your default scope as a class method and use the standard techniques for sharing code (inheritance, mixins, etc.): - -class Post < ActiveRecord::Base - def self.default_scope - where(:published => true).where(:hidden => false) - end -end - WARN - end - scope = Proc.new if block_given? - self.default_scopes = default_scopes.dup << scope + self.default_scopes = default_scopes + [scope] end def build_default_scope #:nodoc: @@ -1400,7 +1390,7 @@ end end.join(', ') end - # Accepts an array of conditions. The array has each value + # Accepts an array of conditions. The array has each value # sanitized and interpolated into the SQL statement. # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'" def sanitize_sql_array(ary) @@ -1484,7 +1474,20 @@ end # attributes but not yet saved (pass a hash with key names matching the associated table column names). # In both instances, valid attribute keys are determined by the column names of the associated table -- # hence you can't have attributes that aren't part of the table columns. - def initialize(attributes = nil) + # + # +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options + # in the +options+ parameter. + # + # ==== Examples + # # Instantiates a single new object + # User.new(:first_name => 'Jamie') + # + # # Instantiates a single new object using the :admin mass-assignment security scope + # User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin) + # + # # Instantiates a single new object bypassing mass-assignment security + # User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true) + def initialize(attributes = nil, options = {}) @attributes = attributes_from_column_definition @association_cache = {} @aggregation_cache = {} @@ -1500,7 +1503,8 @@ end set_serialized_attributes populate_with_current_scope_attributes - self.attributes = attributes unless attributes.nil? + + assign_attributes(attributes, options) if attributes result = yield self if block_given? run_callbacks :initialize @@ -1508,7 +1512,7 @@ end end # Populate +coder+ with attributes about this record that should be - # serialized. The structure of +coder+ defined in this method is + # serialized. The structure of +coder+ defined in this method is # guaranteed to match the structure of +coder+ passed to the +init_with+ # method. # @@ -1523,8 +1527,8 @@ end coder['attributes'] = attributes end - # Initialize an empty model object from +coder+. +coder+ must contain - # the attributes necessary for initializing an empty model object. For + # Initialize an empty model object from +coder+. +coder+ must contain + # the attributes necessary for initializing an empty model object. For # example: # # class Post < ActiveRecord::Base @@ -1939,32 +1943,9 @@ end errors = [] 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 { |v| v.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 - end - else - klass.new(*values) - end - - send(name + "=", value) - end + send(name + "=", read_value_from_parameter(name, values_with_empty_parameters)) rescue => ex - errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name) + errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name) end end unless errors.empty? @@ -1972,19 +1953,65 @@ end end end + def read_value_from_parameter(name, values_hash_from_param) + klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass + if values_hash_from_param.values.all?{|v|v.nil?} + nil + elsif klass == Time + read_time_parameter_value(name, values_hash_from_param) + elsif klass == Date + read_date_parameter_value(name, values_hash_from_param) + else + read_other_parameter_value(klass, name, values_hash_from_param) + end + end + + def read_time_parameter_value(name, values_hash_from_param) + # If Date bits were not provided, error + raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)} + max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6) + set_values = (1..max_position).collect{|position| values_hash_from_param[position] } + # If Date bits were provided but blank, then default to 1 + # If Time bits are not there, then default to 0 + [1,1,1,0,0,0].each_with_index{|v,i| set_values[i] = set_values[i].blank? ? v : set_values[i]} + instantiate_time_object(name, set_values) + end + + def read_date_parameter_value(name, values_hash_from_param) + set_values = (1..3).collect{|position| values_hash_from_param[position].blank? ? 1 : values_hash_from_param[position]} + begin + Date.new(*set_values) + rescue ArgumentError => ex # if Date.new raises an exception on an invalid date + instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates + end + end + + def read_other_parameter_value(klass, name, values_hash_from_param) + max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param) + values = (1..max_position).collect do |position| + raise "Missing Parameter" if !values_hash_from_param.has_key?(position) + values_hash_from_param[position] + end + klass.new(*values) + end + + def extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100) + [values_hash_from_param.keys.max,upper_cap].min + end + def extract_callstack_for_multiparameter_attributes(pairs) attributes = { } for pair in pairs multiparameter_name, value = pair attribute_name = multiparameter_name.split("(").first - attributes[attribute_name] = [] unless attributes.include?(attribute_name) + attributes[attribute_name] = {} unless attributes.include?(attribute_name) parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value) - attributes[attribute_name] << [ find_parameter_position(multiparameter_name), parameter_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 } } + attributes end def type_cast_attribute_value(multiparameter_name, value) @@ -1992,7 +2019,7 @@ end end def find_parameter_position(multiparameter_name) - multiparameter_name.scan(/\(([0-9]*).*\)/).first.first + multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i end # Returns a comma-separated pair list, like "key1 = val1, key2 = val2". diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index b4db1eed18..6f21cea288 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -113,7 +113,7 @@ module ActiveRecord end end - # A cached lookup for table existence + # A cached lookup for table existence. def table_exists?(name) return true if @tables.key? name @@ -135,7 +135,7 @@ module ActiveRecord @tables.clear end - # Clear out internal caches for table with +table_name+ + # Clear out internal caches for table with +table_name+. def clear_table_cache!(table_name) @columns.delete table_name @columns_hash.delete table_name @@ -193,7 +193,7 @@ module ActiveRecord @connections = [] end - # Clears the cache which maps classes + # Clears the cache which maps classes. def clear_reloadable_connections! @reserved_connections.each do |name, conn| checkin conn @@ -365,7 +365,7 @@ module ActiveRecord @connection_pools.each_value {|pool| pool.release_connection } end - # Clears the cache which maps classes + # Clears the cache which maps classes. def clear_reloadable_connections! @connection_pools.each_value {|pool| pool.clear_reloadable_connections! } end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb index 29ac9341ec..30ccb8f0a4 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb @@ -2,52 +2,53 @@ module ActiveRecord module ConnectionAdapters # :nodoc: module DatabaseLimits - # the maximum length of a table alias + # Returns the maximum length of a table alias. def table_alias_length 255 end - # the maximum length of a column name + # Returns the maximum length of a column name. def column_name_length 64 end - # the maximum length of a table name + # Returns the maximum length of a table name. def table_name_length 64 end - # the maximum length of an index name + # Returns the maximum length of an index name. def index_name_length 64 end - # the maximum number of columns per table + # Returns the maximum number of columns per table. def columns_per_table 1024 end - # the maximum number of indexes per table + # Returns the maximum number of indexes per table. def indexes_per_table 16 end - # the maximum number of columns in a multicolumn index + # Returns the maximum number of columns in a multicolumn index. def columns_per_multicolumn_index 16 end - # the maximum number of elements in an IN (x,y,z) clause. nil means no limit + # Returns the maximum number of elements in an IN (x,y,z) clause. + # nil means no limit. def in_clause_length nil end - # the maximum length of an SQL query + # Returns the maximum length of an SQL query. def sql_query_length 1048575 end - # maximum number of joins in a single query + # Returns maximum number of joins in a single query. def joins_per_query 256 end 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 70da9d5f1e..3045e30407 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -55,13 +55,27 @@ module ActiveRecord def exec_query(sql, name = 'SQL', binds = []) end - # Executes insert +sql+ statement in the context of this connection using + # 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 + # Executes delete +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_delete(sql, name, binds) + exec_query(sql, name, binds) + end + + # Executes update +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_update(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 @@ -77,13 +91,13 @@ module ActiveRecord end # Executes the update statement and returns the number of rows affected. - def update(sql, name = nil) - update_sql(sql, name) + def update(sql, name = nil, binds = []) + exec_update(sql, name, binds) end # Executes the delete statement and returns the number of rows affected. - def delete(sql, name = nil) - delete_sql(sql, name) + def delete(sql, name = nil, binds = []) + exec_delete(sql, name, binds) end # Checks whether there is currently no transaction active. This is done diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 1db397f584..093c30aa42 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -29,6 +29,14 @@ module ActiveRecord @query_cache_enabled = old end + def enable_query_cache! + @query_cache_enabled = true + end + + def disable_query_cache! + @query_cache_enabled = false + end + # Disable the query cache within the block. def uncached old, @query_cache_enabled = @query_cache_enabled, false diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 7ac48c6646..70a8f6bb58 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -104,7 +104,7 @@ module ActiveRecord # Available options are (none of these exists by default): # * <tt>:limit</tt> - # Requests a maximum column length. This is number of characters for <tt>:string</tt> and - # <tt>:text</tt> columns and number of bytes for :binary and :integer columns. + # <tt>:text</tt> columns and number of bytes for <tt>:binary</tt> and <tt>:integer</tt> columns. # * <tt>:default</tt> - # The column's default value. Use nil for NULL. # * <tt>:null</tt> - @@ -153,7 +153,7 @@ module ActiveRecord # This method returns <tt>self</tt>. # # == Examples - # # Assuming td is an instance of TableDefinition + # # Assuming +td+ is an instance of TableDefinition # td.column(:granted, :boolean) # # granted BOOLEAN # @@ -204,7 +204,7 @@ module ActiveRecord # end # # There's a short-hand method for each of the type values declared at the top. And then there's - # TableDefinition#timestamps that'll add created_at and +updated_at+ as datetimes. + # TableDefinition#timestamps that'll add +created_at+ and +updated_at+ as datetimes. # # TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type # column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of @@ -351,7 +351,7 @@ module ActiveRecord @base.index_exists?(@table_name, column_name, options) end - # Adds timestamps (created_at and updated_at) columns to the table. See SchemaStatements#add_timestamps + # Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps # ===== Example # t.timestamps def timestamps @@ -398,7 +398,7 @@ module ActiveRecord @base.remove_index(@table_name, options) end - # Removes the timestamp columns (created_at and updated_at) from the table. + # Removes the timestamp columns (+created_at+ and +updated_at+) from the table. # ===== Example # t.remove_timestamps def remove_timestamps @@ -412,7 +412,7 @@ module ActiveRecord @base.rename_column(@table_name, column_name, new_column_name) end - # Adds a reference. Optionally adds a +type+ column. + # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided. # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. # ===== Examples # t.references(:goat) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 8bae50885f..9f9c2c42cb 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -17,6 +17,10 @@ module ActiveRecord # def tables(name = nil) end + # Checks to see if the table +table_name+ exists on the database. + # + # === Example + # table_exists?(:developers) def table_exists?(table_name) tables.include?(table_name.to_s) end @@ -24,7 +28,7 @@ module ActiveRecord # Returns an array of indexes for the given table. # def indexes(table_name, name = nil) end - # Checks to see if an index exists on a table for a given index definition + # Checks to see if an index exists on a table for a given index definition. # # === Examples # # Check an index exists diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 468a2b106b..65024d76f8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -46,34 +46,34 @@ module ActiveRecord @instrumenter = ActiveSupport::Notifications.instrumenter end - # Returns the human-readable name of the adapter. Use mixed case - one + # Returns the human-readable name of the adapter. Use mixed case - one # can always use downcase if needed. def adapter_name 'Abstract' end - # Does this adapter support migrations? Backend specific, as the + # Does this adapter support migrations? Backend specific, as the # abstract adapter always returns +false+. def supports_migrations? false end # Can this adapter determine the primary key for tables not attached - # to an Active Record class, such as join tables? Backend specific, as + # to an Active Record 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+ + # Does this adapter support using DISTINCT within COUNT? This is +true+ # for all adapters except sqlite. def supports_count_distinct? true end - # Does this adapter support DDL rollbacks in transactions? That is, would - # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL, - # SQL Server, and others support this. MySQL and others do not. + # Does this adapter support DDL rollbacks in transactions? That is, would + # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL, + # SQL Server, and others support this. MySQL and others do not. def supports_ddl_transactions? false end @@ -89,7 +89,7 @@ module ActiveRecord end # Should primary key values be selected from their corresponding - # sequence before the insert statement? If true, next_sequence_value + # sequence before the insert statement? If true, next_sequence_value # is called before each insert to set the record's primary key. # This is false for all adapters but Firebird. def prefetch_primary_key?(table_name = nil) @@ -149,7 +149,7 @@ module ActiveRecord ### # Clear any caching the database adapter may be doing, for example - # clearing the prepared statement cache. This is database specific. + # clearing the prepared statement cache. This is database specific. def clear_cache! # this should be overridden by concrete adapters end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index cf68ddc2da..8af22fe9f5 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -5,6 +5,7 @@ require 'mysql2' module ActiveRecord class Base + # Establishes a connection to the database that's used by all Active Record objects. def self.mysql2_connection(config) config[:username] = 'root' if config[:username].nil? @@ -132,6 +133,7 @@ module ActiveRecord ADAPTER_NAME end + # Returns true, since this connection adapter supports migrations. def supports_migrations? true end @@ -140,6 +142,7 @@ module ActiveRecord true end + # Returns true, since this connection adapter supports savepoints. def supports_savepoints? true end @@ -211,6 +214,8 @@ module ActiveRecord false end + # Disconnects from the database if already connected. + # Otherwise, this method does nothing. def disconnect! unless @connection.nil? @connection.close @@ -290,6 +295,15 @@ module ActiveRecord execute sql.gsub('?') { quote(*binds.shift.reverse) }, name end + def exec_delete(sql, name, binds) + binds = binds.dup + + # Pretend to support bind parameters + execute sql.gsub('?') { quote(*binds.shift.reverse) }, name + @connection.affected_rows + end + alias :exec_update :exec_delete + def last_inserted_id(result) @connection.last_id end @@ -357,6 +371,8 @@ module ActiveRecord end end + # Drops the database specified on the +name+ attribute + # and creates it again using the provided +options+. def recreate_database(name, options = {}) drop_database(name) create_database(name, options) @@ -377,6 +393,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 @@ -395,22 +415,36 @@ module ActiveRecord show_variable 'collation_database' end - def tables(name = nil) - tables = [] - execute("SHOW TABLES", name).each do |field| - tables << field.first + def tables(name = nil, database = nil) #:nodoc: + sql = ["SHOW TABLES", database].compact.join(' IN ') + execute(sql, 'SCHEMA').collect do |field| + field.first end - tables + end + + def table_exists?(name) + return true if super + + name = name.to_s + schema, table = name.split('.', 2) + + unless table # A table was provided without a schema + table = schema + schema = nil + end + + tables(nil, schema).include? table end def drop_table(table_name, options = {}) super(table_name, options) end + # Returns an array of indexes for the given table. def indexes(table_name, name = nil) indexes = [] current_index = nil - result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name) + result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') result.each(:symbolize_keys => true, :as => :hash) do |row| if current_index != row[:Key_name] next if row[:Key_name] == PRIMARY # skip the primary key @@ -424,10 +458,11 @@ module ActiveRecord indexes end + # Returns an array of +Mysql2Column+ objects for the table specified by +table_name+. def columns(table_name, name = nil) sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}" columns = [] - result = execute(sql) + result = execute(sql, 'SCHEMA') result.each(:symbolize_keys => true, :as => :hash) { |field| columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES") } @@ -438,6 +473,10 @@ module ActiveRecord super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB")) end + # Renames a table. + # + # Example: + # rename_table('octopuses', 'octopi') def rename_table(table_name, new_name) execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}" end @@ -517,14 +556,16 @@ module ActiveRecord end end + # SHOW VARIABLES LIKE 'name'. def show_variable(name) variables = select_all("SHOW VARIABLES LIKE '#{name}'") variables.first['Value'] unless variables.empty? end + # Returns a table's primary key and belonging sequence. def pk_and_sequence_for(table) keys = [] - result = execute("describe #{quote_table_name(table)}") + result = execute("DESCRIBE #{quote_table_name(table)}", 'SCHEMA') result.each(:symbolize_keys => true, :as => :hash) do |row| keys << row[:Field] if row[:Key] == "PRI" end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 1136dbc45e..a9f4c08348 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -3,15 +3,8 @@ require 'active_support/core_ext/kernel/requires' require 'active_support/core_ext/object/blank' require 'set' -begin - require 'mysql' -rescue LoadError - raise "!!! Missing the mysql gem. Add it to your Gemfile: gem 'mysql'" -end - -unless defined?(Mysql::Result) && Mysql::Result.method_defined?(:each_hash) - raise "!!! Outdated mysql gem. Upgrade to 2.8.1 or later. In your Gemfile: gem 'mysql', '2.8.1'. Or use gem 'mysql2'" -end +gem 'mysql', '~> 2.8.1' +require 'mysql' class Mysql class Time @@ -208,13 +201,13 @@ module ActiveRecord true end - # Returns +true+, since this connection adapter supports prepared statement + # Returns true, since this connection adapter supports prepared statement # caching. def supports_statement_cache? true end - # Returns true. + # Returns true, since this connection adapter supports migrations. def supports_migrations? #:nodoc: true end @@ -224,6 +217,7 @@ module ActiveRecord true end + # Returns true, since this connection adapter supports savepoints. def supports_savepoints? #:nodoc: true end @@ -401,44 +395,9 @@ module ActiveRecord def exec_query(sql, name = 'SQL', binds = []) log(sql, name, binds) do - result = nil - - cache = {} - if binds.empty? - stmt = @connection.prepare(sql) - else - cache = @statements[sql] ||= { - :stmt => @connection.prepare(sql) - } - stmt = cache[:stmt] - end - - - begin - stmt.execute(*binds.map { |col, val| type_cast(val, col) }) - rescue Mysql::Error => e - # Older versions of MySQL leave the prepared statement in a bad - # place when an error occurs. To support older mysql versions, we - # need to close the statement and delete the statement from the - # cache. - stmt.close - @statements.delete sql - raise e + exec_stmt(sql, name, binds) do |cols, stmt| + ActiveRecord::Result.new(cols, stmt.to_a) if cols end - - if metadata = stmt.result_metadata - cols = cache[:cols] ||= metadata.fetch_fields.map { |field| - field.name - } - - metadata.free - result = ActiveRecord::Result.new(cols, stmt.to_a) - end - - stmt.free_result - stmt.close if binds.empty? - - result end end @@ -490,6 +449,15 @@ module ActiveRecord @connection.affected_rows end + def exec_delete(sql, name, binds) + log(sql, name, binds) do + exec_stmt(sql, name, binds) do |cols, stmt| + stmt.affected_rows + end + end + end + alias :exec_update :exec_delete + def begin_db_transaction #:nodoc: exec_without_stmt "BEGIN" rescue Mysql::Error @@ -549,6 +517,8 @@ module ActiveRecord end.join("") end + # Drops the database specified on the +name+ attribute + # and creates it again using the provided +options+. def recreate_database(name, options = {}) #:nodoc: drop_database(name) create_database(name, options) @@ -592,9 +562,8 @@ module ActiveRecord end def tables(name = nil, database = nil) #:nodoc: - tables = [] result = execute(["SHOW TABLES", database].compact.join(' IN '), 'SCHEMA') - result.each { |field| tables << field[0] } + tables = result.collect { |field| field[0] } result.free tables end @@ -617,6 +586,7 @@ module ActiveRecord super(table_name, options) end + # Returns an array of indexes for the given table. def indexes(table_name, name = nil)#:nodoc: indexes = [] current_index = nil @@ -635,11 +605,11 @@ module ActiveRecord indexes end + # Returns an array of +MysqlColumn+ objects for the table specified by +table_name+. def columns(table_name, name = nil)#:nodoc: sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}" - columns = [] result = execute(sql, 'SCHEMA') - result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") } + columns = result.collect { |field| MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") } result.free columns end @@ -648,6 +618,10 @@ module ActiveRecord super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB")) end + # Renames a table. + # + # Example: + # rename_table('octopuses', 'octopi') def rename_table(table_name, new_name) execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}" end @@ -843,6 +817,46 @@ module ActiveRecord end private + def exec_stmt(sql, name, binds) + cache = {} + if binds.empty? + stmt = @connection.prepare(sql) + else + cache = @statements[sql] ||= { + :stmt => @connection.prepare(sql) + } + stmt = cache[:stmt] + end + + + begin + stmt.execute(*binds.map { |col, val| type_cast(val, col) }) + rescue Mysql::Error => e + # Older versions of MySQL leave the prepared statement in a bad + # place when an error occurs. To support older mysql versions, we + # need to close the statement and delete the statement from the + # cache. + stmt.close + @statements.delete sql + raise e + end + + cols = nil + if metadata = stmt.result_metadata + cols = cache[:cols] ||= metadata.fetch_fields.map { |field| + field.name + } + end + + result = yield [cols, stmt] + + stmt.result_metadata.free if cols + stmt.free_result + stmt.close if binds.empty? + + result + end + def connect encoding = @config[:encoding] if encoding @@ -885,6 +899,7 @@ module ActiveRecord version[0] >= 5 end + # Returns the version of the connected MySQL server. def version @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i } end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 0c2afc180b..37db2be7a9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -122,6 +122,14 @@ module ActiveRecord # Extracts the value from a PostgreSQL column default definition. def self.extract_value_from_default(default) case default + # This is a performance optimization for Ruby 1.9.2 in development. + # If the value is nil, we return nil straight away without checking + # the regular expressions. If we check each regular expression, + # Regexp#=== will call NilClass#to_str, which will trigger + # method_missing (defined by whiny nil in ActiveSupport) which + # makes this method very very slow. + when NilClass + nil # Numeric types when /\A\(?(-?\d+(\.\d*)?\)?)\z/ $1 @@ -237,7 +245,6 @@ module ActiveRecord # @local_tz is initialized as nil to avoid warnings when connect tries to use it @local_tz = nil @table_alias_length = nil - @postgresql_version = nil @statements = {} connect @@ -259,28 +266,16 @@ module ActiveRecord # Is this connection alive and ready for queries? def active? - if @connection.respond_to?(:status) - @connection.status == PGconn::CONNECTION_OK - else - # We're asking the driver, not Active Record, so use @connection.query instead of #query - @connection.query 'SELECT 1' - true - end - # postgres-pr raises a NoMethodError when querying if no connection is available. - rescue PGError, NoMethodError + @connection.status == PGconn::CONNECTION_OK + rescue PGError false end # Close then reopen the connection. def reconnect! - if @connection.respond_to?(:reset) - clear_cache! - @connection.reset - configure_connection - else - disconnect! - connect - end + clear_cache! + @connection.reset + configure_connection end def reset! @@ -299,7 +294,7 @@ module ActiveRecord NATIVE_DATABASE_TYPES end - # Does PostgreSQL support migrations? + # Returns true, since this connection adapter supports migrations. def supports_migrations? true end @@ -325,6 +320,7 @@ module ActiveRecord true end + # Returns true, since this connection adapter supports savepoints. def supports_savepoints? true end @@ -433,17 +429,17 @@ module ActiveRecord # REFERENTIAL INTEGRITY ==================================== - def supports_disable_referential_integrity?() #:nodoc: + def supports_disable_referential_integrity? #:nodoc: true end def disable_referential_integrity #:nodoc: - if supports_disable_referential_integrity?() then + if supports_disable_referential_integrity? then execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) end yield ensure - if supports_disable_referential_integrity?() then + if supports_disable_referential_integrity? then execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";")) end end @@ -517,12 +513,7 @@ module ActiveRecord # Queries the database and returns the results in an Array-like object def query(sql, name = nil) #:nodoc: log(sql, name) do - if @async - res = @connection.async_exec(sql) - else - res = @connection.exec(sql) - end - return result_as_array(res) + result_as_array @connection.async_exec(sql) end end @@ -530,11 +521,7 @@ module ActiveRecord # or raising a PGError exception otherwise. def execute(sql, name = nil) log(sql, name) do - if @async - @connection.async_exec(sql) - else - @connection.exec(sql) - end + @connection.async_exec(sql) end end @@ -543,30 +530,27 @@ module ActiveRecord end def exec_query(sql, name = 'SQL', binds = []) - return exec_no_cache(sql, name) if binds.empty? - log(sql, name, binds) do - unless @statements.key? sql - nextkey = "a#{@statements.length + 1}" - @connection.prepare nextkey, sql - @statements[sql] = nextkey - end + result = binds.empty? ? exec_no_cache(sql, binds) : + exec_cache(sql, binds) - key = @statements[sql] - - # Clear the queue - @connection.get_last_result - @connection.send_query_prepared(key, binds.map { |col, val| - type_cast(val, col) - }) - @connection.block - result = @connection.get_last_result ret = ActiveRecord::Result.new(result.fields, result_as_array(result)) result.clear return ret end end + def exec_delete(sql, name = 'SQL', binds = []) + log(sql, name, binds) do + result = binds.empty? ? exec_no_cache(sql, binds) : + exec_cache(sql, binds) + affected = result.cmd_tuples + result.clear + affected + end + end + alias :exec_update :exec_delete + def sql_for_insert(sql, pk, id_value, sequence_name, binds) unless pk _, table = extract_schema_and_table(sql.split(" ", 4)[2]) @@ -699,7 +683,7 @@ module ActiveRecord [schema, table] end - # Returns the list of all indexes for a table. + # Returns an array of indexes for the given table. def indexes(table_name, name = nil) schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',') result = query(<<-SQL, name) @@ -864,6 +848,9 @@ module ActiveRecord end # Renames a table. + # + # Example: + # rename_table('octopuses', 'octopi') def rename_table(name, new_name) execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}" end @@ -948,24 +935,9 @@ module ActiveRecord end protected - # Returns the version of the connected PostgreSQL version. + # Returns the version of the connected PostgreSQL server. def postgresql_version - @postgresql_version ||= - if @connection.respond_to?(:server_version) - @connection.server_version - else - # Mimic PGconn.server_version behavior - begin - if query('SELECT version()')[0][0] =~ /PostgreSQL ([0-9.]+)/ - major, minor, tiny = $1.split(".") - (major.to_i * 10000) + (minor.to_i * 100) + tiny.to_i - else - 0 - end - rescue - 0 - end - end + @connection.server_version end def translate_exception(exception, message) @@ -980,13 +952,26 @@ module ActiveRecord end private - def exec_no_cache(sql, name) - log(sql, name) do - result = @connection.async_exec(sql) - ret = ActiveRecord::Result.new(result.fields, result_as_array(result)) - result.clear - ret + def exec_no_cache(sql, binds) + @connection.async_exec(sql) + end + + def exec_cache(sql, binds) + unless @statements.key? sql + nextkey = "a#{@statements.length + 1}" + @connection.prepare nextkey, sql + @statements[sql] = nextkey end + + key = @statements[sql] + + # Clear the queue + @connection.get_last_result + @connection.send_query_prepared(key, binds.map { |col, val| + type_cast(val, col) + }) + @connection.block + @connection.get_last_result end # The internal PostgreSQL identifier of the money data type. @@ -998,10 +983,6 @@ module ActiveRecord # connected server's characteristics. def connect @connection = PGconn.connect(*@connection_parameters) - PGconn.translate_results = false if PGconn.respond_to?(:translate_results=) - - # Ignore async_exec and async_query when using postgres-pr. - @async = @connection.respond_to?(:async_exec) # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision @@ -1015,11 +996,7 @@ module ActiveRecord # This is called by #connect and should not be called manually. def configure_connection if @config[:encoding] - if @connection.respond_to?(:set_client_encoding) - @connection.set_client_encoding(@config[:encoding]) - else - execute("SET client_encoding TO '#{@config[:encoding]}'") - end + @connection.set_client_encoding(@config[:encoding]) end self.client_min_messages = @config[:min_messages] if @config[:min_messages] self.schema_search_path = @config[:schema_search_path] || @config[:schema_order] diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index ed5006dcec..d2785b234a 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -58,10 +58,12 @@ module ActiveRecord 'SQLite' end + # Returns true if SQLite version is '2.0.0' or greater, false otherwise. def supports_ddl_transactions? sqlite_version >= '2.0.0' end + # Returns true if SQLite version is '3.6.8' or greater, false otherwise. def supports_savepoints? sqlite_version >= '3.6.8' end @@ -72,7 +74,7 @@ module ActiveRecord true end - # Returns true. + # Returns true, since this connection adapter supports migrations. def supports_migrations? #:nodoc: true end @@ -86,6 +88,7 @@ module ActiveRecord true end + # Returns true if SQLite version is '3.1.6' or greater, false otherwise. def supports_add_column? sqlite_version >= '3.1.6' end @@ -103,10 +106,12 @@ module ActiveRecord @statements.clear end + # Returns true if SQLite version is '3.2.6' or greater, false otherwise. def supports_count_distinct? #:nodoc: sqlite_version >= '3.2.6' end + # Returns true if SQLite version is '3.1.0' or greater, false otherwise. def supports_autoincrement? #:nodoc: sqlite_version >= '3.1.0' end @@ -178,6 +183,12 @@ module ActiveRecord end end + def exec_delete(sql, name = 'SQL', binds = []) + exec_query(sql, name, binds) + @connection.changes + end + alias :exec_update :exec_delete + def last_inserted_id(result) @connection.last_insert_row_id end @@ -244,6 +255,7 @@ module ActiveRecord end end + # Returns an array of +SQLiteColumn+ objects for the table specified by +table_name+. def columns(table_name, name = nil) #:nodoc: table_structure(table_name).map do |field| case field["dflt_value"] @@ -259,6 +271,7 @@ module ActiveRecord end end + # Returns an array of indexes for the given table. def indexes(table_name, name = nil) #:nodoc: exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row| IndexDefinition.new( @@ -282,6 +295,10 @@ module ActiveRecord exec_query "DROP INDEX #{quote_column_name(index_name)}" end + # Renames a table. + # + # Example: + # rename_table('octopuses', 'octopi') def rename_table(name, new_name) exec_query "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}" end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 0939ec2626..96fea741e0 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -509,7 +509,9 @@ class Fixtures # FIXME: Apparently JK uses this. connection = block_given? ? yield : ActiveRecord::Base.connection - files_to_read = table_names.reject { |table_name| fixture_is_cached?(connection, table_name) } + files_to_read = table_names.reject { |table_name| + fixture_is_cached?(connection, table_name) + } unless files_to_read.empty? connection.disable_referential_integrity do @@ -521,7 +523,7 @@ class Fixtures fixtures_map[path] = Fixtures.new( connection, table_name, - class_names[table_name.to_sym], + class_names[table_name.to_sym] || table_name.classify, File.join(fixtures_directory, path)) end diff --git a/activerecord/lib/active_record/identity_map.rb b/activerecord/lib/active_record/identity_map.rb index 95a8e5cff7..9eb47ad99f 100644 --- a/activerecord/lib/active_record/identity_map.rb +++ b/activerecord/lib/active_record/identity_map.rb @@ -49,12 +49,15 @@ module ActiveRecord end def get(klass, primary_key) - obj = repository[klass.symbolized_base_class][primary_key] - if obj.is_a?(klass) - if ActiveRecord::Base.logger - ActiveRecord::Base.logger.debug "#{klass} with ID = #{primary_key} loaded from Identity Map" - end - obj + record = repository[klass.symbolized_base_class][primary_key] + + if record.is_a?(klass) + ActiveSupport::Notifications.instrument("identity.active_record", + :line => "From Identity Map (id: #{primary_key})", + :name => "#{klass} Loaded", + :connection_id => object_id) + + record else nil end @@ -95,14 +98,33 @@ module ActiveRecord end class Middleware + class Body #:nodoc: + def initialize(target, original) + @target = target + @original = original + end + + def each(&block) + @target.each(&block) + end + + def close + @target.close if @target.respond_to?(:close) + ensure + IdentityMap.enabled = @original + IdentityMap.clear + end + end + def initialize(app) @app = app end def call(env) - ActiveRecord::IdentityMap.use do - @app.call(env) - end + enabled = IdentityMap.enabled + IdentityMap.enabled = true + status, headers, body = @app.call(env) + [status, headers, Body.new(body, enabled)] end end end diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index d31e321440..3a015ee8c2 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -46,6 +46,15 @@ module ActiveRecord debug " #{name} #{sql}#{binds}" end + def identity(event) + return unless logger.debug? + + name = color(event.payload[:name], odd? ? CYAN : MAGENTA, true) + line = odd? ? color(event.payload[:line], nil, true) : event.payload[:line] + + debug " #{name} #{line}" + end + def odd? @odd_or_even = !@odd_or_even end diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb index 0893d7e337..c723436330 100644 --- a/activerecord/lib/active_record/observer.rb +++ b/activerecord/lib/active_record/observer.rb @@ -110,8 +110,8 @@ module ActiveRecord next unless respond_to?(callback) callback_meth = :"_notify_#{observer_name}_for_#{callback}" unless klass.respond_to?(callback_meth) - klass.send(:define_method, callback_meth) do - observer.send(callback, self) + klass.send(:define_method, callback_meth) do |&block| + observer.send(callback, self, &block) end klass.send(callback, callback_meth) end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 787ac977e0..b4531ed35f 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -77,7 +77,15 @@ module ActiveRecord def destroy if persisted? IdentityMap.remove(self) if IdentityMap.enabled? - self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all + pk = self.class.primary_key + column = self.class.columns_hash[pk] + substitute = connection.substitute_at(column, 0) + + relation = self.class.unscoped.where( + self.class.arel_table[pk].eq(substitute)) + + relation.bind_values = [[column, id]] + relation.delete_all end @destroyed = true diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index d9f85a4e5e..4e61671473 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -27,10 +27,32 @@ module ActiveRecord @app = app end - def call(env) - ActiveRecord::Base.cache do - @app.call(env) + class BodyProxy # :nodoc: + def initialize(original_cache_value, target) + @original_cache_value = original_cache_value + @target = target + end + + def each(&block) + @target.each(&block) + end + + def close + @target.close if @target.respond_to?(:close) + ensure + ActiveRecord::Base.connection.clear_query_cache + unless @original_cache_value + ActiveRecord::Base.connection.disable_query_cache! + end end end + + def call(env) + old = ActiveRecord::Base.connection.query_cache_enabled + ActiveRecord::Base.connection.enable_query_cache! + + status, headers, body = @app.call(env) + [status, headers, BodyProxy.new(old, body)] + end end end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index d38588519b..bae2ded244 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -26,10 +26,12 @@ module ActiveRecord load "active_record/railties/databases.rake" end - # When loading console, force ActiveRecord to be loaded to avoid cross - # references when loading a constant for the first time. - console do - ActiveRecord::Base + # When loading console, force ActiveRecord::Base to be loaded + # to avoid cross references when loading a constant for the + # first time. Also, make it output to STDERR. + console do |sandbox| + require "active_record/railties/console_sandbox" if sandbox + ActiveRecord::Base.logger = Logger.new(STDERR) end initializer "active_record.initialize_timezone" do diff --git a/activerecord/lib/active_record/railties/console_sandbox.rb b/activerecord/lib/active_record/railties/console_sandbox.rb new file mode 100644 index 0000000000..65a3d68619 --- /dev/null +++ b/activerecord/lib/active_record/railties/console_sandbox.rb @@ -0,0 +1,6 @@ +ActiveRecord::Base.connection.increment_open_transactions +ActiveRecord::Base.connection.begin_db_transaction +at_exit do + ActiveRecord::Base.connection.rollback_db_transaction + ActiveRecord::Base.connection.decrement_open_transactions +end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 7d76d7a19f..5703fac033 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -4,11 +4,11 @@ db_namespace = namespace :db do task :load_config => :rails_env do require 'active_record' ActiveRecord::Base.configurations = Rails.application.config.database_configuration - ActiveRecord::Migrator.migrations_paths = Rails.application.paths["db/migrate"].to_a + ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH) - if engine.paths["db/migrate"].existent - ActiveRecord::Migrator.migrations_paths += engine.paths["db/migrate"].to_a + if engine.paths['db/migrate'].existent + ActiveRecord::Migrator.migrations_paths += engine.paths['db/migrate'].to_a end end end @@ -143,7 +143,7 @@ db_namespace = namespace :db do end def local_database?(config, &block) - if config['host'].in?(["127.0.0.1", "localhost"]) || config['host'].blank? + if config['host'].in?(['127.0.0.1', 'localhost']) || config['host'].blank? yield else $stderr.puts "This task only modifies local databases. #{config['database']} is on a remote host." @@ -161,35 +161,35 @@ db_namespace = namespace :db do namespace :migrate do # desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).' task :redo => [:environment, :load_config] do - if ENV["VERSION"] - db_namespace["migrate:down"].invoke - db_namespace["migrate:up"].invoke + if ENV['VERSION'] + db_namespace['migrate:down'].invoke + db_namespace['migrate:up'].invoke else - db_namespace["rollback"].invoke - db_namespace["migrate"].invoke + db_namespace['rollback'].invoke + db_namespace['migrate'].invoke end end # desc 'Resets your database using your migrations for the current environment' - task :reset => ["db:drop", "db:create", "db:migrate"] + task :reset => ['db:drop', 'db:create', 'db:migrate'] # desc 'Runs the "up" for a given migration VERSION.' task :up => [:environment, :load_config] do - version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil - raise "VERSION is required" unless version + version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil + raise 'VERSION is required' unless version ActiveRecord::Migrator.run(:up, ActiveRecord::Migrator.migrations_paths, version) - db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby end # desc 'Runs the "down" for a given migration VERSION.' task :down => [:environment, :load_config] do - version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil - raise "VERSION is required" unless version + version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil + raise 'VERSION is required' unless version ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_paths, version) - db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby end - desc "Display status of migrations" + desc 'Display status of migrations' task :status => [:environment, :load_config] do config = ActiveRecord::Base.configurations[Rails.env || 'development'] ActiveRecord::Base.establish_connection(config) @@ -208,7 +208,7 @@ db_namespace = namespace :db do end # output puts "\ndatabase: #{config['database']}\n\n" - puts "#{"Status".center(8)} #{"Migration ID".ljust(14)} Migration Name" + puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name" puts "-" * 50 file_list.each do |file| puts "#{file[0].center(8)} #{file[1].ljust(14)} #{file[2].humanize}" @@ -224,14 +224,14 @@ db_namespace = namespace :db do task :rollback => [:environment, :load_config] do step = ENV['STEP'] ? ENV['STEP'].to_i : 1 ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_paths, step) - db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby end # desc 'Pushes the schema to the next version (specify steps w/ STEP=n).' task :forward => [:environment, :load_config] do step = ENV['STEP'] ? ENV['STEP'].to_i : 1 ActiveRecord::Migrator.forward(ActiveRecord::Migrator.migrations_paths, step) - db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby end # desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.' @@ -244,10 +244,10 @@ db_namespace = namespace :db do when /mysql/ ActiveRecord::Base.establish_connection(config) puts ActiveRecord::Base.connection.charset - when 'postgresql' + when /postgresql/ ActiveRecord::Base.establish_connection(config) puts ActiveRecord::Base.connection.encoding - when 'sqlite3' + when /sqlite/ ActiveRecord::Base.establish_connection(config) puts ActiveRecord::Base.connection.encoding else @@ -267,7 +267,7 @@ db_namespace = namespace :db do end end - desc "Retrieves the current schema version number" + desc 'Retrieves the current schema version number' task :version => :environment do puts "Current version: #{ActiveRecord::Migrator.current_version}" end @@ -301,8 +301,8 @@ db_namespace = namespace :db do require 'active_record/fixtures' ActiveRecord::Base.establish_connection(Rails.env) - base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures') - fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir + base_dir = File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten + fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.{yml,csv}"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file| Fixtures.create_fixtures(fixtures_dir, fixture_file) @@ -313,8 +313,8 @@ db_namespace = namespace :db do task :identify => :environment do require 'active_record/fixtures' - label, id = ENV["LABEL"], ENV["ID"] - raise "LABEL or ID required" if label.blank? && id.blank? + label, id = ENV['LABEL'], ENV['ID'] + raise 'LABEL or ID required' if label.blank? && id.blank? puts %Q(The fixture ID for "#{label}" is #{Fixtures.identify(label)}.) if label @@ -334,16 +334,16 @@ db_namespace = namespace :db do end namespace :schema do - desc "Create a db/schema.rb file that can be portably used against any DB supported by AR" + desc 'Create a db/schema.rb file that can be portably used against any DB supported by AR' task :dump => :load_config do require 'active_record/schema_dumper' File.open(ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb", "w") do |file| ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file) end - db_namespace["schema:dump"].reenable + db_namespace['schema:dump'].reenable end - desc "Load a schema.rb file into the database" + desc 'Load a schema.rb file into the database' task :load => :environment do file = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb" if File.exists?(file) @@ -355,29 +355,29 @@ db_namespace = namespace :db do end namespace :structure do - desc "Dump the database structure to an SQL file" + desc 'Dump the database structure to an SQL file' task :dump => :environment do abcs = ActiveRecord::Base.configurations - case abcs[Rails.env]["adapter"] - when /mysql/, "oci", "oracle" + case abcs[Rails.env]['adapter'] + when /mysql/, 'oci', 'oracle' ActiveRecord::Base.establish_connection(abcs[Rails.env]) File.open("#{Rails.root}/db/#{Rails.env}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump } - when "postgresql" - ENV['PGHOST'] = abcs[Rails.env]["host"] if abcs[Rails.env]["host"] - ENV['PGPORT'] = abcs[Rails.env]["port"].to_s if abcs[Rails.env]["port"] - ENV['PGPASSWORD'] = abcs[Rails.env]["password"].to_s if abcs[Rails.env]["password"] - search_path = abcs[Rails.env]["schema_search_path"] + when /postgresql/ + ENV['PGHOST'] = abcs[Rails.env]['host'] if abcs[Rails.env]['host'] + ENV['PGPORT'] = abcs[Rails.env]["port"].to_s if abcs[Rails.env]['port'] + ENV['PGPASSWORD'] = abcs[Rails.env]['password'].to_s if abcs[Rails.env]['password'] + search_path = abcs[Rails.env]['schema_search_path'] unless search_path.blank? search_path = search_path.split(",").map{|search_path| "--schema=#{search_path.strip}" }.join(" ") end - `pg_dump -i -U "#{abcs[Rails.env]["username"]}" -s -x -O -f db/#{Rails.env}_structure.sql #{search_path} #{abcs[Rails.env]["database"]}` - raise "Error dumping database" if $?.exitstatus == 1 - when "sqlite", "sqlite3" - dbfile = abcs[Rails.env]["database"] || abcs[Rails.env]["dbfile"] - `#{abcs[Rails.env]["adapter"]} #{dbfile} .schema > db/#{Rails.env}_structure.sql` - when "sqlserver" - `scptxfr /s #{abcs[Rails.env]["host"]} /d #{abcs[Rails.env]["database"]} /I /f db\\#{Rails.env}_structure.sql /q /A /r` - `scptxfr /s #{abcs[Rails.env]["host"]} /d #{abcs[Rails.env]["database"]} /I /F db\ /q /A /r` + `pg_dump -i -U "#{abcs[Rails.env]['username']}" -s -x -O -f db/#{Rails.env}_structure.sql #{search_path} #{abcs[Rails.env]['database']}` + raise 'Error dumping database' if $?.exitstatus == 1 + when /sqlite/ + dbfile = abcs[Rails.env]['database'] || abcs[Rails.env]['dbfile'] + `sqlite3 #{dbfile} .schema > db/#{Rails.env}_structure.sql` + when 'sqlserver' + `scptxfr /s #{abcs[Rails.env]['host']} /d #{abcs[Rails.env]['database']} /I /f db\\#{Rails.env}_structure.sql /q /A /r` + `scptxfr /s #{abcs[Rails.env]['host']} /d #{abcs[Rails.env]['database']} /I /F db\ /q /A /r` when "firebird" set_firebird_env(abcs[Rails.env]) db_string = firebird_db_string(abcs[Rails.env]) @@ -397,81 +397,81 @@ db_namespace = namespace :db do task :load => 'db:test:purge' do ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) ActiveRecord::Schema.verbose = false - db_namespace["schema:load"].invoke + db_namespace['schema:load'].invoke end # desc "Recreate the test database from the current environment's database schema" task :clone => %w(db:schema:dump db:test:load) # desc "Recreate the test databases from the development structure" - task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do + task :clone_structure => [ 'db:structure:dump', 'db:test:purge' ] do abcs = ActiveRecord::Base.configurations - case abcs["test"]["adapter"] + case abcs['test']['adapter'] when /mysql/ ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0') IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split("\n\n").each do |table| ActiveRecord::Base.connection.execute(table) end - when "postgresql" - ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"] - ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"] - ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"] - `psql -U "#{abcs["test"]["username"]}" -f #{Rails.root}/db/#{Rails.env}_structure.sql #{abcs["test"]["database"]} #{abcs["test"]["template"]}` - when "sqlite", "sqlite3" - dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"] - `#{abcs["test"]["adapter"]} #{dbfile} < #{Rails.root}/db/#{Rails.env}_structure.sql` - when "sqlserver" - `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{Rails.env}_structure.sql` - when "oci", "oracle" + when /postgresql/ + ENV['PGHOST'] = abcs['test']['host'] if abcs['test']['host'] + ENV['PGPORT'] = abcs['test']['port'].to_s if abcs['test']['port'] + ENV['PGPASSWORD'] = abcs['test']['password'].to_s if abcs['test']['password'] + `psql -U "#{abcs['test']['username']}" -f #{Rails.root}/db/#{Rails.env}_structure.sql #{abcs['test']['database']} #{abcs['test']['template']}` + when /sqlite/ + dbfile = abcs['test']['database'] || abcs['test']['dbfile'] + `sqlite3 #{dbfile} < #{Rails.root}/db/#{Rails.env}_structure.sql` + when 'sqlserver' + `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{Rails.env}_structure.sql` + when 'oci', 'oracle' ActiveRecord::Base.establish_connection(:test) IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split(";\n\n").each do |ddl| ActiveRecord::Base.connection.execute(ddl) end - when "firebird" - set_firebird_env(abcs["test"]) - db_string = firebird_db_string(abcs["test"]) + when 'firebird' + set_firebird_env(abcs['test']) + db_string = firebird_db_string(abcs['test']) sh "isql -i #{Rails.root}/db/#{Rails.env}_structure.sql #{db_string}" else - raise "Task not supported by '#{abcs["test"]["adapter"]}'" + raise "Task not supported by '#{abcs['test']['adapter']}'" end end # desc "Empty the test database" task :purge => :environment do abcs = ActiveRecord::Base.configurations - case abcs["test"]["adapter"] + case abcs['test']['adapter'] when /mysql/ ActiveRecord::Base.establish_connection(:test) - ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"], abcs["test"]) - when "postgresql" + ActiveRecord::Base.connection.recreate_database(abcs['test']['database'], abcs['test']) + when /postgresql/ ActiveRecord::Base.clear_active_connections! drop_database(abcs['test']) create_database(abcs['test']) - when "sqlite","sqlite3" - dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"] + when /sqlite/ + dbfile = abcs['test']['database'] || abcs['test']['dbfile'] File.delete(dbfile) if File.exist?(dbfile) - when "sqlserver" - dropfkscript = "#{abcs["test"]["host"]}.#{abcs["test"]["database"]}.DP1".gsub(/\\/,'-') - `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{dropfkscript}` - `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{Rails.env}_structure.sql` + when 'sqlserver' + dropfkscript = "#{abcs['test']['host']}.#{abcs['test']['database']}.DP1".gsub(/\\/,'-') + `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{dropfkscript}` + `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{Rails.env}_structure.sql` when "oci", "oracle" ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl| ActiveRecord::Base.connection.execute(ddl) end - when "firebird" + when 'firebird' ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.recreate_database! else - raise "Task not supported by '#{abcs["test"]["adapter"]}'" + raise "Task not supported by '#{abcs['test']['adapter']}'" end end # desc 'Check for pending migrations and load the test schema' task :prepare => 'db:abort_if_pending_migrations' do if defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank? - db_namespace[{ :sql => "test:clone_structure", :ruby => "test:load" }[ActiveRecord::Base.schema_format]].invoke + db_namespace[{ :sql => 'test:clone_structure', :ruby => 'test:load' }[ActiveRecord::Base.schema_format]].invoke end end end @@ -479,11 +479,11 @@ db_namespace = namespace :db do namespace :sessions do # desc "Creates a sessions migration for use with ActiveRecord::SessionStore" task :create => :environment do - raise "Task unavailable to this database (no migration support)" unless ActiveRecord::Base.connection.supports_migrations? + raise 'Task unavailable to this database (no migration support)' unless ActiveRecord::Base.connection.supports_migrations? require 'rails/generators' Rails::Generators.configure! require 'rails/generators/rails/session_migration/session_migration_generator' - Rails::Generators::SessionMigrationGenerator.start [ ENV["MIGRATION"] || "add_sessions_table" ] + Rails::Generators::SessionMigrationGenerator.start [ ENV['MIGRATION'] || 'add_sessions_table' ] end # desc "Clear the sessions table" @@ -496,13 +496,13 @@ end namespace :railties do namespace :install do # desc "Copies missing migrations from Railties (e.g. plugins, engines). You can specify Railties to use with FROM=railtie1,railtie2" - task :migrations => :"db:load_config" do - to_load = ENV["FROM"].blank? ? :all : ENV["FROM"].split(",").map {|n| n.strip } + task :migrations => :'db:load_config' do + to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map {|n| n.strip } railties = {} Rails.application.railties.all do |railtie| next unless to_load == :all || to_load.include?(railtie.railtie_name) - if railtie.respond_to?(:paths) && (path = railtie.paths["db/migrate"].first) + if railtie.respond_to?(:paths) && (path = railtie.paths['db/migrate'].first) railties[railtie.railtie_name] = path end end @@ -528,13 +528,13 @@ def drop_database(config) when /mysql/ ActiveRecord::Base.establish_connection(config) ActiveRecord::Base.connection.drop_database config['database'] - when /^sqlite/ + when /sqlite/ require 'pathname' path = Pathname.new(config['database']) file = path.absolute? ? path.to_s : File.join(Rails.root, path) FileUtils.rm(file) - when 'postgresql' + when /postgresql/ ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public')) ActiveRecord::Base.connection.drop_database config['database'] end @@ -545,8 +545,8 @@ def session_table_name end def set_firebird_env(config) - ENV["ISC_USER"] = config["username"].to_s if config["username"] - ENV["ISC_PASSWORD"] = config["password"].to_s if config["password"] + ENV['ISC_USER'] = config['username'].to_s if config['username'] + ENV['ISC_PASSWORD'] = config['password'].to_s if config['password'] end def firebird_db_string(config) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 8e5f66ec1d..ae9afad48a 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -220,7 +220,7 @@ module ActiveRecord stmt.take limit if limit stmt.order(*order) stmt.key = table[primary_key] - @klass.connection.update stmt.to_sql + @klass.connection.update stmt.to_sql, 'SQL', bind_values end end @@ -338,7 +338,9 @@ module ActiveRecord where(conditions).delete_all else statement = arel.compile_delete - affected = @klass.connection.delete statement.to_sql + affected = @klass.connection.delete( + statement.to_sql, 'SQL', bind_values) + reset affected end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 982b3d7e9f..2814771002 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -25,7 +25,18 @@ module ActiveRecord values = value.to_a.map { |x| x.is_a?(ActiveRecord::Base) ? x.id : x } - attribute.in(values) + + if values.include?(nil) + values = values.compact + if values.empty? + attribute.eq nil + else + attribute.in(values.compact).or attribute.eq(nil) + end + else + attribute.in(values) + end + when Range, Arel::Relation attribute.in(value) when ActiveRecord::Base diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index d73fce9fd0..de36dd20b3 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -30,7 +30,7 @@ module ActiveRecord include ActiveModel::Validations module ClassMethods - # Creates an object just like Base.create but calls save! instead of save + # Creates an object just like Base.create but calls <tt>save!</tt> instead of +save+ # so an exception is raised if the record is invalid. def create!(attributes = nil, &block) if attributes.is_a?(Array) @@ -44,13 +44,13 @@ module ActiveRecord end end - # The validation process on save can be skipped by passing :validate => false. The regular Base#save method is + # The validation process on save can be skipped by passing <tt>:validate => false</tt>. The regular Base#save method is # replaced with this when the validations module is mixed in, which it is by default. def save(options={}) perform_validations(options) ? super : false end - # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false + # Attempts to save the record just like Base#save but will raise a +RecordInvalid+ exception instead of returning false # if the record is not valid. def save!(options={}) perform_validations(options) ? super : raise(RecordInvalid.new(self)) diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index 0667be7d23..2c20dd997f 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -3,7 +3,7 @@ module ActiveRecord MAJOR = 3 MINOR = 1 TINY = 0 - PRE = "beta" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end |