diff options
Diffstat (limited to 'activerecord/lib')
13 files changed, 107 insertions, 62 deletions
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index be0af081d1..3005bef092 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -28,7 +28,7 @@ module ActiveRecord # Association with autosave option defines several callbacks on your # model (before_save, after_create, after_update). Please note that # callbacks are executed in the order they were defined in - # model. You should avoid modyfing the association content, before + # model. You should avoid modifying the association content, before # autosave callbacks are executed. Placing your callbacks after # associations is usually a good practice. # @@ -328,13 +328,14 @@ module ActiveRecord autosave = reflection.options[:autosave] if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave) + records_to_destroy = [] records.each do |record| next if record.destroyed? saved = true if autosave && record.marked_for_destruction? - association.proxy.destroy(record) + records_to_destroy << record elsif autosave != false && (@new_record_before_save || record.new_record?) if autosave saved = association.insert_record(record, false) @@ -347,6 +348,10 @@ module ActiveRecord raise ActiveRecord::Rollback unless saved end + + records_to_destroy.each do |record| + association.proxy.destroy(record) + end end # reconstruct the scope now that we know the owner's id diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 767c99de4c..1d713e472b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -284,26 +284,25 @@ module ActiveRecord protected - def log(sql, name = "SQL", binds = []) - @instrumenter.instrument( - "sql.active_record", - :sql => sql, - :name => name, - :connection_id => object_id, - :binds => binds) { yield } - rescue Exception => e - message = "#{e.class.name}: #{e.message}: #{sql}" - @logger.debug message if @logger - exception = translate_exception(e, message) - exception.set_backtrace e.backtrace - raise exception - end - - def translate_exception(e, message) - # override in derived class - ActiveRecord::StatementInvalid.new(message) - end - + def log(sql, name = "SQL", binds = []) + @instrumenter.instrument( + "sql.active_record", + :sql => sql, + :name => name, + :connection_id => object_id, + :binds => binds) { yield } + rescue Exception => e + message = "#{e.class.name}: #{e.message}: #{sql}" + @logger.error message if @logger + exception = translate_exception(e, message) + exception.set_backtrace e.backtrace + raise exception + end + + def translate_exception(e, message) + # override in derived class + ActiveRecord::StatementInvalid.new(message) + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 5b7fa029da..10a178e369 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1006,7 +1006,7 @@ module ActiveRecord # This should be not be called manually but set in database.yml. def schema_search_path=(schema_csv) if schema_csv - execute "SET search_path TO #{schema_csv}" + execute("SET search_path TO #{schema_csv}", 'SCHEMA') @schema_search_path = schema_csv end end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 46aababfb6..9a2f859fc7 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -176,7 +176,7 @@ module ActiveRecord assign_attributes(attributes, options) if attributes yield self if block_given? - run_callbacks :initialize + run_callbacks :initialize if _initialize_callbacks.any? end # Initialize an empty model object from +coder+. +coder+ must contain diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 5c42e8f719..9796b0a321 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -4,16 +4,12 @@ require 'zlib' require 'active_support/dependencies' require 'active_support/core_ext/object/blank' require 'active_record/fixtures/file' +require 'active_record/errors' -if defined? ActiveRecord +module ActiveRecord class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc: end -else - class FixtureClassNotFound < StandardError #:nodoc: - end -end -module ActiveRecord # \Fixtures are a way of organizing data that you want to test against; in short, sample data. # # They are stored in YAML files, one file per model, which are placed in the directory diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index ebe244c6a6..46d253b0a7 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -48,6 +48,20 @@ module ActiveRecord end # Set this to true if this is an abstract class (see <tt>abstract_class?</tt>). + # If you are using inheritance with ActiveRecord and don't want child classes + # to utilize the implied STI table name of the parent class, this will need to be true. + # For example, given the following: + # + # class SuperClass < ActiveRecord::Base + # self.abstract_class = true + # end + # class Child < SuperClass + # self.table_name = 'the_table_i_really_want' + # end + # + # + # <tt>self.abstract_class = true</tt> is required to make <tt>Child<.find,.create, or any Arel method></tt> use <tt>the_table_i_really_want</tt> instead of a table called <tt>super_classes</tt> + # attr_accessor :abstract_class # Returns whether this class is an abstract class or not. diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 99847ac161..c85d590ce1 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -160,6 +160,7 @@ module ActiveRecord # Sets the value of inheritance_column def inheritance_column=(value) @inheritance_column = value.to_s + @explicit_inheritance_column = true end def sequence_name @@ -303,7 +304,7 @@ module ActiveRecord @column_types = nil @content_columns = nil @dynamic_methods_hash = nil - @inheritance_column = nil + @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column @relation = nil end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index f26989ae57..f26e18b1e0 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -426,6 +426,7 @@ db_namespace = namespace :db do if ActiveRecord::Base.connection.supports_migrations? File.open(filename, "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information } end + db_namespace['structure:dump'].reenable end # desc "Recreate the databases from the structure.sql file" diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 6f39708ec3..b125449127 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -256,7 +256,7 @@ module ActiveRecord # # Update all books with 'Rails' in their title # Book.update_all "author = 'David'", "title LIKE '%Rails%'" # - # # Update all avatars migrated more than a week ago + # # Update all avatars migrated more recently than a week ago # Avatar.update_all ['migrated_at = ?', Time.now.utc], ['migrated_at > ?', 1.week.ago] # # # Update all books that match conditions, but limit it to 5 ordered by date @@ -507,6 +507,10 @@ module ActiveRecord end end + def blank? + to_a.blank? + end + private def references_eager_loaded_tables? diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index 5f05d146f2..b0609a8c08 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -1,4 +1,5 @@ require 'active_support/concern' +require 'active_support/deprecation' module ActiveRecord module Scoping @@ -51,7 +52,7 @@ module ActiveRecord # the model. # # class Article < ActiveRecord::Base - # default_scope where(:published => true) + # default_scope { where(:published => true) } # end # # Article.all # => SELECT * FROM articles WHERE published = true @@ -62,12 +63,6 @@ module ActiveRecord # Article.new.published # => true # Article.create.published # => true # - # You can also use <tt>default_scope</tt> with a block, in order to have it lazily evaluated: - # - # class Article < ActiveRecord::Base - # default_scope { where(:published_at => Time.now - 1.week) } - # end - # # (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.) # @@ -75,8 +70,8 @@ module ActiveRecord # be merged together: # # class Article < ActiveRecord::Base - # default_scope where(:published => true) - # default_scope where(:rating => 'G') + # default_scope { where(:published => true) } + # default_scope { where(:rating => 'G') } # end # # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G' @@ -94,6 +89,16 @@ module ActiveRecord # end def default_scope(scope = {}) scope = Proc.new if block_given? + + if scope.is_a?(Relation) || !scope.respond_to?(:call) + ActiveSupport::Deprecation.warn( + "Calling #default_scope without a block is deprecated. For example instead " \ + "of `default_scope where(color: 'red')`, please use " \ + "`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \ + "self.default_scope.)" + ) + end + self.default_scopes = default_scopes + [scope] end @@ -106,7 +111,7 @@ module ActiveRecord if scope.is_a?(Hash) default_scope.apply_finder_options(scope) elsif !scope.is_a?(Relation) && scope.respond_to?(:call) - default_scope.merge(scope.call) + default_scope.merge(unscoped { scope.call }) else default_scope.merge(scope) end diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index 0edc3f1dcc..077e2d067e 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -3,6 +3,7 @@ require 'active_support/core_ext/hash/except' require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/class/attribute' +require 'active_support/deprecation' module ActiveRecord # = Active Record Named \Scopes @@ -171,30 +172,30 @@ module ActiveRecord # Article.published.featured.latest_article # Article.featured.titles - def scope(name, scope_options = {}) - name = name.to_sym - valid_scope_name?(name) - extension = Module.new(&Proc.new) if block_given? + def scope(name, body = {}, &block) + extension = Module.new(&block) if block - scope_proc = lambda do |*args| - options = scope_options.respond_to?(:call) ? unscoped { scope_options.call(*args) } : scope_options + # Check body.is_a?(Relation) to prevent the relation actually being + # loaded by respond_to? + if body.is_a?(Relation) || !body.respond_to?(:call) + ActiveSupport::Deprecation.warn( + "Using #scope without passing a callable object is deprecated. For " \ + "example `scope :red, where(color: 'red')` should be changed to " \ + "`scope :red, -> { where(color: 'red') }`. There are numerous gotchas " \ + "in the former usage and it makes the implementation more complicated " \ + "and buggy. (If you prefer, you can just define a class method named " \ + "`self.red`.)" + ) + end + + singleton_class.send(:define_method, name) do |*args| + options = body.respond_to?(:call) ? unscoped { body.call(*args) } : body options = scoped.apply_finder_options(options) if options.is_a?(Hash) relation = scoped.merge(options) extension ? relation.extending(extension) : relation end - - singleton_class.send(:redefine_method, name, &scope_proc) - end - - protected - - def valid_scope_name?(name) - if respond_to?(name, true) - logger.warn "Creating scope :#{name}. " \ - "Overwriting existing method #{self.name}.#{name}." - end end end end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 9556878f63..db618f617f 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -35,8 +35,14 @@ module ActiveRecord relation = relation.and(table[scope_item].eq(scope_value)) end - if finder_class.unscoped.where(relation).exists? - record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value)) + relation = finder_class.unscoped.where(relation) + + if options[:conditions] + relation = relation.merge(options[:conditions]) + end + + if relation.exists? + record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope, :conditions).merge(:value => value)) end end @@ -102,6 +108,14 @@ module ActiveRecord # validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id] # end # + # It is also possible to limit the uniqueness constraint to a set of records matching certain conditions. + # In this example archived articles are not being taken into consideration when validating uniqueness + # of the title attribute: + # + # class Article < ActiveRecord::Base + # validates_uniqueness_of :title, :conditions => where('status != ?', 'archived') + # end + # # When the record is created, a check is performed to make sure that no record exists in the database # with the given value for the specified attribute (that maps to a column). When the record is updated, # the same check is made but disregarding the record itself. @@ -109,6 +123,8 @@ module ActiveRecord # Configuration options: # * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken"). # * <tt>:scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint. + # * <tt>:conditions</tt> - Specify the conditions to be included as a <tt>WHERE</tt> SQL fragment to limit + # the uniqueness constraint lookup. (e.g. <tt>:conditions => where('status = ?', 'active')</tt>) # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+true+ by default). # * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+). # * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+). diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb index d084a00ed7..9ca63a209e 100644 --- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb @@ -5,7 +5,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> <%- if attribute.has_index? -%> add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> - <%- end %> + <%- end -%> <%- end -%> end <%- else -%> @@ -24,6 +24,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration <% attributes.reverse.each do |attribute| -%> <%- if migration_action -%> <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><%= attribute.inject_options %><% end %> + <%- if attribute.has_index? && migration_action == 'remove' -%> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> + <%- end -%> <%- end -%> <%- end -%> end |