diff options
Diffstat (limited to 'activerecord')
43 files changed, 472 insertions, 300 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 38bcf0c787..ffff0b7e09 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,15 @@ *Edge* +* Allow relations to be used as scope. + + class Item + scope :red, where(:colour => 'red') + end + + Item.red.limit(10) # Ten red items + +* Rename named_scope to scope. [Pratik Naik] + * Changed ActiveRecord::Base.store_full_sti_class to be true by default reflecting the previously announced Rails 3 default [DHH] * Add Relation#except. [Pratik Naik] diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 468a6cd9f8..ebf1a41e85 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1701,30 +1701,19 @@ module ActiveRecord end def construct_finder_arel_with_included_associations(options, join_dependency) - relation = unscoped + relation = scoped for association in join_dependency.join_associations relation = association.join_relation(relation) end - relation = relation.joins(options[:joins]). - select(column_aliases(join_dependency)). - group(options[:group]). - having(options[:having]). - order(options[:order]). - where(options[:conditions]). - from(options[:from]) + relation = relation.apply_finder_options(options).select(column_aliases(join_dependency)) - scoped_relation = current_scoped_methods - scoped_relation_limit = scoped_relation.taken if scoped_relation - - relation = current_scoped_methods.except(:limit).merge(relation) if current_scoped_methods - - if !using_limitable_reflections?(join_dependency.reflections) && ((scoped_relation && scoped_relation.taken) || options[:limit]) + if !using_limitable_reflections?(join_dependency.reflections) && relation.limit_value relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency)) end - relation = relation.limit(options[:limit] || scoped_relation_limit) if using_limitable_reflections?(join_dependency.reflections) + relation = relation.except(:limit, :offset) unless using_limitable_reflections?(join_dependency.reflections) relation end @@ -1752,23 +1741,14 @@ module ActiveRecord end def construct_finder_sql_for_association_limiting(options, join_dependency) - relation = unscoped + relation = scoped for association in join_dependency.join_associations relation = association.join_relation(relation) end - relation = relation.joins(options[:joins]). - where(options[:conditions]). - group(options[:group]). - having(options[:having]). - order(options[:order]). - limit(options[:limit]). - offset(options[:offset]). - from(options[:from]) - - relation = current_scoped_methods.except(:select, :includes, :eager_load).merge(relation) if current_scoped_methods - relation = relation.select(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", options[:order])) + relation = relation.apply_finder_options(options).except(:select) + relation = relation.select(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", relation.order_values.join(", "))) relation.to_sql end diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 64dd5cf629..e9402d3547 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -403,8 +403,6 @@ module ActiveRecord else super end - elsif @reflection.klass.scopes.include?(method) - @reflection.klass.scopes[method].call(self, *args) else with_scope(construct_scope) do if block_given? diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index bd05d1014c..7f39a189e4 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -37,7 +37,7 @@ module ActiveRecord if force record.save! else - return false unless record.save(validate) + return false unless record.save(:validate => validate) end end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index d3336cf2d2..146a6ca55f 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -58,7 +58,7 @@ module ActiveRecord def insert_record(record, force = false, validate = true) set_belongs_to_association_for(record) - force ? record.save! : record.save(validate) + force ? record.save! : record.save(:validate => validate) end # Deletes the records according to the <tt>:dependent</tt> option. 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 387b85aacd..bd2acd4340 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -60,7 +60,7 @@ module ActiveRecord if force record.save! else - return false unless record.save(validate) + return false unless record.save(:validate => validate) end end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index e178cb4ef2..325a8aa7ec 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -116,14 +116,14 @@ module ActiveRecord # post = Post.find(1) # post.author.name = '' # post.save # => false - # post.errors # => #<ActiveRecord::Errors:0x174498c @errors={"author_name"=>["can't be blank"]}, @base=#<Post ...>> + # post.errors # => #<ActiveRecord::Errors:0x174498c @errors={"author.name"=>["can't be blank"]}, @base=#<Post ...>> # # No validations will be performed on the associated models when validations # are skipped for the parent: # # post = Post.find(1) # post.author.name = '' - # post.save(false) # => true + # post.save(:validate => false) # => true module AutosaveAssociation extend ActiveSupport::Concern @@ -302,7 +302,7 @@ module ActiveRecord association.send(:insert_record, record) end elsif autosave - saved = record.save(false) + saved = record.save(:validate => false) end raise ActiveRecord::Rollback if saved == false @@ -332,7 +332,7 @@ module ActiveRecord key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id if autosave != false && (new_record? || association.new_record? || association[reflection.primary_key_name] != key || autosave) association[reflection.primary_key_name] = key - saved = association.save(!autosave) + saved = association.save(:validate => !autosave) raise ActiveRecord::Rollback if !saved && autosave saved end @@ -355,7 +355,7 @@ module ActiveRecord if autosave && association.marked_for_destruction? association.destroy elsif autosave != false - saved = association.save(!autosave) if association.new_record? || autosave + saved = association.save(:validate => !autosave) if association.new_record? || autosave if association.updated? association_id = association.send(reflection.options[:primary_key] || :id) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 4ee9887186..06244d1132 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2136,16 +2136,16 @@ module ActiveRecord #:nodoc: end # :call-seq: - # save(perform_validation = true) + # save(options) # # Saves the model. # # If the model is new a record gets created in the database, otherwise # the existing record gets updated. # - # If +perform_validation+ is true validations run. If any of them fail - # the action is cancelled and +save+ returns +false+. If the flag is - # false validations are bypassed altogether. See + # By default, save always run validations. If any of them fail the action + # is cancelled and +save+ returns +false+. However, if you supply + # :validate => false, validations are bypassed altogether. See # ActiveRecord::Validations for more information. # # There's a series of callbacks associated with +save+. If any of the @@ -2220,7 +2220,7 @@ module ActiveRecord #:nodoc: # in Base is replaced with this when the validations module is mixed in, which it is by default. def update_attribute(name, value) send(name.to_s + '=', value) - save(false) + save(:validate => false) end # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index e4b3caab4e..8a44dc7df1 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -46,19 +46,19 @@ module ActiveRecord def count(*args) case args.size when 0 - construct_calculation_arel({}, current_scoped_methods).count + construct_calculation_arel.count when 1 if args[0].is_a?(Hash) options = args[0] distinct = options.has_key?(:distinct) ? options.delete(:distinct) : false - construct_calculation_arel(options, current_scoped_methods).count(options[:select], :distinct => distinct) + construct_calculation_arel(options).count(options[:select], :distinct => distinct) else - construct_calculation_arel({}, current_scoped_methods).count(args[0]) + construct_calculation_arel.count(args[0]) end when 2 column_name, options = args distinct = options.has_key?(:distinct) ? options.delete(:distinct) : false - construct_calculation_arel(options, current_scoped_methods).count(column_name, :distinct => distinct) + construct_calculation_arel(options).count(column_name, :distinct => distinct) else raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}" end @@ -141,7 +141,7 @@ module ActiveRecord # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors # Person.sum("2 * age") def calculate(operation, column_name, options = {}) - construct_calculation_arel(options, current_scoped_methods).calculate(operation, column_name, options.slice(:distinct)) + construct_calculation_arel(options).calculate(operation, column_name, options.slice(:distinct)) rescue ThrowResult 0 end @@ -151,60 +151,21 @@ module ActiveRecord options.assert_valid_keys(CALCULATIONS_OPTIONS) end - def construct_calculation_arel(options = {}, merge_with_relation = nil) + def construct_calculation_arel(options = {}) validate_calculation_options(options) options = options.except(:distinct) - merge_with_includes = merge_with_relation ? merge_with_relation.includes_values : [] + merge_with_includes = current_scoped_methods ? current_scoped_methods.includes_values : [] includes = (merge_with_includes + Array.wrap(options[:include])).uniq if includes.any? - merge_with_joins = merge_with_relation ? merge_with_relation.joins_values : [] + merge_with_joins = current_scoped_methods ? current_scoped_methods.joins_values : [] joins = (merge_with_joins + Array.wrap(options[:joins])).uniq join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(joins)) - construct_calculation_arel_with_included_associations(options, join_dependency, merge_with_relation) + construct_finder_arel_with_included_associations(options, join_dependency) else - relation = unscoped.apply_finder_options(options.slice(:joins, :conditions, :order, :limit, :offset, :group, :having)) - - if merge_with_relation - relation = merge_with_relation.except(:select, :order, :limit, :offset, :group, :from).merge(relation) - end - - from = merge_with_relation.from_value if merge_with_relation && merge_with_relation.from_value.present? - from = options[:from] if from.blank? && options[:from].present? - relation = relation.from(from) - - select = options[:select].presence || (merge_with_relation ? merge_with_relation.select_values.join(", ") : nil) - relation = relation.select(select) - - relation - end - end - - def construct_calculation_arel_with_included_associations(options, join_dependency, merge_with_relation = nil) - relation = unscoped - - for association in join_dependency.join_associations - relation = association.join_relation(relation) + scoped.apply_finder_options(options) end - - if merge_with_relation - relation.joins_values = (merge_with_relation.joins_values + relation.joins_values).uniq - relation.where_values = merge_with_relation.where_values - - merge_limit = merge_with_relation.taken - end - - relation = relation.apply_finder_options(options.slice(:joins, :group, :having, :order, :conditions, :from)). - select(column_aliases(join_dependency)) - - if !using_limitable_reflections?(join_dependency.reflections) && (merge_limit || options[:limit]) - relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency)) - end - - relation = relation.limit(options[:limit] || merge_limit) if using_limitable_reflections?(join_dependency.reflections) - - relation end end diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index aecde5848c..1128286f2b 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -291,7 +291,7 @@ module ActiveRecord end def deprecated_callback_method(symbol) #:nodoc: - if respond_to?(symbol) + if respond_to?(symbol, true) ActiveSupport::Deprecation.warn("Overwriting #{symbol} in your models has been deprecated, please use Base##{symbol} :method_name instead") send(symbol) end diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 90fd700437..92030e5bfd 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -9,7 +9,7 @@ module ActiveRecord module ClassMethods # Returns a relation if invoked without any arguments. # - # posts = Post.scoped + # posts = Post.scoped # posts.size # Fires "select count(*) from posts" and returns the count # posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects # @@ -24,15 +24,9 @@ module ActiveRecord # You can define a scope that applies to all finders using ActiveRecord::Base.default_scope. def scoped(options = {}, &block) if options.present? - Scope.new(self, options, &block) + Scope.init(self, options, &block) else - current_scope = current_scoped_methods - - unless current_scope - unscoped.spawn - else - construct_finder_arel({}, current_scoped_methods) - end + current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.spawn end end @@ -44,11 +38,11 @@ module ActiveRecord # such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>. # # class Shirt < ActiveRecord::Base - # named_scope :red, :conditions => {:color => 'red'} - # named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true] + # scope :red, :conditions => {:color => 'red'} + # scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true] # end - # - # The above calls to <tt>named_scope</tt> define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red, + # + # The above calls to <tt>scope</tt> define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red, # in effect, represents the query <tt>Shirt.find(:all, :conditions => {:color => 'red'})</tt>. # # Unlike <tt>Shirt.find(...)</tt>, however, the object returned by Shirt.red is not an Array; it resembles the association object @@ -74,7 +68,7 @@ module ActiveRecord # Named \scopes can also be procedural: # # class Shirt < ActiveRecord::Base - # named_scope :colored, lambda { |color| + # scope :colored, lambda { |color| # { :conditions => { :color => color } } # } # end @@ -84,7 +78,7 @@ module ActiveRecord # Named \scopes can also have extensions, just as with <tt>has_many</tt> declarations: # # class Shirt < ActiveRecord::Base - # named_scope :red, :conditions => {:color => 'red'} do + # scope :red, :conditions => {:color => 'red'} do # def dom_id # 'red_shirts' # end @@ -96,18 +90,23 @@ module ActiveRecord # <tt>proxy_options</tt> method on the proxy itself. # # class Shirt < ActiveRecord::Base - # named_scope :colored, lambda { |color| + # scope :colored, lambda { |color| # { :conditions => { :color => color } } # } # end # # expected_options = { :conditions => { :colored => 'red' } } # assert_equal expected_options, Shirt.colored('red').proxy_options - def named_scope(name, options = {}, &block) + def scope(name, options = {}, &block) name = name.to_sym + + if !scopes[name] && respond_to?(name, true) + raise ArgumentError, "Cannot define scope :#{name} because #{self.name}.#{name} method already exists." + end + scopes[name] = lambda do |parent_scope, *args| - Scope.new(parent_scope, case options - when Hash + Scope.init(parent_scope, case options + when Hash, Relation options when Proc options.call(*args) @@ -119,104 +118,90 @@ module ActiveRecord end end end - end - class Scope - attr_reader :proxy_scope, :proxy_options, :current_scoped_methods_when_defined - NON_DELEGATE_METHODS = %w(nil? send object_id class extend find size count sum average maximum minimum paginate first last empty? any? many? respond_to?).to_set - [].methods.each do |m| - unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s) - delegate m, :to => :proxy_found - end + def named_scope(*args, &block) + ActiveSupport::Deprecation.warn("Base.named_scope has been deprecated, please use Base.scope instead", caller) + scope(*args, &block) end + end - delegate :scopes, :with_scope, :scoped_methods, :to => :proxy_scope + class Scope < Relation + attr_accessor :current_scoped_methods_when_defined - def initialize(proxy_scope, options, &block) - options ||= {} - [options[:extend]].flatten.each { |extension| extend extension } if options[:extend] - extend Module.new(&block) if block_given? - unless Scope === proxy_scope - @current_scoped_methods_when_defined = proxy_scope.send(:current_scoped_methods) - end - @proxy_scope, @proxy_options = proxy_scope, options.except(:extend) - end + delegate :scopes, :with_scope, :with_exclusive_scope, :scoped_methods, :scoped, :to => :klass - def reload - load_found; self - end + def self.init(klass, options, &block) + relation = new(klass, klass.arel_table) - def first(*args) - if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash)) - proxy_found.first(*args) + scope = if options.is_a?(Hash) + klass.scoped.apply_finder_options(options.except(:extend)) else - find(:first, *args) + options ? klass.scoped.merge(options) : klass.scoped end - end - def last(*args) - if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash)) - proxy_found.last(*args) - else - find(:last, *args) - end - end + relation = relation.merge(scope) - def size - @found ? @found.length : count - end + Array.wrap(options[:extend]).each {|extension| relation.send(:extend, extension) } if options.is_a?(Hash) + relation.send(:extend, Module.new(&block)) if block_given? - def empty? - @found ? @found.empty? : count.zero? + relation.current_scoped_methods_when_defined = klass.send(:current_scoped_methods) + relation end - def respond_to?(method, include_private = false) - super || @proxy_scope.respond_to?(method, include_private) + def find(*args) + options = args.extract_options! + relation = options.present? ? apply_finder_options(options) : self + + case args.first + when :first, :last, :all + relation.send(args.first) + else + options.present? ? relation.find(*args) : super + end end - def any? - if block_given? - proxy_found.any? { |*block_args| yield(*block_args) } + def first(*args) + if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash)) + to_a.first(*args) else - !empty? + args.first.present? ? apply_finder_options(args.first).first : super end end - # Returns true if the named scope has more than 1 matching record. - def many? - if block_given? - proxy_found.many? { |*block_args| yield(*block_args) } + def last(*args) + if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash)) + to_a.last(*args) else - size > 1 + args.first.present? ? apply_finder_options(args.first).last : super end end - protected - def proxy_found - @found || load_found + def count(*args) + options = args.extract_options! + options.present? ? apply_finder_options(options).count(*args) : super + end + + def ==(other) + to_a == other.to_a end private + def method_missing(method, *args, &block) - if scopes.include?(method) - scopes[method].call(self, *args) - else - with_scope({:find => proxy_options, :create => proxy_options[:conditions].is_a?(Hash) ? proxy_options[:conditions] : {}}, :reverse_merge) do - method = :new if method == :build - if current_scoped_methods_when_defined && !scoped_methods.include?(current_scoped_methods_when_defined) - with_scope current_scoped_methods_when_defined do - proxy_scope.send(method, *args, &block) - end + if klass.respond_to?(method) + with_scope(self) do + if current_scoped_methods_when_defined && !scoped_methods.include?(current_scoped_methods_when_defined) && !scopes.include?(method) + with_scope(current_scoped_methods_when_defined) { klass.send(method, *args, &block) } else - proxy_scope.send(method, *args, &block) + klass.send(method, *args, &block) end end + else + super end end - def load_found - @found = find(:all) - end end + end end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index b39e064e45..88974dd786 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -428,7 +428,7 @@ namespace :db do task :create => :environment do raise "Task unavailable to this database (no migration support)" unless ActiveRecord::Base.connection.supports_migrations? require 'rails/generators' - require 'rails/generators/rails/session_migration/session_migration_generator' + require 'generators/rails/session_migration/session_migration_generator' Rails::Generators::SessionMigrationGenerator.start [ ENV["MIGRATION"] || "add_sessions_table" ] end diff --git a/activerecord/lib/active_record/railties/subscriber.rb b/activerecord/lib/active_record/railties/subscriber.rb index 7c2a10cf0f..fd873dbff8 100644 --- a/activerecord/lib/active_record/railties/subscriber.rb +++ b/activerecord/lib/active_record/railties/subscriber.rb @@ -12,7 +12,7 @@ module ActiveRecord name = color(name, :magenta, true) end - debug "#{name} #{sql}" + debug " #{name} #{sql}" end def odd? diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 85bf878416..e37e692a97 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -7,7 +7,7 @@ module ActiveRecord include FinderMethods, CalculationMethods, SpawnMethods, QueryMethods - delegate :length, :collect, :map, :each, :all?, :to => :to_a + delegate :length, :collect, :map, :each, :all?, :include?, :to => :to_a attr_reader :table, :klass @@ -20,6 +20,8 @@ module ActiveRecord with_create_scope { @klass.new(*args, &block) } end + alias build new + def create(*args, &block) with_create_scope { @klass.create(*args, &block) } end @@ -43,33 +45,12 @@ module ActiveRecord def to_a return @records if loaded? - find_with_associations = @eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?) - - @records = if find_with_associations - begin - options = { - :select => @select_values.any? ? @select_values.join(", ") : nil, - :joins => arel.joins(arel), - :group => @group_values.any? ? @group_values.join(", ") : nil, - :order => order_clause, - :conditions => where_clause, - :limit => arel.taken, - :offset => arel.skipped, - :from => (arel.send(:from_clauses) if arel.send(:sources).present?) - } - - including = (@eager_load_values + @includes_values).uniq - join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, nil) - @klass.send(:find_with_associations, options, join_dependency) - rescue ThrowResult - [] - end - else - @klass.find_by_sql(arel.to_sql) - end + eager_loading = @eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?) + + @records = eager_loading ? find_with_associations : @klass.find_by_sql(arel.to_sql) preload = @preload_values - preload += @includes_values unless find_with_associations + preload += @includes_values unless eager_loading preload.each {|associations| @klass.send(:preload_associations, @records, associations) } # @readonly_value is true only if set explicity. @implicit_readonly is true if there are JOINS and no explicit SELECT. @@ -124,12 +105,13 @@ module ActiveRecord end def reload - @loaded = false reset + to_a # force reload + self end def reset - @first = @last = @to_sql = @order_clause = @scope_for_create = @arel = nil + @first = @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil @records = [] self end @@ -172,6 +154,8 @@ module ActiveRecord end end + private + def with_create_scope @klass.send(:with_scope, :create => scope_for_create, :find => {}) { yield } end @@ -180,10 +164,6 @@ module ActiveRecord arel.send(:where_clauses).join(join_string) end - def order_clause - @order_clause ||= arel.send(:order_clauses).join(', ') - end - def references_eager_loaded_tables? joined_tables = (tables_in_string(arel.joins(arel)) + [table.name, table.table_alias]).compact.uniq (tables_in_string(to_sql) - joined_tables).any? diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 3668b0997f..980c5796f3 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -44,6 +44,42 @@ module ActiveRecord protected + def find_with_associations + including = (@eager_load_values + @includes_values).uniq + join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, nil) + rows = construct_relation_for_association_find(join_dependency).to_a + join_dependency.instantiate(rows) + rescue ThrowResult + [] + end + + def construct_relation_for_association_find(join_dependency) + relation = except(:includes, :eager_load, :preload, :select).select(@klass.send(:column_aliases, join_dependency)) + + for association in join_dependency.join_associations + relation = association.join_relation(relation) + end + + limitable_reflections = @klass.send(:using_limitable_reflections?, join_dependency.reflections) + + if !limitable_reflections && relation.limit_value + limited_id_condition = construct_limited_ids_condition(relation.except(:select)) + relation = relation.where(limited_id_condition) + end + + relation = relation.except(:limit, :offset) unless limitable_reflections + + relation + end + + def construct_limited_ids_condition(relation) + orders = relation.order_values.join(", ") + values = @klass.connection.distinct("#{@klass.connection.quote_table_name @klass.table_name}.#{@klass.primary_key}", orders) + + ids_array = relation.select(values).collect {|row| row[@klass.primary_key]} + ids_array.empty? ? raise(ThrowResult) : primary_key.in(ids_array) + end + def find_by_attributes(match, attributes, *args) conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h} result = where(conditions).send(match.finder) diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 6b7d941350..9e855209f9 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -24,7 +24,8 @@ module ActiveRecord case value when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope - attribute.in(value) + values = value.to_a + values.any? ? attribute.in(values) : attribute.eq(nil) when Range # TODO : Arel should handle ranges with excluded end. if value.exclude_end? diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index f4abaae43e..d5b13c6100 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -1,7 +1,7 @@ module ActiveRecord module SpawnMethods def spawn(arel_table = self.table) - relation = Relation.new(@klass, arel_table) + relation = self.class.new(@klass, arel_table) (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |query_method| relation.send(:"#{query_method}_values=", send(:"#{query_method}_values")) @@ -15,11 +15,10 @@ module ActiveRecord end def merge(r) - if r.klass != @klass - raise ArgumentError, "Cannot merge a #{r.klass.name}(##{r.klass.object_id}) relation with #{@klass.name}(##{@klass.object_id}) relation" - end + merged_relation = spawn + return merged_relation unless r - merged_relation = spawn.eager_load(r.eager_load_values).preload(r.preload_values).includes(r.includes_values) + merged_relation = merged_relation.eager_load(r.eager_load_values).preload(r.preload_values).includes(r.includes_values) merged_relation.readonly_value = r.readonly_value unless r.readonly_value.nil? merged_relation.limit_value = r.limit_value if r.limit_value.present? @@ -33,7 +32,7 @@ module ActiveRecord from(r.from_value). having(r.having_values) - merged_relation.order_values = Array.wrap(order_values) + Array.wrap(r.order_values) + merged_relation.order_values = r.order_values if r.order_values.present? merged_relation.create_with_value = @create_with_value @@ -61,7 +60,7 @@ module ActiveRecord alias :& :merge def except(*skips) - result = Relation.new(@klass, table) + result = self.class.new(@klass, table) (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |method| result.send(:"#{method}_values=", send(:"#{method}_values")) unless skips.include?(method) @@ -75,7 +74,7 @@ module ActiveRecord end def only(*onlies) - result = Relation.new(@klass, table) + result = self.class.new(@klass, table) onlies.each do |only| if (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).include?(only) @@ -94,9 +93,10 @@ module ActiveRecord :order, :select, :readonly, :group, :having, :from, :lock ] def apply_finder_options(options) - options.assert_valid_keys(VALID_FIND_OPTIONS) - relation = spawn + return relation unless options + + options.assert_valid_keys(VALID_FIND_OPTIONS) relation = relation.joins(options[:joins]). where(options[:conditions]). diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 4f8ccdd40e..cf0fe8934d 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -192,8 +192,8 @@ module ActiveRecord with_transaction_returning_status(:destroy_without_transactions) end - def save_with_transactions(perform_validation = true) #:nodoc: - rollback_active_record_state! { with_transaction_returning_status(:save_without_transactions, perform_validation) } + def save_with_transactions(*args) #:nodoc: + rollback_active_record_state! { with_transaction_returning_status(:save_without_transactions, *args) } end def save_with_transactions! #:nodoc: diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index d5adcba3ba..a9743aa1ea 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -42,7 +42,17 @@ module ActiveRecord module InstanceMethods # 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_with_validation(perform_validation = true) + def save_with_validation(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 + if perform_validation && valid? || !perform_validation save_without_validation else diff --git a/activerecord/lib/generators/active_record.rb b/activerecord/lib/generators/active_record.rb new file mode 100644 index 0000000000..25b982f296 --- /dev/null +++ b/activerecord/lib/generators/active_record.rb @@ -0,0 +1,31 @@ +require 'rails/generators/named_base' +require 'rails/generators/migration' +require 'rails/generators/active_model' +require 'active_record' + +module ActiveRecord + module Generators + class Base < Rails::Generators::NamedBase #:nodoc: + include Rails::Generators::Migration + + def self.source_root + @_ar_source_root ||= begin + if base_name && generator_name + File.expand_path(File.join(base_name, generator_name, 'templates'), File.dirname(__FILE__)) + end + end + end + + protected + # Implement the required interface for Rails::Generators::Migration. + # + def next_migration_number(dirname) #:nodoc: + if ActiveRecord::Base.timestamped_migrations + Time.now.utc.strftime("%Y%m%d%H%M%S") + else + "%.3d" % (current_migration_number(dirname) + 1) + end + end + end + end +end diff --git a/activerecord/lib/generators/active_record/migration/migration_generator.rb b/activerecord/lib/generators/active_record/migration/migration_generator.rb new file mode 100644 index 0000000000..7939977f72 --- /dev/null +++ b/activerecord/lib/generators/active_record/migration/migration_generator.rb @@ -0,0 +1,25 @@ +require 'generators/active_record' + +module ActiveRecord + module Generators + class MigrationGenerator < Base + argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" + + def create_migration_file + set_local_assigns! + migration_template "migration.rb", "db/migrate/#{file_name}.rb" + end + + protected + attr_reader :migration_action + + def set_local_assigns! + if file_name =~ /^(add|remove)_.*_(?:to|from)_(.*)/ + @migration_action = $1 + @table_name = $2.pluralize + end + end + + end + end +end diff --git a/activerecord/lib/generators/active_record/migration/templates/migration.rb b/activerecord/lib/generators/active_record/migration/templates/migration.rb new file mode 100644 index 0000000000..bbb7c53d86 --- /dev/null +++ b/activerecord/lib/generators/active_record/migration/templates/migration.rb @@ -0,0 +1,11 @@ +class <%= migration_class_name %> < ActiveRecord::Migration + def self.up<% attributes.each do |attribute| %> + <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><% end -%> + <%- end %> + end + + def self.down<% attributes.reverse.each do |attribute| %> + <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><% end -%> + <%- end %> + end +end diff --git a/activerecord/lib/generators/active_record/model/model_generator.rb b/activerecord/lib/generators/active_record/model/model_generator.rb new file mode 100644 index 0000000000..2641083e0d --- /dev/null +++ b/activerecord/lib/generators/active_record/model/model_generator.rb @@ -0,0 +1,33 @@ +require 'generators/active_record' + +module ActiveRecord + module Generators + class ModelGenerator < Base + argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" + + check_class_collision + + class_option :migration, :type => :boolean + class_option :timestamps, :type => :boolean + class_option :parent, :type => :string, :desc => "The parent class for the generated model" + + def create_migration_file + return unless options[:migration] && options[:parent].nil? + migration_template "migration.rb", "db/migrate/create_#{table_name}.rb" + end + + def create_model_file + template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb") + end + + hook_for :test_framework + + protected + + def parent_class_name + options[:parent] || "ActiveRecord::Base" + end + + end + end +end diff --git a/activerecord/lib/generators/active_record/model/templates/migration.rb b/activerecord/lib/generators/active_record/model/templates/migration.rb new file mode 100644 index 0000000000..1f68487304 --- /dev/null +++ b/activerecord/lib/generators/active_record/model/templates/migration.rb @@ -0,0 +1,16 @@ +class <%= migration_class_name %> < ActiveRecord::Migration + def self.up + create_table :<%= table_name %> do |t| +<% for attribute in attributes -%> + t.<%= attribute.type %> :<%= attribute.name %> +<% end -%> +<% if options[:timestamps] %> + t.timestamps +<% end -%> + end + end + + def self.down + drop_table :<%= table_name %> + end +end diff --git a/activerecord/lib/generators/active_record/model/templates/model.rb b/activerecord/lib/generators/active_record/model/templates/model.rb new file mode 100644 index 0000000000..21ae29e9f2 --- /dev/null +++ b/activerecord/lib/generators/active_record/model/templates/model.rb @@ -0,0 +1,5 @@ +class <%= class_name %> < <%= parent_class_name.classify %> +<% attributes.select {|attr| attr.reference? }.each do |attribute| -%> + belongs_to :<%= attribute.name %> +<% end -%> +end diff --git a/activerecord/lib/generators/active_record/observer/observer_generator.rb b/activerecord/lib/generators/active_record/observer/observer_generator.rb new file mode 100644 index 0000000000..a6b57423b8 --- /dev/null +++ b/activerecord/lib/generators/active_record/observer/observer_generator.rb @@ -0,0 +1,15 @@ +require 'generators/active_record' + +module ActiveRecord + module Generators + class ObserverGenerator < Base + check_class_collision :suffix => "Observer" + + def create_observer_file + template 'observer.rb', File.join('app/models', class_path, "#{file_name}_observer.rb") + end + + hook_for :test_framework + end + end +end diff --git a/activerecord/lib/generators/active_record/observer/templates/observer.rb b/activerecord/lib/generators/active_record/observer/templates/observer.rb new file mode 100644 index 0000000000..b9a3004161 --- /dev/null +++ b/activerecord/lib/generators/active_record/observer/templates/observer.rb @@ -0,0 +1,2 @@ +class <%= class_name %>Observer < ActiveRecord::Observer +end diff --git a/activerecord/lib/generators/active_record/session_migration/session_migration_generator.rb b/activerecord/lib/generators/active_record/session_migration/session_migration_generator.rb new file mode 100644 index 0000000000..59c4792066 --- /dev/null +++ b/activerecord/lib/generators/active_record/session_migration/session_migration_generator.rb @@ -0,0 +1,24 @@ +require 'generators/active_record' + +module ActiveRecord + module Generators + class SessionMigrationGenerator < Base + argument :name, :type => :string, :default => "add_sessions_table" + + def create_migration_file + migration_template "migration.rb", "db/migrate/#{file_name}.rb" + end + + protected + + def session_table_name + current_table_name = ActiveRecord::SessionStore::Session.table_name + if ["sessions", "session"].include?(current_table_name) + current_table_name = (ActiveRecord::Base.pluralize_table_names ? 'session'.pluralize : 'session') + end + current_table_name + end + + end + end +end diff --git a/activerecord/lib/generators/active_record/session_migration/templates/migration.rb b/activerecord/lib/generators/active_record/session_migration/templates/migration.rb new file mode 100644 index 0000000000..919822af7b --- /dev/null +++ b/activerecord/lib/generators/active_record/session_migration/templates/migration.rb @@ -0,0 +1,16 @@ +class <%= migration_class_name %> < ActiveRecord::Migration + def self.up + create_table :<%= session_table_name %> do |t| + t.string :session_id, :null => false + t.text :data + t.timestamps + end + + add_index :<%= session_table_name %>, :session_id + add_index :<%= session_table_name %>, :updated_at + end + + def self.down + drop_table :<%= session_table_name %> + end +end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index cc36a6dc5b..cc5460ddb7 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -805,7 +805,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase def test_should_still_allow_to_bypass_validations_on_the_associated_model @pirate.catchphrase = '' @pirate.ship.name = '' - @pirate.save(false) + @pirate.save(:validate => false) # Oracle saves empty string as NULL if current_adapter?(:OracleAdapter) assert_equal [nil, nil], [@pirate.reload.catchphrase, @pirate.ship.name] @@ -820,7 +820,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase @pirate.catchphrase = '' @pirate.ship.name = '' @pirate.ship.parts.each { |part| part.name = '' } - @pirate.save(false) + @pirate.save(:validate => false) values = [@pirate.reload.catchphrase, @pirate.ship.name, *@pirate.ship.parts.map(&:name)] # Oracle saves empty string as NULL @@ -917,7 +917,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase def test_should_still_allow_to_bypass_validations_on_the_associated_model @ship.pirate.catchphrase = '' @ship.name = '' - @ship.save(false) + @ship.save(:validate => false) # Oracle saves empty string as NULL if current_adapter?(:OracleAdapter) assert_equal [nil, nil], [@ship.reload.name, @ship.pirate.catchphrase] @@ -1029,7 +1029,7 @@ module AutosaveAssociationOnACollectionAssociationTests @pirate.catchphrase = '' @pirate.send(@association_name).each { |child| child.name = '' } - assert @pirate.save(false) + assert @pirate.save(:validate => false) # Oracle saves empty string as NULL if current_adapter?(:OracleAdapter) assert_equal [nil, nil, nil], [ @@ -1049,14 +1049,14 @@ module AutosaveAssociationOnACollectionAssociationTests def test_should_validation_the_associated_models_on_create assert_no_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count") do 2.times { @pirate.send(@association_name).build } - @pirate.save(true) + @pirate.save end end def test_should_allow_to_bypass_validations_on_the_associated_models_on_create assert_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count", +2) do 2.times { @pirate.send(@association_name).build } - @pirate.save(false) + @pirate.save(:validate => false) end end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index aea6aed8d9..1441b4278d 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1865,7 +1865,9 @@ class BasicsTest < ActiveRecord::TestCase end assert scoped_developers.include?(developers(:poor_jamis)) assert scoped_developers.include?(developers(:david)) - assert scoped_developers.include?(developers(:dev_10)) + assert ! scoped_developers.include?(developers(:jamis)) + assert_equal 3, scoped_developers.size + # Test without scoped find conditions to ensure we get the right thing developers = Developer.find(:all, :order => 'id', :limit => 1) assert scoped_developers.include?(developers(:david)) diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index 26aa3ed8d5..7ca5b5a988 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -608,7 +608,7 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_default_scoping_with_threads 2.times do - Thread.new { assert_equal 'salary DESC', DeveloperOrderedBySalary.scoped.send(:order_clause) }.join + Thread.new { assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values }.join end end @@ -618,28 +618,28 @@ class DefaultScopingTest < ActiveRecord::TestCase klass.send :default_scope, {} # Scopes added on children should append to parent scope - assert klass.scoped.send(:order_clause).blank? + assert klass.scoped.order_values.blank? # Parent should still have the original scope - assert_equal 'salary DESC', DeveloperOrderedBySalary.scoped.send(:order_clause) + assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values end def test_method_scope - expected = Developer.find(:all, :order => 'name DESC, salary DESC').collect { |dev| dev.salary } + expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary } received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary } assert_equal expected, received end def test_nested_scope - expected = Developer.find(:all, :order => 'name DESC, salary DESC').collect { |dev| dev.salary } + expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary } received = DeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary } end assert_equal expected, received end - def test_named_scope_order_appended_to_default_scope_order - expected = Developer.find(:all, :order => 'name DESC, salary DESC').collect { |dev| dev.name } + def test_named_scope_overwrites_default + expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.name } received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name } assert_equal expected, received end diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 5d9232bc52..3e2bd58f9a 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -31,7 +31,7 @@ class NamedScopeTest < ActiveRecord::TestCase def test_reload_expires_cache_of_found_items all_posts = Topic.base - all_posts.inspect + all_posts.all new_post = Topic.create! assert !all_posts.include?(new_post) @@ -48,14 +48,14 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_scope_should_respond_to_own_methods_and_methods_of_the_proxy - assert Topic.approved.respond_to?(:proxy_found) + assert Topic.approved.respond_to?(:limit) assert Topic.approved.respond_to?(:count) assert Topic.approved.respond_to?(:length) end def test_respond_to_respects_include_private_parameter - assert !Topic.approved.respond_to?(:load_found) - assert Topic.approved.respond_to?(:load_found, true) + assert !Topic.approved.respond_to?(:with_create_scope) + assert Topic.approved.respond_to?(:with_create_scope, true) end def test_subclasses_inherit_scopes @@ -150,13 +150,13 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_named_scopes_honor_current_scopes_from_when_defined - assert !Post.ranked_by_comments.limit(5).empty? - assert !authors(:david).posts.ranked_by_comments.limit(5).empty? - assert_not_equal Post.ranked_by_comments.limit(5), authors(:david).posts.ranked_by_comments.limit(5) + assert !Post.ranked_by_comments.limit_by(5).empty? + assert !authors(:david).posts.ranked_by_comments.limit_by(5).empty? + assert_not_equal Post.ranked_by_comments.limit_by(5), authors(:david).posts.ranked_by_comments.limit_by(5) assert_not_equal Post.top(5), authors(:david).posts.top(5) # Oracle sometimes sorts differently if WHERE condition is changed - assert_equal authors(:david).posts.ranked_by_comments.limit(5).sort_by(&:id), authors(:david).posts.top(5).sort_by(&:id) - assert_equal Post.ranked_by_comments.limit(5), Post.top(5) + assert_equal authors(:david).posts.ranked_by_comments.limit_by(5).to_a.sort_by(&:id), authors(:david).posts.top(5).to_a.sort_by(&:id) + assert_equal Post.ranked_by_comments.limit_by(5), Post.top(5) end def test_active_records_have_scope_named__all__ @@ -171,11 +171,6 @@ class NamedScopeTest < ActiveRecord::TestCase assert_equal Topic.find(:all, scope), Topic.scoped(scope) end - def test_proxy_options - expected_proxy_options = { :conditions => { :approved => true } } - assert_equal expected_proxy_options, Topic.approved.proxy_options - end - def test_first_and_last_should_support_find_options assert_equal Topic.base.first(:order => 'title'), Topic.base.find(:first, :order => 'title') assert_equal Topic.base.last(:order => 'title'), Topic.base.find(:last, :order => 'title') @@ -297,7 +292,7 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_find_all_should_behave_like_select - assert_equal Topic.base.select(&:approved), Topic.base.find_all(&:approved) + assert_equal Topic.base.to_a.select(&:approved), Topic.base.to_a.find_all(&:approved) end def test_rand_should_select_a_random_object_from_proxy @@ -345,14 +340,14 @@ class NamedScopeTest < ActiveRecord::TestCase def test_chaining_should_use_latest_conditions_when_searching # Normal hash conditions - assert_equal Topic.where(:approved => true).to_a, Topic.rejected.approved.all.to_a - assert_equal Topic.where(:approved => false).to_a, Topic.approved.rejected.all.to_a + assert_equal Topic.where(:approved => true).to_a, Topic.rejected.approved.all + assert_equal Topic.where(:approved => false).to_a, Topic.approved.rejected.all # Nested hash conditions with same keys - assert_equal [posts(:sti_comments)], Post.with_special_comments.with_very_special_comments.all.to_a + assert_equal [posts(:sti_comments)], Post.with_special_comments.with_very_special_comments.all # Nested hash conditions with different keys - assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).all.to_a.uniq + assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).all.uniq end def test_named_scopes_batch_finders @@ -374,6 +369,16 @@ class NamedScopeTest < ActiveRecord::TestCase Comment.for_first_post.for_first_author.all end end + + def test_named_scopes_with_reserved_names + [:where, :with_scope].each do |protected_method| + assert_raises(ArgumentError) { Topic.scope protected_method } + end + end + + def test_deprecated_named_scope_method + assert_deprecated('named_scope has been deprecated') { Topic.named_scope :deprecated_named_scope } + end end class DynamicScopeMatchTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 195889f1df..d34c9b4895 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -68,10 +68,12 @@ class RelationTest < ActiveRecord::TestCase assert topics.loaded? - topics.reload - assert ! topics.loaded? + original_size = topics.to_a.size + Topic.create! :title => 'fake' - assert_queries(1) { topics.to_a } + assert_queries(1) { topics.reload } + assert_equal original_size + 1, topics.size + assert topics.loaded? end def test_finding_with_conditions @@ -337,6 +339,11 @@ class RelationTest < ActiveRecord::TestCase assert_raises(ActiveRecord::RecordNotFound) { authors.find(['42', 43]) } end + def test_find_in_empty_array + authors = Author.scoped.where(:id => []) + assert authors.all.blank? + end + def test_exists davids = Author.where(:name => 'David') assert davids.exists? @@ -418,10 +425,6 @@ class RelationTest < ActiveRecord::TestCase end end - def test_invalid_merge - assert_raises(ArgumentError) { Post.scoped & Developer.scoped } - end - def test_count posts = Post.scoped diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index 3a1d5ae212..5aac0229cd 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -19,7 +19,7 @@ end class DeprecatedPerson < ActiveRecord::Base set_table_name 'people' - protected + private def validate errors[:name] << "always invalid" @@ -124,7 +124,15 @@ class ValidationsTest < ActiveRecord::TestCase def test_create_without_validation reply = WrongReply.new assert !reply.save - assert reply.save(false) + assert reply.save(:validate => false) + end + + def test_deprecated_create_without_validation + reply = WrongReply.new + assert !reply.save + assert_deprecated do + assert reply.save(false) + end end def test_create_without_validation_bang @@ -153,4 +161,21 @@ class ValidationsTest < ActiveRecord::TestCase topic = Topic.create("author_name" => "Dan Brown") assert_equal "Dan Brown", topic["author_name"] end + + def test_validate_is_deprecated_on_create + p = DeprecatedPerson.new + assert_deprecated do + assert !p.valid? + end + assert_equal ["always invalid", "invalid on create"], p.errors[:name] + end + + def test_validate_is_deprecated_on_update + p = DeprecatedPerson.new(:first_name => "David") + assert p.save(:validate => false) + assert_deprecated do + assert !p.valid? + end + assert_equal ["always invalid", "invalid on update"], p.errors[:name] + end end diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index 399dea9f12..a8a99f6dce 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -1,7 +1,7 @@ class Comment < ActiveRecord::Base - named_scope :containing_the_letter_e, :conditions => "comments.body LIKE '%e%'" - named_scope :for_first_post, :conditions => { :post_id => 1 } - named_scope :for_first_author, + scope :containing_the_letter_e, :conditions => "comments.body LIKE '%e%'" + scope :for_first_post, :conditions => { :post_id => 1 } + scope :for_first_author, :joins => :post, :conditions => { "posts.author_id" => 1 } diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 7e93fda1eb..df5fd10b6b 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -45,7 +45,7 @@ class Firm < Company has_many :unvalidated_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :validate => false has_many :dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :destroy has_many :exclusively_dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all - has_many :limited_clients, :class_name => "Client", :order => "id", :limit => 1 + has_many :limited_clients, :class_name => "Client", :limit => 1 has_many :clients_like_ms, :conditions => "name = 'Microsoft'", :class_name => "Client", :order => "id" has_many :clients_with_interpolated_conditions, :class_name => "Client", :conditions => 'rating > #{rating}' has_many :clients_like_ms_with_hash_conditions, :conditions => { :name => 'Microsoft' }, :class_name => "Client", :order => "id" @@ -91,10 +91,8 @@ class Firm < Company end class DependentFirm < Company - # added order by id as in fixtures there are two accounts for Rails Core - # Oracle tests were failing because of that as the second fixture was selected - has_one :account, :foreign_key => "firm_id", :dependent => :nullify, :order => "id" - has_many :companies, :foreign_key => 'client_of', :order => "id", :dependent => :nullify + has_one :account, :foreign_key => "firm_id", :dependent => :nullify + has_many :companies, :foreign_key => 'client_of', :dependent => :nullify end class Client < Company diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 058970336b..e7a1e110d7 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -43,7 +43,7 @@ class Developer < ActiveRecord::Base has_many :audit_logs - named_scope :jamises, :conditions => {:name => 'Jamis'} + scope :jamises, :conditions => {:name => 'Jamis'} validates_inclusion_of :salary, :in => 50000..200000 validates_length_of :name, :within => 3..20 @@ -81,7 +81,7 @@ end class DeveloperOrderedBySalary < ActiveRecord::Base self.table_name = 'developers' default_scope :order => 'salary DESC' - named_scope :by_name, :order => 'name DESC' + scope :by_name, :order => 'name DESC' def self.all_ordered_by_name with_scope(:find => { :order => 'name DESC' }) do diff --git a/activerecord/test/models/organization.rb b/activerecord/test/models/organization.rb index c85726169e..1da342a0bd 100644 --- a/activerecord/test/models/organization.rb +++ b/activerecord/test/models/organization.rb @@ -2,5 +2,5 @@ class Organization < ActiveRecord::Base has_many :member_details has_many :members, :through => :member_details - named_scope :clubs, { :from => 'clubs' } + scope :clubs, { :from => 'clubs' } end
\ No newline at end of file diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index 57fa6418f1..2a73b1ee01 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -12,6 +12,6 @@ class Person < ActiveRecord::Base has_many :agents, :class_name => 'Person', :foreign_key => 'primary_contact_id' belongs_to :number1_fan, :class_name => 'Person' - named_scope :males, :conditions => { :gender => 'M' } - named_scope :females, :conditions => { :gender => 'F' } + scope :males, :conditions => { :gender => 'M' } + scope :females, :conditions => { :gender => 'F' } end diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 7392515ec7..f48b35486c 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -1,8 +1,8 @@ class Post < ActiveRecord::Base - named_scope :containing_the_letter_a, :conditions => "body LIKE '%a%'" - named_scope :ranked_by_comments, :order => "comments_count DESC" - named_scope :limit, lambda {|limit| {:limit => limit} } - named_scope :with_authors_at_address, lambda { |address| { + scope :containing_the_letter_a, where("body LIKE '%a%'") + scope :ranked_by_comments, order("comments_count DESC") + scope :limit_by, lambda {|l| limit(l) } + scope :with_authors_at_address, lambda { |address| { :conditions => [ 'authors.author_address_id = ?', address.id ], :joins => 'JOIN authors ON authors.id = posts.author_id' } @@ -19,13 +19,13 @@ class Post < ActiveRecord::Base has_one :last_comment, :class_name => 'Comment', :order => 'id desc' - named_scope :with_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'SpecialComment'} } - named_scope :with_very_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'VerySpecialComment'} } - named_scope :with_post, lambda {|post_id| + scope :with_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'SpecialComment'} } + scope :with_very_special_comments, joins(:comments).where(:comments => {:type => 'VerySpecialComment'}) + scope :with_post, lambda {|post_id| { :joins => :comments, :conditions => {:comments => {:post_id => post_id} } } } - has_many :comments, :order => "body" do + has_many :comments do def find_most_recent find(:first, :order => "id DESC") end @@ -73,7 +73,7 @@ class Post < ActiveRecord::Base has_many :impatient_people, :through => :skimmers, :source => :person def self.top(limit) - ranked_by_comments.limit(limit) + ranked_by_comments.limit_by(limit) end def self.reset_log diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb index f1ba45b528..264a49b465 100644 --- a/activerecord/test/models/reply.rb +++ b/activerecord/test/models/reply.rb @@ -1,7 +1,7 @@ require 'models/topic' class Reply < Topic - named_scope :base + scope :base belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true belongs_to :topic_with_primary_key, :class_name => "Topic", :primary_key => "title", :foreign_key => "parent_title", :counter_cache => "replies_count" diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index baca4972cb..91fc7c9416 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -1,19 +1,19 @@ class Topic < ActiveRecord::Base - named_scope :base - named_scope :written_before, lambda { |time| + scope :base + scope :written_before, lambda { |time| if time { :conditions => ['written_on < ?', time] } end } - named_scope :approved, :conditions => {:approved => true} - named_scope :rejected, :conditions => {:approved => false} + scope :approved, :conditions => {:approved => true} + scope :rejected, :conditions => {:approved => false} - named_scope :by_lifo, :conditions => {:author_name => 'lifo'} + scope :by_lifo, :conditions => {:author_name => 'lifo'} - named_scope :approved_as_hash_condition, :conditions => {:topics => {:approved => true}} - named_scope 'approved_as_string', :conditions => {:approved => true} - named_scope :replied, :conditions => ['replies_count > 0'] - named_scope :anonymous_extension do + scope :approved_as_hash_condition, :conditions => {:topics => {:approved => true}} + scope 'approved_as_string', :conditions => {:approved => true} + scope :replied, :conditions => ['replies_count > 0'] + scope :anonymous_extension do def one 1 end @@ -33,8 +33,8 @@ class Topic < ActiveRecord::Base 2 end end - named_scope :named_extension, :extend => NamedExtension - named_scope :multiple_extensions, :extend => [MultipleExtensionTwo, MultipleExtensionOne] + scope :named_extension, :extend => NamedExtension + scope :multiple_extensions, :extend => [MultipleExtensionTwo, MultipleExtensionOne] has_many :replies, :dependent => :destroy, :foreign_key => "parent_id" has_many :replies_with_primary_key, :class_name => "Reply", :dependent => :destroy, :primary_key => "title", :foreign_key => "parent_title" |