Skip to content

Commit

Permalink
Merge branch 'master' into nested_has_many_through
Browse files Browse the repository at this point in the history
  • Loading branch information
jonleighton committed Mar 16, 2011
2 parents 9abc94c + 0eae625 commit 8aaf3c1
Show file tree
Hide file tree
Showing 30 changed files with 164 additions and 81 deletions.
1 change: 1 addition & 0 deletions Rakefile
Expand Up @@ -85,6 +85,7 @@ RDoc::Task.new do |rdoc|
rdoc.rdoc_files.include('actionmailer/README.rdoc')
rdoc.rdoc_files.include('actionmailer/CHANGELOG')
rdoc.rdoc_files.include('actionmailer/lib/action_mailer/base.rb')
rdoc.rdoc_files.include('actionmailer/lib/action_mailer/mail_helper.rb')
rdoc.rdoc_files.exclude('actionmailer/lib/action_mailer/vendor/*')

rdoc.rdoc_files.include('activesupport/README.rdoc')
Expand Down
8 changes: 4 additions & 4 deletions actionpack/lib/abstract_controller/view_paths.rb
Expand Up @@ -36,7 +36,7 @@ module ClassMethods
# ==== Parameters
# * <tt>path</tt> - If a String is provided, it gets converted into
# the default view path. You may also provide a custom view path
# (see ActionView::ViewPathSet for more information)
# (see ActionView::PathSet for more information)
def append_view_path(path)
self.view_paths = view_paths.dup + Array(path)
end
Expand All @@ -46,7 +46,7 @@ def append_view_path(path)
# ==== Parameters
# * <tt>path</tt> - If a String is provided, it gets converted into
# the default view path. You may also provide a custom view path
# (see ActionView::ViewPathSet for more information)
# (see ActionView::PathSet for more information)
def prepend_view_path(path)
self.view_paths = Array(path) + view_paths.dup
end
Expand All @@ -59,8 +59,8 @@ def view_paths
# Set the view paths.
#
# ==== Parameters
# * <tt>paths</tt> - If a ViewPathSet is provided, use that;
# otherwise, process the parameter into a ViewPathSet.
# * <tt>paths</tt> - If a PathSet is provided, use that;
# otherwise, process the parameter into a PathSet.
def view_paths=(paths)
self._view_paths = ActionView::Base.process_view_paths(paths)
self._view_paths.freeze
Expand Down
9 changes: 5 additions & 4 deletions actionpack/lib/action_dispatch/routing/mapper.rb
Expand Up @@ -195,8 +195,8 @@ def constraints

