diff options
Diffstat (limited to 'activerecord/lib')
16 files changed, 123 insertions, 87 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 9d39a0c49b..f23d881c7b 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1399,7 +1399,7 @@ module ActiveRecord primary_key = reflection.source_reflection.primary_key_name send(through.name).select("DISTINCT #{through.quoted_table_name}.#{primary_key}").map!(&:"#{primary_key}") else - send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map!(&:id) + send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").except(:includes).map!(&:id) end end end diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index dd44bd8d51..cf4594ad7f 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -8,7 +8,7 @@ module ActiveRecord include AttributeMethods::Write included do - if self < Timestamp + if self < ::ActiveRecord::Timestamp raise "You cannot include Dirty after Timestamp" end diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 411330dda2..82d94b848a 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -6,7 +6,7 @@ module ActiveRecord # Returns this record's primary key value wrapped in an Array # or nil if the record is a new_record? def to_key - new_record? ? nil : [ send(self.class.primary_key) ] + new_record? ? nil : [ id ] end module ClassMethods diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 650a91b385..c02af328c1 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -668,7 +668,6 @@ module ActiveRecord #:nodoc: name = "#{full_table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}" end - @quoted_table_name = nil set_table_name(name) name end @@ -702,6 +701,7 @@ module ActiveRecord #:nodoc: # set_table_name "project" # end def set_table_name(value = nil, &block) + @quoted_table_name = nil define_attr_method :table_name, value, &block end alias :table_name= :set_table_name @@ -1190,7 +1190,11 @@ module ActiveRecord #:nodoc: # default_scope order('last_name, first_name') # end def default_scope(options = {}) - self.default_scoping << construct_finder_arel(options) + self.default_scoping << construct_finder_arel(options, default_scoping.pop) + end + + def clear_default_scope + self.default_scoping.clear end def scoped_methods #:nodoc: @@ -1463,6 +1467,7 @@ module ActiveRecord #:nodoc: @attributes_cache = {} @new_record = true ensure_proper_type + @changed_attributes = other.changed_attributes.dup if scope = self.class.send(:current_scoped_methods) create_with = scope.scope_for_create 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 6c477e48ce..7d58bc2adf 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -258,7 +258,7 @@ module ActiveRecord end end - class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc: + class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths) #:nodoc: end # Abstract representation of a column definition. Instances of this type @@ -494,7 +494,7 @@ module ActiveRecord end %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type| - class_eval <<-EOV + class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{column_type}(*args) # def string(*args) options = args.extract_options! # options = args.extract_options! column_names = args # column_names = args @@ -694,7 +694,7 @@ module ActiveRecord # t.string(:goat) # t.string(:goat, :sheep) %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type| - class_eval <<-EOV + class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{column_type}(*args) # def string(*args) options = args.extract_options! # options = args.extract_options! column_names = args # column_names = args 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 e8cba1bd41..1255ef09be 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -256,18 +256,32 @@ module ActiveRecord # name. # # ===== Examples + # # ====== Creating a simple index # add_index(:suppliers, :name) # generates # CREATE INDEX suppliers_name_index ON suppliers(name) + # # ====== Creating a unique index # add_index(:accounts, [:branch_id, :party_id], :unique => true) # generates # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id) + # # ====== Creating a named index # add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party') # generates # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id) + # + # ====== Creating an index with specific key length + # add_index(:accounts, :name, :name => 'by_name', :length => 10) + # generates + # CREATE INDEX by_name ON accounts(name(10)) + # + # add_index(:accounts, [:name, :surname], :name => 'by_name_surname', :length => {:name => 10, :surname => 15}) + # generates + # CREATE INDEX by_name_surname ON accounts(name(10), surname(15)) + # + # Note: SQLite doesn't support index length def add_index(table_name, column_name, options = {}) column_names = Array.wrap(column_name) index_name = index_name(table_name, :column => column_names) @@ -278,7 +292,9 @@ module ActiveRecord else index_type = options end - quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ") + + quoted_column_names = quoted_columns_for_index(column_names, options).join(", ") + execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})" end @@ -430,6 +446,11 @@ module ActiveRecord end protected + # Overridden by the mysql adapter for supporting index lengths + def quoted_columns_for_index(column_names, options = {}) + column_names.map {|name| quote_column_name(name) } + end + def options_include_default?(options) options.include?(:default) && !(options[:null] == false && options[:default].nil?) end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 55d9d20bb5..e12924e63f 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -15,26 +15,26 @@ module MysqlCompat #:nodoc: # Ruby driver has a version string and returns null values in each_hash # C driver >= 2.7 returns null values in each_hash if Mysql.const_defined?(:VERSION) && (Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700) - target.class_eval <<-'end_eval' - def all_hashes # def all_hashes - rows = [] # rows = [] - each_hash { |row| rows << row } # each_hash { |row| rows << row } - rows # rows - end # end + target.class_eval <<-'end_eval', __FILE__, __LINE__ + 1 + def all_hashes # def all_hashes + rows = [] # rows = [] + each_hash { |row| rows << row } # each_hash { |row| rows << row } + rows # rows + end # end end_eval # adapters before 2.7 don't have a version constant # and don't return null values in each_hash else - target.class_eval <<-'end_eval' - def all_hashes # def all_hashes - rows = [] # rows = [] - all_fields = fetch_fields.inject({}) { |fields, f| # all_fields = fetch_fields.inject({}) { |fields, f| - fields[f.name] = nil; fields # fields[f.name] = nil; fields - } # } - each_hash { |row| rows << all_fields.dup.update(row) } # each_hash { |row| rows << all_fields.dup.update(row) } - rows # rows - end # end + target.class_eval <<-'end_eval', __FILE__, __LINE__ + 1 + def all_hashes # def all_hashes + rows = [] # rows = [] + all_fields = fetch_fields.inject({}) { |fields, f| # all_fields = fetch_fields.inject({}) { |fields, f| + fields[f.name] = nil; fields # fields[f.name] = nil; fields + } # } + each_hash { |row| rows << all_fields.dup.update(row) } # each_hash { |row| rows << all_fields.dup.update(row) } + rows # rows + end # end end_eval end @@ -461,10 +461,11 @@ module ActiveRecord if current_index != row[2] next if row[2] == "PRIMARY" # skip the primary key current_index = row[2] - indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", []) + indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [], []) end indexes.last.columns << row[4] + indexes.last.lengths << row[7] end result.free indexes @@ -512,7 +513,7 @@ module ActiveRecord def change_column(table_name, column_name, type, options = {}) #:nodoc: column = column_for(table_name, column_name) - unless options_include_default?(options) + if has_default?(type) && !options_include_default?(options) options[:default] = column.default end @@ -594,6 +595,18 @@ module ActiveRecord end protected + def quoted_columns_for_index(column_names, options = {}) + length = options[:length] if options.is_a?(Hash) + + quoted_column_names = case length + when Hash + column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) } + when Fixnum + column_names.map {|name| "#{quote_column_name(name)}(#{length})"} + else + column_names.map {|name| quote_column_name(name) } + end + end def translate_exception(exception, message) return super unless exception.respond_to?(:errno) @@ -662,6 +675,10 @@ module ActiveRecord end column end + + def has_default?(sql_type) + sql_type =~ :binary || sql_type == :text #mysql forbids defaults on blob and text columns + end end end end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 4bf33c3856..8099aaa7f7 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -891,6 +891,7 @@ module ActiveRecord instances.size == 1 ? instances.first : instances end + private table_name end end diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 71057efa15..ceb0902fde 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -113,12 +113,11 @@ module ActiveRecord lock_col = self.class.locking_column previous_value = send(lock_col).to_i - affected_rows = connection.delete( - "DELETE FROM #{self.class.quoted_table_name} " + - "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id} " + - "AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}", - "#{self.class.name} Destroy" - ) + table = self.class.arel_table + predicate = table[self.class.primary_key].eq(id) + predicate = predicate.and(table[self.class.locking_column].eq(previous_value)) + + affected_rows = self.class.unscoped.where(predicate).delete_all unless affected_rows == 1 raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object: #{self.class.name}" diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index aeed52e72a..3d8f4a030b 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -99,11 +99,7 @@ module ActiveRecord block_given? ? relation.extending(Module.new(&block)) : relation end - singleton_class.instance_eval do - define_method name do |*args| - scopes[name].call(*args) - end - end + singleton_class.send :define_method, name, &scopes[name] end def named_scope(*args, &block) diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 898df0a67a..a32fb7d399 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -15,6 +15,12 @@ module ActiveRecord config.generators.orm :active_record, :migration => true, :timestamps => true + config.app_middleware.insert_after "::ActionDispatch::Callbacks", + "ActiveRecord::QueryCache" + + config.app_middleware.insert_after "::ActionDispatch::Callbacks", + "ActiveRecord::ConnectionAdapters::ConnectionManagement" + rake_tasks do load "active_record/railties/databases.rake" end @@ -58,16 +64,9 @@ module ActiveRecord end end - # Setup database middleware after initializers have run - initializer "active_record.initialize_database_middleware", :after => "action_controller.set_configs" do |app| - middleware = app.config.middleware - middleware.insert_after "::ActionDispatch::Callbacks", ActiveRecord::QueryCache - middleware.insert_after "::ActionDispatch::Callbacks", ActiveRecord::ConnectionAdapters::ConnectionManagement - end - initializer "active_record.set_dispatch_hooks", :before => :set_clear_dependencies_hook do |app| - ActiveSupport.on_load(:active_record) do - unless app.config.cache_classes + unless app.config.cache_classes + ActiveSupport.on_load(:active_record) do ActionDispatch::Callbacks.after do ActiveRecord::Base.reset_subclasses ActiveRecord::Base.clear_reloadable_connections! diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 1c61e7d450..412be895c4 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -71,7 +71,12 @@ module ActiveRecord yield records break if records.size < batch_size - records = relation.where(primary_key.gt(records.last.id)).all + + if primary_key_offset = records.last.id + records = relation.where(primary_key.gt(primary_key_offset)).all + else + raise "Primary key not included in the custom select clause" + end end end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 858d298470..44baeb6c84 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -45,9 +45,8 @@ module ActiveRecord calculate(:count, column_name, options) end - # Calculates the average value on a given column. The value is returned as - # a float, or +nil+ if there's no row. See +calculate+ for examples with - # options. + # Calculates the average value on a given column. Returns +nil+ if there's + # no row. See +calculate+ for examples with options. # # Person.average('age') # => 35.8 def average(column_name, options = {}) @@ -241,9 +240,9 @@ module ActiveRecord def type_cast_calculated_value(value, column, operation = nil) if value.is_a?(String) || value.nil? case operation - when 'count' then value.to_i - when 'sum' then type_cast_using_column(value || '0', column) - when 'average' then value && (value.is_a?(Fixnum) ? value.to_f : value).to_d + when 'count' then value.to_i + when 'sum' then type_cast_using_column(value || '0', column) + when 'average' then value.try(:to_d) else type_cast_using_column(value, column) end else diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 8d8bb659e1..6782554854 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -10,7 +10,7 @@ module ActiveRecord attr_accessor :"#{query_method}_values" next if [:where, :having].include?(query_method) - class_eval <<-CEVAL, __FILE__ + class_eval <<-CEVAL, __FILE__, __LINE__ + 1 def #{query_method}(*args, &block) new_relation = clone new_relation.send(:apply_modules, Module.new(&block)) if block_given? @@ -22,7 +22,7 @@ module ActiveRecord end [:where, :having].each do |query_method| - class_eval <<-CEVAL, __FILE__ + class_eval <<-CEVAL, __FILE__, __LINE__ + 1 def #{query_method}(*args, &block) new_relation = clone new_relation.send(:apply_modules, Module.new(&block)) if block_given? @@ -36,7 +36,7 @@ module ActiveRecord ActiveRecord::Relation::SINGLE_VALUE_METHODS.each do |query_method| attr_accessor :"#{query_method}_value" - class_eval <<-CEVAL, __FILE__ + class_eval <<-CEVAL, __FILE__, __LINE__ + 1 def #{query_method}(value = true, &block) new_relation = clone new_relation.send(:apply_modules, Module.new(&block)) if block_given? diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index c8e1b4f53a..cd54653581 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -178,6 +178,9 @@ HEADER statment_parts << (':name => ' + index.name.inspect) statment_parts << ':unique => true' if index.unique + index_lengths = index.lengths.compact if index.lengths.is_a?(Array) + statment_parts << (':length => ' + Hash[*index.columns.zip(index.lengths).flatten].inspect) if index_lengths.present? + ' ' + statment_parts.join(', ') end diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 55c4236874..be64e00bd1 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -36,49 +36,40 @@ module ActiveRecord # The validation process on save can be skipped by passing false. The regular Base#save method is # replaced with this when the validations module is mixed in, which it is by default. - def save(options=nil) - return super if valid?(options) - false - end - - def save_without_validation! - save!(:validate => false) + 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 # if the record is not valid. - def save!(options = nil) - return super if valid?(options) - raise RecordInvalid.new(self) + def save!(options={}) + perform_validations(options) ? super : raise(RecordInvalid.new(self)) end # Runs all the specified validations and returns true if no errors were added otherwise false. - def valid?(options = nil) - perform_validation = case options - when NilClass - true - when Hash - options[:validate] != false - else - ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller - options - end + def valid?(context = nil) + context ||= (new_record? ? :create : :update) + super(context) - if perform_validation - errors.clear + deprecated_callback_method(:validate) + deprecated_callback_method(:"validate_on_#{context}") - self.validation_context = new_record? ? :create : :update - _run_validate_callbacks + errors.empty? + end - deprecated_callback_method(:validate) + protected - if new_record? - deprecated_callback_method(:validate_on_create) - else - deprecated_callback_method(:validate_on_update) - end + def perform_validations(options={}) + perform_validation = case options + when Hash + options[:validate] != false + else + ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller + options + end - errors.empty? + if perform_validation + valid?(options.is_a?(Hash) ? options[:context] : nil) else true end |