diff options
36 files changed, 311 insertions, 194 deletions
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index be67042999..2bfc6371f5 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -626,7 +626,7 @@ module ActionView # <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time> # # <%= time_tag Time.now do %> - # <span>Right now</span> + # <span>Right now</span> # <% end %> # # => <time datetime="2010-11-04T17:55:45+01:00"><span>Right now</span></time> # @@ -674,11 +674,7 @@ module ActionView @options[:discard_minute] ||= true if @options[:discard_hour] @options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute] - # If the day is hidden, the day should be set to the 1st so all month and year choices are valid. Otherwise, - # February 31st or February 29th, 2011 can be selected, which are invalid. - if @datetime && @options[:discard_day] - @datetime = @datetime.change(:day => 1) - end + set_day_if_discarded if @options[:tag] && @options[:ignore_date] select_time @@ -701,11 +697,7 @@ module ActionView @options[:discard_month] ||= true unless order.include?(:month) @options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day) - # If the day is hidden, the day should be set to the 1st so all month and year choices are valid. Otherwise, - # February 31st or February 29th, 2011 can be selected, which are invalid. - if @datetime && @options[:discard_day] - @datetime = @datetime.change(:day => 1) - end + set_day_if_discarded [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) } @@ -807,6 +799,14 @@ module ActionView end end + # If the day is hidden, the day should be set to the 1st so all month and year choices are + # valid. Otherwise, February 31st or February 29th, 2011 can be selected, which are invalid. + def set_day_if_discarded + if @datetime && @options[:discard_day] + @datetime = @datetime.change(:day => 1) + end + end + # Returns translated month names, but also ensures that a custom month # name array has a leading nil element. def month_names diff --git a/actionpack/test/fixtures/reply.rb b/actionpack/test/fixtures/reply.rb index 19cba93673..0d3b0a7c98 100644 --- a/actionpack/test/fixtures/reply.rb +++ b/actionpack/test/fixtures/reply.rb @@ -1,5 +1,5 @@ class Reply < ActiveRecord::Base - scope :base + scope :base, -> { scoped } belongs_to :topic, :include => [:replies] belongs_to :developer diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index fd0bc4e8e9..b22d9539b0 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -6,13 +6,11 @@ require 'active_support/core_ext/object/blank' module ActiveModel class Name < String - attr_reader :singular, :plural, :element, :collection, :partial_path, + attr_reader :singular, :plural, :element, :collection, :singular_route_key, :route_key, :param_key, :i18n_key alias_method :cache_key, :collection - deprecate :partial_path => "ActiveModel::Name#partial_path is deprecated. Call #to_partial_path on model instances directly instead." - def initialize(klass, namespace = nil, name = nil) name ||= klass.name @@ -27,7 +25,6 @@ module ActiveModel @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).freeze @human = ActiveSupport::Inflector.humanize(@element).freeze @collection = ActiveSupport::Inflector.tableize(self).freeze - @partial_path = "#{@collection}/#{@element}".freeze @param_key = (namespace ? _singularize(@unnamespaced) : @singular).freeze @i18n_key = self.underscore.to_sym diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb index 1e14d83bcb..49d8706ac2 100644 --- a/activemodel/test/cases/naming_test.rb +++ b/activemodel/test/cases/naming_test.rb @@ -25,12 +25,6 @@ class NamingTest < ActiveModel::TestCase assert_equal 'post/track_backs', @model_name.collection end - def test_partial_path - assert_deprecated(/#partial_path.*#to_partial_path/) do - assert_equal 'post/track_backs/track_back', @model_name.partial_path - end - end - def test_human assert_equal 'Track back', @model_name.human end @@ -61,12 +55,6 @@ class NamingWithNamespacedModelInIsolatedNamespaceTest < ActiveModel::TestCase assert_equal 'blog/posts', @model_name.collection end - def test_partial_path - assert_deprecated(/#partial_path.*#to_partial_path/) do - assert_equal 'blog/posts/post', @model_name.partial_path - end - end - def test_human assert_equal 'Post', @model_name.human end @@ -105,12 +93,6 @@ class NamingWithNamespacedModelInSharedNamespaceTest < ActiveModel::TestCase assert_equal 'blog/posts', @model_name.collection end - def test_partial_path - assert_deprecated(/#partial_path.*#to_partial_path/) do - assert_equal 'blog/posts/post', @model_name.partial_path - end - end - def test_human assert_equal 'Post', @model_name.human end @@ -149,12 +131,6 @@ class NamingWithSuppliedModelNameTest < ActiveModel::TestCase assert_equal 'articles', @model_name.collection end - def test_partial_path - assert_deprecated(/#partial_path.*#to_partial_path/) do - assert_equal 'articles/article', @model_name.partial_path - end - end - def test_human assert_equal 'Article', @model_name.human end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 5a289a5aac..46031e7c13 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,36 @@ ## Rails 4.0.0 (unreleased) ## +* Deprecate eager-evaluated scopes. + + Don't use this: + + scope :red, where(color: 'red') + default_scope where(color: 'red') + + Use this: + + scope :red, -> { where(color: 'red') } + default_scope { where(color: 'red') } + + The former has numerous issues. It is a common newbie gotcha to do + the following: + + scope :recent, where(published_at: Time.now - 2.weeks) + + Or a more subtle variant: + + scope :recent, -> { where(published_at: Time.now - 2.weeks) } + scope :recent_red, recent.where(color: 'red') + + Eager scopes are also very complex to implement within Active + Record, and there are still bugs. For example, the following does + not do what you expect: + + scope :remove_conditions, except(:where) + where(...).remove_conditions # => still has conditions + + *Jon Leighton* + * Remove IdentityMap IdentityMap has never graduated to be an "enabled-by-default" feature, due 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/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/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 diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 562b370c97..e660b2ca35 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -514,7 +514,7 @@ class InvalidTableNameFixturesTest < ActiveRecord::TestCase self.use_transactional_fixtures = false def test_raises_error - assert_raise FixtureClassNotFound do + assert_raise ActiveRecord::FixtureClassNotFound do funny_jokes(:a_joke) end end diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index e17ba76437..0d3c0b20a4 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -123,16 +123,6 @@ class NamedScopeTest < ActiveRecord::TestCase assert objects.all?(&:approved?), 'all objects should be approved' end - def test_extensions - assert_equal 1, Topic.anonymous_extension.one - assert_equal 2, Topic.named_extension.two - end - - def test_multiple_extensions - assert_equal 2, Topic.multiple_extensions.extension_two - assert_equal 1, Topic.multiple_extensions.extension_one - end - def test_has_many_associations_have_access_to_scopes assert_not_equal Post.containing_the_letter_a, authors(:david).posts assert !Post.containing_the_letter_a.empty? @@ -393,25 +383,6 @@ class NamedScopeTest < ActiveRecord::TestCase end end - def test_scopes_with_reserved_names - class << Topic - def public_method; end - public :public_method - - def protected_method; end - protected :protected_method - - def private_method; end - private :private_method - end - - [:public_method, :protected_method, :private_method].each do |reserved_method| - assert Topic.respond_to?(reserved_method, true) - ActiveRecord::Base.logger.expects(:warn) - Topic.scope(reserved_method) - end - end - def test_scopes_on_relations # Topic.replied approved_topics = Topic.scoped.approved.order('id DESC') @@ -483,6 +454,41 @@ class NamedScopeTest < ActiveRecord::TestCase require "models/without_table" end end + + def test_eager_scopes_are_deprecated + klass = Class.new(ActiveRecord::Base) + klass.table_name = 'posts' + + assert_deprecated do + klass.scope :welcome, { :conditions => { :id => posts(:welcome).id } } + end + assert_equal [posts(:welcome).title], klass.welcome.map(&:title) + + assert_deprecated do + klass.scope :welcome_2, klass.where(:id => posts(:welcome).id) + end + assert_equal [posts(:welcome).title], klass.welcome_2.map(&:title) + end + + def test_eager_default_scope_hashes_are_deprecated + klass = Class.new(ActiveRecord::Base) + klass.table_name = 'posts' + + assert_deprecated do + klass.send(:default_scope, :conditions => { :id => posts(:welcome).id }) + end + assert_equal [posts(:welcome).title], klass.all.map(&:title) + end + + def test_eager_default_scope_relations_are_deprecated + klass = Class.new(ActiveRecord::Base) + klass.table_name = 'posts' + + assert_deprecated do + klass.send(:default_scope, klass.where(:id => posts(:welcome).id)) + end + assert_equal [posts(:welcome).title], klass.all.map(&:title) + end end class DynamicScopeMatchTest < ActiveRecord::TestCase diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index d50e11d6c9..14444a0092 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -140,8 +140,8 @@ class Author < ActiveRecord::Base has_many :posts_with_default_include, :class_name => 'PostWithDefaultInclude' has_many :comments_on_posts_with_default_include, :through => :posts_with_default_include, :source => :comments - scope :relation_include_posts, includes(:posts) - scope :relation_include_tags, includes(:tags) + scope :relation_include_posts, -> { includes(:posts) } + scope :relation_include_tags, -> { includes(:tags) } attr_accessor :post_log after_initialize :set_post_log diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb index 888afc7604..640e57555d 100644 --- a/activerecord/test/models/bulb.rb +++ b/activerecord/test/models/bulb.rb @@ -1,5 +1,5 @@ class Bulb < ActiveRecord::Base - default_scope where(:name => 'defaulty') + default_scope { where(:name => 'defaulty') } belongs_to :car attr_protected :car_id, :frickinawesome diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index 6ff1329d8e..42ac81690f 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -11,18 +11,18 @@ class Car < ActiveRecord::Base has_many :engines, :dependent => :destroy has_many :wheels, :as => :wheelable, :dependent => :destroy - scope :incl_tyres, includes(:tyres) - scope :incl_engines, includes(:engines) + scope :incl_tyres, -> { includes(:tyres) } + scope :incl_engines, -> { includes(:engines) } - scope :order_using_new_style, order('name asc') - scope :order_using_old_style, :order => 'name asc' + scope :order_using_new_style, -> { order('name asc') } + scope :order_using_old_style, -> { { :order => 'name asc' } } end class CoolCar < Car - default_scope :order => 'name desc' + default_scope { order('name desc') } end class FastCar < Car - default_scope :order => 'name desc' + default_scope { order('name desc') } end diff --git a/activerecord/test/models/categorization.rb b/activerecord/test/models/categorization.rb index 4bd980e606..6588531de6 100644 --- a/activerecord/test/models/categorization.rb +++ b/activerecord/test/models/categorization.rb @@ -12,7 +12,7 @@ end class SpecialCategorization < ActiveRecord::Base self.table_name = 'categorizations' - default_scope where(:special => true) + default_scope { where(:special => true) } belongs_to :author belongs_to :category diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb index 02b85fd38a..ab3139680c 100644 --- a/activerecord/test/models/category.rb +++ b/activerecord/test/models/category.rb @@ -27,7 +27,7 @@ class Category < ActiveRecord::Base has_many :authors, :through => :categorizations has_many :authors_with_select, :through => :categorizations, :source => :author, :select => 'authors.*, categorizations.post_id' - scope :general, :conditions => { :name => 'General' } + scope :general, -> { where(:name => 'General') } end class SpecialCategory < Category diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index 88b139d931..00f5a74070 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -1,12 +1,10 @@ class Comment < ActiveRecord::Base scope :limit_by, lambda {|l| limit(l) } - scope :containing_the_letter_e, :conditions => "comments.body LIKE '%e%'" - scope :not_again, where("comments.body NOT LIKE '%again%'") - scope :for_first_post, :conditions => { :post_id => 1 } - scope :for_first_author, - :joins => :post, - :conditions => { "posts.author_id" => 1 } - scope :created + scope :containing_the_letter_e, -> { where("comments.body LIKE '%e%'") } + scope :not_again, -> { where("comments.body NOT LIKE '%again%'") } + scope :for_first_post, -> { where(:post_id => 1) } + scope :for_first_author, -> { joins(:post).where("posts.author_id" => 1) } + scope :created, -> { scoped } belongs_to :post, :counter_cache => true has_many :ratings @@ -25,7 +23,7 @@ class Comment < ActiveRecord::Base def self.all_as_method all end - scope :all_as_scope, {} + scope :all_as_scope, -> { scoped } end class SpecialComment < Comment diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 4dc9fff9fd..4cc4947e3b 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -45,7 +45,7 @@ class Developer < ActiveRecord::Base has_many :audit_logs - scope :jamises, :conditions => {:name => 'Jamis'} + scope :jamises, -> { where(:name => 'Jamis') } validates_inclusion_of :salary, :in => 50000..200000 validates_length_of :name, :within => 3..20 @@ -88,20 +88,20 @@ end class DeveloperWithSelect < ActiveRecord::Base self.table_name = 'developers' - default_scope select('name') + default_scope { select('name') } end class DeveloperWithIncludes < ActiveRecord::Base self.table_name = 'developers' has_many :audit_logs, :foreign_key => :developer_id - default_scope includes(:audit_logs) + default_scope { includes(:audit_logs) } end class DeveloperOrderedBySalary < ActiveRecord::Base self.table_name = 'developers' - default_scope :order => 'salary DESC' + default_scope { order('salary DESC') } - 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 @@ -112,7 +112,7 @@ end class DeveloperCalledDavid < ActiveRecord::Base self.table_name = 'developers' - default_scope where("name = 'David'") + default_scope { where("name = 'David'") } end class LazyLambdaDeveloperCalledDavid < ActiveRecord::Base @@ -140,7 +140,7 @@ end class ClassMethodReferencingScopeDeveloperCalledDavid < ActiveRecord::Base self.table_name = 'developers' - scope :david, where(:name => 'David') + scope :david, -> { where(:name => 'David') } def self.default_scope david @@ -149,40 +149,40 @@ end class LazyBlockReferencingScopeDeveloperCalledDavid < ActiveRecord::Base self.table_name = 'developers' - scope :david, where(:name => 'David') + scope :david, -> { where(:name => 'David') } default_scope { david } end class DeveloperCalledJamis < ActiveRecord::Base self.table_name = 'developers' - default_scope where(:name => 'Jamis') - scope :poor, where('salary < 150000') + default_scope { where(:name => 'Jamis') } + scope :poor, -> { where('salary < 150000') } end class PoorDeveloperCalledJamis < ActiveRecord::Base self.table_name = 'developers' - default_scope where(:name => 'Jamis', :salary => 50000) + default_scope -> { where(:name => 'Jamis', :salary => 50000) } end class InheritedPoorDeveloperCalledJamis < DeveloperCalledJamis self.table_name = 'developers' - default_scope where(:salary => 50000) + default_scope -> { where(:salary => 50000) } end class MultiplePoorDeveloperCalledJamis < ActiveRecord::Base self.table_name = 'developers' - default_scope where(:name => 'Jamis') - default_scope where(:salary => 50000) + default_scope -> { where(:name => 'Jamis') } + default_scope -> { where(:salary => 50000) } end module SalaryDefaultScope extend ActiveSupport::Concern - included { default_scope where(:salary => 50000) } + included { default_scope { where(:salary => 50000) } } end class ModuleIncludedPoorDeveloperCalledJamis < DeveloperCalledJamis @@ -195,7 +195,7 @@ class EagerDeveloperWithDefaultScope < ActiveRecord::Base self.table_name = 'developers' has_and_belongs_to_many :projects, :foreign_key => 'developer_id', :join_table => 'developers_projects', :order => 'projects.id' - default_scope includes(:projects) + default_scope { includes(:projects) } end class EagerDeveloperWithClassMethodDefaultScope < ActiveRecord::Base diff --git a/activerecord/test/models/organization.rb b/activerecord/test/models/organization.rb index 4a4111833f..72e7bade68 100644 --- a/activerecord/test/models/organization.rb +++ b/activerecord/test/models/organization.rb @@ -8,5 +8,5 @@ class Organization < ActiveRecord::Base has_one :author, :primary_key => :name has_one :author_owned_essay_category, :through => :author, :source => :owned_essay_category - scope :clubs, { :from => 'clubs' } + scope :clubs, -> { from('clubs') } end diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index 84bc901b5e..b7d5dabc4f 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -27,8 +27,8 @@ class Person < ActiveRecord::Base has_many :agents_posts, :through => :agents, :source => :posts has_many :agents_posts_authors, :through => :agents_posts, :source => :author - scope :males, :conditions => { :gender => 'M' } - scope :females, :conditions => { :gender => 'F' } + scope :males, -> { where(:gender => 'M') } + scope :females, -> { where(:gender => 'F') } end class PersonWithDependentDestroyJobs < ActiveRecord::Base diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 0fc22ac6a3..5002ab9ff8 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -5,8 +5,8 @@ class Post < ActiveRecord::Base end end - scope :containing_the_letter_a, where("body LIKE '%a%'") - scope :ranked_by_comments, order("comments_count DESC") + 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| { @@ -30,8 +30,8 @@ class Post < ActiveRecord::Base has_one :first_comment, :class_name => 'Comment', :order => 'id ASC' has_one :last_comment, :class_name => 'Comment', :order => 'id desc' - scope :with_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'SpecialComment'} } - scope :with_very_special_comments, joins(:comments).where(:comments => {:type => 'VerySpecialComment'}) + scope :with_special_comments, -> { joins(:comments).where(: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} } } } @@ -171,7 +171,7 @@ end class FirstPost < ActiveRecord::Base self.table_name = 'posts' - default_scope where(:id => 1) + default_scope { where(:id => 1) } has_many :comments, :foreign_key => :post_id has_one :comment, :foreign_key => :post_id @@ -179,16 +179,16 @@ end class PostWithDefaultInclude < ActiveRecord::Base self.table_name = 'posts' - default_scope includes(:comments) + default_scope { includes(:comments) } has_many :comments, :foreign_key => :post_id end class PostWithDefaultScope < ActiveRecord::Base self.table_name = 'posts' - default_scope :order => :title + default_scope { order(:title) } end class SpecialPostWithDefaultScope < ActiveRecord::Base self.table_name = 'posts' - default_scope where(:id => [1, 5,6]) + default_scope { where(:id => [1, 5,6]) } end diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb index efe1ce67da..32ce164995 100644 --- a/activerecord/test/models/project.rb +++ b/activerecord/test/models/project.rb @@ -32,7 +32,7 @@ class Project < ActiveRecord::Base def self.all_as_method all end - scope :all_as_scope, {} + scope :all_as_scope, -> { scoped } end class SpecialProject < Project diff --git a/activerecord/test/models/reference.rb b/activerecord/test/models/reference.rb index c5af0b5d5f..561b431766 100644 --- a/activerecord/test/models/reference.rb +++ b/activerecord/test/models/reference.rb @@ -19,5 +19,5 @@ end class BadReference < ActiveRecord::Base self.table_name = 'references' - default_scope where(:favourite => false) + default_scope { where(:favourite => false) } end diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb index 6adfe0ae3c..53bc95e5f2 100644 --- a/activerecord/test/models/reply.rb +++ b/activerecord/test/models/reply.rb @@ -1,7 +1,7 @@ require 'models/topic' class Reply < Topic - scope :base + scope :base, -> { scoped } 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 8bcb9df8a8..785839be75 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -1,21 +1,24 @@ class Topic < ActiveRecord::Base - scope :base + scope :base, -> { scoped } scope :written_before, lambda { |time| if time { :conditions => ['written_on < ?', time] } end } - scope :approved, :conditions => {:approved => true} - scope :rejected, :conditions => {:approved => false} + scope :approved, -> { where(:approved => true) } + scope :rejected, -> { where(:approved => false) } scope :scope_with_lambda, lambda { scoped } - scope :by_lifo, :conditions => {:author_name => 'lifo'} + scope :by_lifo, -> { where(:author_name => 'lifo') } - 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 + ActiveSupport::Deprecation.silence do + scope :approved_as_hash_condition, :conditions => {:topics => {:approved => true}} + scope :replied, :conditions => ['replies_count > 0'] + end + + scope 'approved_as_string', -> { where(:approved => true) } + scope :anonymous_extension, -> { scoped } do def one 1 end @@ -42,8 +45,8 @@ class Topic < ActiveRecord::Base 2 end end - scope :named_extension, :extend => NamedExtension - 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" diff --git a/activerecord/test/models/toy.rb b/activerecord/test/models/toy.rb index 0377e50011..ddc7048a56 100644 --- a/activerecord/test/models/toy.rb +++ b/activerecord/test/models/toy.rb @@ -2,5 +2,5 @@ class Toy < ActiveRecord::Base self.primary_key = :toy_id belongs_to :pet - scope :with_pet, joins(:pet) + scope :with_pet, -> { joins(:pet) } end diff --git a/activerecord/test/models/without_table.rb b/activerecord/test/models/without_table.rb index 184ab1649e..50c824e4ac 100644 --- a/activerecord/test/models/without_table.rb +++ b/activerecord/test/models/without_table.rb @@ -1,3 +1,3 @@ class WithoutTable < ActiveRecord::Base - default_scope where(:published => true) + default_scope -> { where(:published => true) } end diff --git a/activesupport/lib/active_support/core_ext/proc.rb b/activesupport/lib/active_support/core_ext/proc.rb index 94bb5fb0cb..cd63740940 100644 --- a/activesupport/lib/active_support/core_ext/proc.rb +++ b/activesupport/lib/active_support/core_ext/proc.rb @@ -1,7 +1,10 @@ require "active_support/core_ext/kernel/singleton_class" +require "active_support/deprecation" class Proc #:nodoc: def bind(object) + ActiveSupport::Deprecation.warn 'Proc#bind is deprecated and will be removed in future versions', caller + block, time = self, Time.now object.class_eval do method_name = "__bind_#{time.to_i}_#{time.usec}" diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 13f675c654..8cf7bdafda 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -1,3 +1,6 @@ +require 'active_support/notifications/instrumenter' +require 'active_support/notifications/fanout' + module ActiveSupport # = Notifications # @@ -105,10 +108,6 @@ module ActiveSupport # to log subscribers in a thread. You can use any queue implementation you want. # module Notifications - autoload :Instrumenter, 'active_support/notifications/instrumenter' - autoload :Event, 'active_support/notifications/instrumenter' - autoload :Fanout, 'active_support/notifications/fanout' - @instrumenters = Hash.new { |h,k| h[k] = notifier.listening?(k) } class << self diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 4c1b7b2784..17c99089c1 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -9,7 +9,7 @@ module ActiveSupport end def subscribe(pattern = nil, block = Proc.new) - subscriber = Subscriber.new(pattern, block) + subscriber = Subscribers.new pattern, block @subscribers << subscriber @listeners_for.clear subscriber @@ -20,6 +20,14 @@ module ActiveSupport @listeners_for.clear end + def start(name, id, payload) + listeners_for(name).each { |s| s.start(name, id, payload) } + end + + def finish(name, id, payload) + listeners_for(name).each { |s| s.finish(name, id, payload) } + end + def publish(name, *args) listeners_for(name).each { |s| s.publish(name, *args) } end @@ -36,23 +44,89 @@ module ActiveSupport def wait end - class Subscriber #:nodoc: - def initialize(pattern, delegate) - @pattern = pattern - @delegate = delegate + module Subscribers # :nodoc: + def self.new(pattern, listener) + if listener.respond_to?(:call) + subscriber = Timed.new pattern, listener + else + subscriber = Evented.new pattern, listener + end + + unless pattern + AllMessages.new(subscriber) + else + subscriber + end end - def publish(message, *args) - @delegate.call(message, *args) + class Evented #:nodoc: + def initialize(pattern, delegate) + @pattern = pattern + @delegate = delegate + end + + def start(name, id, payload) + @delegate.start name, id, payload + end + + def finish(name, id, payload) + @delegate.finish name, id, payload + end + + def subscribed_to?(name) + @pattern === name.to_s + end + + def matches?(subscriber_or_name) + self === subscriber_or_name || + @pattern && @pattern === subscriber_or_name + end end - def subscribed_to?(name) - !@pattern || @pattern === name.to_s + class Timed < Evented + def initialize(pattern, delegate) + @timestack = Hash.new { |h,id| + h[id] = Hash.new { |ids,name| ids[name] = [] } + } + super + end + + def publish(name, *args) + @delegate.call name, *args + end + + def start(name, id, payload) + @timestack[id][name].push Time.now + end + + def finish(name, id, payload) + started = @timestack[id][name].pop + @delegate.call(name, started, Time.now, id, payload) + end end - def matches?(subscriber_or_name) - self === subscriber_or_name || - @pattern && @pattern === subscriber_or_name + class AllMessages # :nodoc: + def initialize(delegate) + @delegate = delegate + end + + def start(name, id, payload) + @delegate.start name, id, payload + end + + def finish(name, id, payload) + @delegate.finish name, id, payload + end + + def publish(name, *args) + @delegate.publish name, *args + end + + def subscribed_to?(name) + true + end + + alias :matches? :=== end end end diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index 547df5c731..58e292c658 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -1,5 +1,6 @@ module ActiveSupport module Notifications + # Instrumentors are stored in a thread local. class Instrumenter attr_reader :id @@ -12,15 +13,14 @@ module ActiveSupport # and publish it. Notice that events get sent even if an error occurs # in the passed-in block def instrument(name, payload={}) - started = Time.now - + @notifier.start(name, @id, payload) begin yield rescue Exception => e payload[:exception] = [e.class.name, e.message] raise e ensure - @notifier.publish(name, started, Time.now, @id, payload) + @notifier.finish(name, @id, payload) end end diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb index 0f4a06468a..0fbe6e5b29 100644 --- a/activesupport/lib/active_support/rescuable.rb +++ b/activesupport/lib/active_support/rescuable.rb @@ -108,7 +108,11 @@ module ActiveSupport when Symbol method(rescuer) when Proc - rescuer.bind(self) + if rescuer.arity == 0 + Proc.new { instance_exec(&rescuer) } + else + Proc.new { |exception| instance_exec(exception, &rescuer) } + end end end end diff --git a/activesupport/test/core_ext/proc_test.rb b/activesupport/test/core_ext/proc_test.rb index 690bfd3bf8..c4d5592196 100644 --- a/activesupport/test/core_ext/proc_test.rb +++ b/activesupport/test/core_ext/proc_test.rb @@ -3,10 +3,12 @@ require 'active_support/core_ext/proc' class ProcTests < ActiveSupport::TestCase def test_bind_returns_method_with_changed_self - block = Proc.new { self } - assert_equal self, block.call - bound_block = block.bind("hello") - assert_not_equal block, bound_block - assert_equal "hello", bound_block.call + assert_deprecated do + block = Proc.new { self } + assert_equal self, block.call + bound_block = block.bind("hello") + assert_not_equal block, bound_block + assert_equal "hello", bound_block.call + end end end diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb index 9bfc2b16ab..4cd60fdc39 100644 --- a/railties/lib/rails/source_annotation_extractor.rb +++ b/railties/lib/rails/source_annotation_extractor.rb @@ -22,7 +22,7 @@ class SourceAnnotationExtractor # If +options+ has a flag <tt>:tag</tt> the tag is shown as in the example above. # Otherwise the string contains just line and text. def to_s(options={}) - s = "[#{line.to_s.rjust(options[:indent])}]" + s = "[#{line.to_s.rjust(options[:indent])}] " s << "[#{tag}] " if options[:tag] s << text end diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb index b66433f64d..04abf9e3a1 100644 --- a/railties/test/application/rake/notes_test.rb +++ b/railties/test/application/rake/notes_test.rb @@ -28,7 +28,7 @@ module ApplicationTests Dir.chdir(app_path) do output = `bundle exec rake notes` - lines = output.scan(/\[([0-9\s]+)\]/).flatten + lines = output.scan(/\[([0-9\s]+)\](\s)/) assert_match /note in erb/, output assert_match /note in haml/, output @@ -38,8 +38,9 @@ module ApplicationTests assert_equal 5, lines.size - lines.each do |line_number| - assert_equal 4, line_number.size + lines.each do |line| + assert_equal 4, line[0].size + assert_equal ' ', line[1] end end diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb index 4e08e5dae1..fd84164340 100644 --- a/railties/test/generators/migration_generator_test.rb +++ b/railties/test/generators/migration_generator_test.rb @@ -41,6 +41,24 @@ class MigrationGeneratorTest < Rails::Generators::TestCase end end + def test_remove_migration_with_indexed_attribute + migration = "remove_title_body_from_posts" + run_generator [migration, "title:string:index", "body:text"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :up, content do |up| + assert_match(/remove_column :posts, :title/, up) + assert_match(/remove_column :posts, :body/, up) + end + + assert_method :down, content do |down| + assert_match(/add_column :posts, :title, :string/, down) + assert_match(/add_column :posts, :body, :text/, down) + assert_match(/add_index :posts, :title/, down) + end + end + end + def test_remove_migration_with_attributes migration = "remove_title_body_from_posts" run_generator [migration, "title:string", "body:text"] |