def request_method_condition
if via = @options[:via]
via = Array(via).map { |m| m.to_s.dasherize.upcase }
{ :request_method => %r[^#{via.join('|')}$] }
list = Array(via).map { |m| m.to_s.dasherize.upcase }
{ :request_method => list }
else
{ }
end
Expand Down Expand Up @@ -365,8 +365,9 @@ def root(options = {})
#
# See <tt>Scoping#defaults</tt> for its scope equivalent.
def match(path, options=nil)
mapping = Mapping.new(@set, @scope, path, options || {}).to_route
@set.add_route(*mapping)
mapping = Mapping.new(@set, @scope, path, options || {})
app, conditions, requirements, defaults, as, anchor = mapping.to_route
@set.add_route(app, conditions, requirements, defaults, as, anchor)
self
end

Expand Down
23 changes: 13 additions & 10 deletions actionpack/lib/action_dispatch/routing/route.rb
Expand Up @@ -12,6 +12,8 @@ def initialize(set, app, conditions, requirements, defaults, name, anchor)
@defaults = defaults
@name = name

# FIXME: we should not be doing this much work in a constructor.

@requirements = requirements.merge(defaults)
@requirements.delete(:controller) if @requirements[:controller].is_a?(Regexp)
@requirements.delete_if { |k, v|
Expand All @@ -23,21 +25,22 @@ def initialize(set, app, conditions, requirements, defaults, name, anchor)
conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS, anchor)
end

@conditions = Hash[conditions.map { |k,v| [k, Rack::Mount::RegexpWithNamedGroups.new(v)] }]
@verbs = conditions[:request_method] || []

@conditions = conditions.dup

# Rack-Mount requires that :request_method be a regular expression.
# :request_method represents the HTTP verb that matches this route.
#
# Here we munge values before they get sent on to rack-mount.
@conditions[:request_method] = %r[^#{verb}$] unless @verbs.empty?
@conditions[:path_info] = Rack::Mount::RegexpWithNamedGroups.new(@conditions[:path_info]) if @conditions[:path_info]
@conditions.delete_if{ |k,v| k != :path_info && !valid_condition?(k) }
@requirements.delete_if{ |k,v| !valid_condition?(k) }
end

def verb
if method = conditions[:request_method]
case method
when Regexp
source = method.source.upcase
source =~ /\A\^[-A-Z|]+\$\Z/ ? source[1..-2] : source
else
method.to_s.upcase
end
end
@verbs.join '|'
end

def segment_keys
Expand Down
2 changes: 1 addition & 1 deletion actionpack/lib/action_view/helpers/number_helper.rb
Expand Up @@ -472,7 +472,7 @@ def number_to_human(number, options = {})
end.keys.map{|e_name| inverted_du[e_name] }.sort_by{|e| -e}

number_exponent = number != 0 ? Math.log10(number.abs).floor : 0
display_exponent = unit_exponents.find{|e| number_exponent >= e }
display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0
number /= 10 ** display_exponent

unit = case units
Expand Down
2 changes: 1 addition & 1 deletion actionpack/lib/action_view/template/resolver.rb
Expand Up @@ -36,7 +36,7 @@ def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[])
# because Resolver guarantees that the arguments are present and
# normalized.
def find_templates(name, prefix, partial, details)
raise NotImplementedError
raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details) method"
end

# Helpers that builds a path. Useful for building virtual paths.
Expand Down
4 changes: 3 additions & 1 deletion actionpack/test/template/number_helper_test.rb
Expand Up @@ -195,7 +195,9 @@ def test_number_to_human_size_with_custom_delimiter_and_separator

def test_number_to_human
assert_equal '-123', number_to_human(-123)
assert_equal '0', number_to_human(0)
assert_equal '-0.5', number_to_human(-0.5)
assert_equal '0', number_to_human(0)
assert_equal '0.5', number_to_human(0.5)
assert_equal '123', number_to_human(123)
assert_equal '1.23 Thousand', number_to_human(1234)
assert_equal '12.3 Thousand', number_to_human(12345)
Expand Down
4 changes: 2 additions & 2 deletions activemodel/lib/active_model/lint.rb
Expand Up @@ -23,7 +23,7 @@ module Tests
def test_to_key
assert model.respond_to?(:to_key), "The model should respond to to_key"
def model.persisted?() false end
assert model.to_key.nil?
assert model.to_key.nil?, "to_key should return nil when `persisted?` returns false"
end

# == Responds to <tt>to_param</tt>
Expand All @@ -40,7 +40,7 @@ def test_to_param
assert model.respond_to?(:to_param), "The model should respond to to_param"
def model.to_key() [1] end
def model.persisted?() false end
assert model.to_param.nil?
assert model.to_param.nil?, "to_param should return nil when `persisted?` returns false"
end

# == Responds to <tt>valid?</tt>
Expand Down
3 changes: 2 additions & 1 deletion activemodel/lib/active_model/validations/length.rb
Expand Up @@ -43,7 +43,8 @@ def validate_each(record, attribute, value)

value ||= [] if key == :maximum

next if value && value.size.send(validity_check, check_value)
value_length = value.respond_to?(:length) ? value.length : value.to_s.length
next if value_length.send(validity_check, check_value)

errors_options = options.except(*RESERVED_OPTIONS)
errors_options[:count] = check_value
Expand Down
4 changes: 2 additions & 2 deletions activemodel/lib/active_model/validator.rb
Expand Up @@ -120,7 +120,7 @@ def kind
# Override this method in subclasses with validation logic, adding errors
# to the records +errors+ array where necessary.
def validate(record)
raise NotImplementedError
raise NotImplementedError, "Subclasses must implement a validate(record) method."
end
end

Expand Down Expand Up @@ -156,7 +156,7 @@ def validate(record)
# Override this method in subclasses with the validation logic, adding
# errors to the records +errors+ array where necessary.
def validate_each(record, attribute, value)
raise NotImplementedError
raise NotImplementedError, "Subclasses must implement a validate_each(record, attribute, value) method"
end

# Hook method that gets called by the initializer allowing verification
Expand Down
11 changes: 11 additions & 0 deletions activemodel/test/cases/validations/length_validation_test.rb
Expand Up @@ -342,6 +342,17 @@ def test_validates_length_of_with_block
assert_equal ["Your essay must be at least 5 words."], t.errors[:content]
end

def test_validates_length_of_for_fixnum
Topic.validates_length_of(:approved, :is => 4)

t = Topic.new("title" => "uhohuhoh", "content" => "whatever", :approved => 1)
assert t.invalid?
assert t.errors[:approved].any?

t = Topic.new("title" => "uhohuhoh", "content" => "whatever", :approved => 1234)
assert t.valid?
end

def test_validates_length_of_for_ruby_class
Person.validates_length_of :karma, :minimum => 5

Expand Down
Expand Up @@ -37,7 +37,7 @@ def find_target

# Implemented by subclasses
def replace(record)
raise NotImplementedError
raise NotImplementedError, "Subclasses must implement a replace(record) method"
end

def set_new_record(record)
Expand Down
2 changes: 1 addition & 1 deletion activerecord/lib/active_record/locking/optimistic.rb
Expand Up @@ -23,7 +23,7 @@ module Locking
# p2.first_name = "should fail"
# p2.save # Raises a ActiveRecord::StaleObjectError
#
# Optimistic locking will also check for stale data when objects are destroyed. Example:
# Optimistic locking will also check for stale data when objects are destroyed. Example:
#
# p1 = Person.find(1)
# p2 = Person.find(1)
Expand Down
6 changes: 3 additions & 3 deletions activerecord/lib/active_record/locking/pessimistic.rb
Expand Up @@ -9,9 +9,8 @@ module Locking
# Account.find(1, :lock => true)
#
# Pass <tt>:lock => 'some locking clause'</tt> to give a database-specific locking clause
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'.
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
#
# Example:
# Account.transaction do
# # select * from accounts where name = 'shugo' limit 1 for update
# shugo = Account.where("name = 'shugo'").lock(true).first
Expand All @@ -24,6 +23,7 @@ module Locking
#
# You can also use ActiveRecord::Base#lock! method to lock one record by id.
# This may be better if you don't need to lock every row. Example:
#
# Account.transaction do
# # select * from accounts where ...
# accounts = Account.where(...).all
Expand All @@ -44,7 +44,7 @@ module Locking
module Pessimistic
# Obtain a row lock on this record. Reloads the record to obtain the requested
# lock. Pass an SQL locking clause to append the end of the SELECT statement
# or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
# or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
# the locked record.
def lock!(lock = true)
reload(:lock => lock) if persisted?
Expand Down
5 changes: 4 additions & 1 deletion activerecord/lib/active_record/relation.rb
Expand Up @@ -110,7 +110,10 @@ def size

# Returns true if there are no records.
def empty?
loaded? ? @records.empty? : count.zero?
return @records.empty? if loaded?

c = count
c.respond_to?(:zero?) ? c.zero? : c.empty?
end

def any?
Expand Down
15 changes: 11 additions & 4 deletions activerecord/lib/active_record/validations/uniqueness.rb
Expand Up @@ -173,10 +173,17 @@ module ClassMethods
# This technique is also known as optimistic concurrency control:
# http://en.wikipedia.org/wiki/Optimistic_concurrency_control
#
# Active Record currently provides no way to distinguish unique
# index constraint errors from other types of database errors, so you
# will have to parse the (database-specific) exception message to detect
# such a case.
# The bundled ActiveRecord::ConnectionAdapters distinguish unique index
# constraint errors from other types of database errors by throwing an
# ActiveRecord::RecordNotUnique exception.
# For other adapters you will have to parse the (database-specific) exception
# message to detect such a case.
# The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
# * ActiveRecord::ConnectionAdapters::MysqlAdapter
# * ActiveRecord::ConnectionAdapters::Mysql2Adapter
# * ActiveRecord::ConnectionAdapters::SQLiteAdapter
# * ActiveRecord::ConnectionAdapters::SQLite3Adapter
# * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
#
def validates_uniqueness_of(*attr_names)
validates_with UniquenessValidator, _merge_attributes(attr_names)
Expand Down
26 changes: 26 additions & 0 deletions activerecord/test/cases/relations_test.rb
Expand Up @@ -673,6 +673,32 @@ def test_count_complex_chained_relations
assert_equal expected, posts.count
end

def test_empty
posts = Post.scoped

assert_queries(1) { assert_equal false, posts.empty? }
assert ! posts.loaded?

no_posts = posts.where(:title => "")
assert_queries(1) { assert_equal true, no_posts.empty? }
assert ! no_posts.loaded?

best_posts = posts.where(:comments_count => 0)
best_posts.to_a # force load
assert_no_queries { assert_equal false, best_posts.empty? }
end

def test_empty_complex_chained_relations
posts = Post.select("comments_count").where("id is not null").group("author_id").where("comments_count > 0")

assert_queries(1) { assert_equal false, posts.empty? }
assert ! posts.loaded?

no_posts = posts.where(:title => "")
assert_queries(1) { assert_equal true, no_posts.empty? }
assert ! no_posts.loaded?
end

def test_any
posts = Post.scoped

Expand Down
3 changes: 2 additions & 1 deletion activesupport/lib/active_support/callbacks.rb
@@ -1,3 +1,4 @@
require 'active_support/concern'
require 'active_support/descendants_tracker'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/attribute'
Expand Down Expand Up @@ -415,7 +416,7 @@ def __update_callbacks(name, filters = [], block = nil) #:nodoc:
options = filters.last.is_a?(Hash) ? filters.pop : {}
filters.unshift(block) if block

([self] + ActiveSupport::DescendantsTracker.descendants(self)).each do |target|
([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target|
chain = target.send("_#{name}_callbacks")
yield target, chain.dup, type, filters, options
target.__define_runner(name)
Expand Down
@@ -1,3 +1,5 @@
require 'active_support/deprecation'

class Module
# Declare that a method has been deprecated.
# deprecate :foo
Expand Down
30 changes: 30 additions & 0 deletions activesupport/test/callback_inheritance_test.rb
Expand Up @@ -82,6 +82,30 @@ def do_nothing
end
end

class CountingParent
include ActiveSupport::Callbacks

attr_reader :count

define_callbacks :dispatch

def initialize
@count = 0
end

def count!
@count += 1
end

def dispatch
run_callbacks(:dispatch)
self
end
end

class CountingChild < CountingParent
end

class BasicCallbacksTest < Test::Unit::TestCase
def setup
@index = GrandParent.new("index").dispatch
Expand Down Expand Up @@ -147,4 +171,10 @@ def test_callbacks_looks_to_the_superclass_before_running
child = EmptyChild.new.dispatch
assert child.performed?
end

def test_callbacks_should_be_performed_once_in_child_class
CountingParent.set_callback(:dispatch, :before) { count! }
child = CountingChild.new.dispatch
assert_equal 1, child.count
end
end
4 changes: 2 additions & 2 deletions railties/guides/source/3_0_release_notes.textile
Expand Up @@ -59,12 +59,12 @@ The +config.gem+ method is gone and has been replaced by using +bundler+ and a +

h4. Upgrade Process

To help with the upgrade process, a plugin named "Rails Upgrade":http://github.com/rails/rails_upgrade has been created to automate part of it.
To help with the upgrade process, a plugin named "Rails Upgrade":http://github.com/jm/rails_upgrade has been created to automate part of it.

Simply install the plugin, then run +rake rails:upgrade:check+ to check your app for pieces that need to be updated (with links to information on how to update them). It also offers a task to generate a +Gemfile+ based on your current +config.gem+ calls and a task to generate a new routes file from your current one. To get the plugin, simply run the following:

<shell>
$ ruby script/plugin install git://github.com/rails/rails_upgrade.git
$ ruby script/plugin install git://github.com/jm/rails_upgrade.git
</shell>

You can see an example of how that works at "Rails Upgrade is now an Official Plugin":http://omgbloglol.com/post/364624593/rails-upgrade-is-now-an-official-plugin
Expand Down

0 comments on commit 8aaf3c1

Please sign in to comment.