aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile6
-rw-r--r--actionmailer/lib/action_mailer/base.rb17
-rw-r--r--actionmailer/lib/action_mailer/delivery_methods.rb1
-rw-r--r--actionmailer/lib/action_mailer/log_subscriber.rb5
-rw-r--r--actionmailer/lib/action_mailer/mail_helper.rb3
-rw-r--r--actionmailer/test/mailers/base_mailer.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb5
-rw-r--r--actionpack/test/dispatch/routing_test.rb13
-rw-r--r--activerecord/CHANGELOG.md2
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb19
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb8
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb6
-rw-r--r--activerecord/lib/active_record/querying.rb7
-rw-r--r--activerecord/lib/active_record/railties/databases.rake2
-rw-r--r--activerecord/lib/active_record/relation/merger.rb45
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb4
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb2
-rw-r--r--activerecord/test/cases/associations_test.rb2
-rw-r--r--activerecord/test/cases/base_test.rb2
-rw-r--r--activerecord/test/cases/batches_test.rb12
-rw-r--r--activerecord/test/cases/counter_cache_test.rb12
-rw-r--r--activerecord/test/cases/reflection_test.rb6
-rw-r--r--activerecord/test/cases/relations_test.rb22
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb2
-rw-r--r--activerecord/test/models/reply.rb1
-rw-r--r--activerecord/test/schema/schema.rb1
-rw-r--r--activesupport/lib/active_support/callbacks.rb6
-rw-r--r--guides/source/active_record_querying.md1
-rw-r--r--guides/source/active_record_validations.md14
-rw-r--r--guides/source/migrations.md8
-rw-r--r--guides/source/upgrading_ruby_on_rails.md53
-rw-r--r--railties/CHANGELOG.md2
-rw-r--r--railties/lib/rails/generators/named_base.rb2
-rw-r--r--railties/lib/rails/tasks.rb2
36 files changed, 233 insertions, 70 deletions
diff --git a/Gemfile b/Gemfile
index d1fa0c2a1d..b0f91c6dac 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,7 +2,11 @@ source 'https://rubygems.org'
gemspec
-gem 'mocha', '~> 0.14'
+# This needs to be with require false as it is
+# loaded after loading the test library to
+# ensure correct loading order
+gem 'mocha', '~> 0.14', require: false
+
gem 'rack-cache', '~> 1.2'
gem 'bcrypt-ruby', '~> 3.0.0'
gem 'jquery-rails', '~> 2.2.0'
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index d2e01cfd4c..fcdd6747b8 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -151,9 +151,9 @@ module ActionMailer
#
# For example, if the following templates exist:
# * signup_notification.text.erb
- # * signup_notification.text.html.erb
- # * signup_notification.text.xml.builder
- # * signup_notification.text.yaml.erb
+ # * signup_notification.html.erb
+ # * signup_notification.xml.builder
+ # * signup_notification.yaml.erb
#
# Each would be rendered and added as a separate part to the message, with the corresponding content
# type. The content type for the entire message is automatically set to <tt>multipart/alternative</tt>,
@@ -175,7 +175,7 @@ module ActionMailer
# end
# end
#
- # Which will (if it had both a <tt>welcome.text.erb</tt> and <tt>welcome.text.html.erb</tt>
+ # Which will (if it had both a <tt>welcome.text.erb</tt> and <tt>welcome.html.erb</tt>
# template in the view directory), send a complete <tt>multipart/mixed</tt> email with two parts,
# the first part being a <tt>multipart/alternative</tt> with the text and HTML email parts inside,
# and the second being a <tt>application/pdf</tt> with a Base64 encoded copy of the file.pdf book
@@ -720,6 +720,15 @@ module ActionMailer
protected
+ # Used by #mail to set the content type of the message.
+ #
+ # It will use the given +user_content_type+, or multipart if the mail
+ # message has any attachments. If the attachments are inline, the content
+ # type will be "multipart/related", otherwise "multipart/mixed".
+ #
+ # If there is no content type passed in via headers, and there are no
+ # attachments, or the message is multipart, then the default content type is
+ # used.
def set_content_type(m, user_content_type, class_default)
params = m.content_type_parameters || {}
case
diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb
index caea3d7535..9a1a27c8ed 100644
--- a/actionmailer/lib/action_mailer/delivery_methods.rb
+++ b/actionmailer/lib/action_mailer/delivery_methods.rb
@@ -38,6 +38,7 @@ module ActionMailer
add_delivery_method :test, Mail::TestMailer
end
+ # Helpers for creating and wrapping delivery behavior, used by DeliveryMethods.
module ClassMethods
# Provides a list of emails that have been delivered by Mail::TestMailer
delegate :deliveries, :deliveries=, to: Mail::TestMailer
diff --git a/actionmailer/lib/action_mailer/log_subscriber.rb b/actionmailer/lib/action_mailer/log_subscriber.rb
index 3fe64759ac..c108156792 100644
--- a/actionmailer/lib/action_mailer/log_subscriber.rb
+++ b/actionmailer/lib/action_mailer/log_subscriber.rb
@@ -1,5 +1,8 @@
module ActionMailer
+ # Implements the ActiveSupport::LogSubscriber for logging notifications when
+ # email is delivered and received.
class LogSubscriber < ActiveSupport::LogSubscriber
+ # An email was delivered.
def deliver(event)
return unless logger.info?
recipients = Array(event.payload[:to]).join(', ')
@@ -7,12 +10,14 @@ module ActionMailer
debug(event.payload[:mail])
end
+ # An email was received.
def receive(event)
return unless logger.info?
info("\nReceived mail (#{event.duration.round(1)}ms)")
debug(event.payload[:mail])
end
+ # Use the logger configured for ActionMailer::Base
def logger
ActionMailer::Base.logger
end
diff --git a/actionmailer/lib/action_mailer/mail_helper.rb b/actionmailer/lib/action_mailer/mail_helper.rb
index d63d3b94a5..8d6e082d02 100644
--- a/actionmailer/lib/action_mailer/mail_helper.rb
+++ b/actionmailer/lib/action_mailer/mail_helper.rb
@@ -1,4 +1,7 @@
module ActionMailer
+ # Provides helper methods for ActionMailer::Base that can be used for easily
+ # formatting messages, accessing mailer or message instances, and the
+ # attachments list.
module MailHelper
# Take the text and format it, indented two spaces for each line, and
# wrapped at 72 columns.
diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb
index 20b6671283..6584bf5195 100644
--- a/actionmailer/test/mailers/base_mailer.rb
+++ b/actionmailer/test/mailers/base_mailer.rb
@@ -38,7 +38,7 @@ class BaseMailer < ActionMailer::Base
end
def attachment_with_hash
- attachments['invoice.jpg'] = { data: "\312\213\254\232)b",
+ attachments['invoice.jpg'] = { data: ::Base64.encode64("\312\213\254\232)b"),
mime_type: "image/x-jpg",
transfer_encoding: "base64" }
mail
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index c3fd0c18ec..3c58a2cfc3 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -515,6 +515,11 @@ module ActionDispatch
end
end
+ # Query if the following named route was already defined.
+ def has_named_route?(name)
+ @set.named_routes.routes[name.to_sym]
+ end
+
private
def app_name(app)
return unless app.respond_to?(:routes)
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 5b42ca0f21..16ba746b9c 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -2662,6 +2662,19 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_raises(ArgumentError) { routes.redirect Object.new }
end
+ def test_named_route_check
+ before, after = nil
+
+ draw do
+ before = has_named_route?(:hello)
+ get "/hello", as: :hello, to: "hello#world"
+ after = has_named_route?(:hello)
+ end
+
+ assert !before, "expected to not have named route :hello before route definition"
+ assert after, "expected to have named route :hello after route definition"
+ end
+
def test_explicitly_avoiding_the_named_route
draw do
scope :as => "routes" do
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 858c33c4e5..19d2195bca 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,4 +1,4 @@
-* Also support extentions in PostgreSQL 9.1. This feature has been supported since 9.1.
+* Also support extensions in PostgreSQL 9.1. This feature has been supported since 9.1.
*kennyj*
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 5c37f42794..6a3658f328 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -1,3 +1,15 @@
+# This is the parent Association class which defines the variables
+# used by all associations.
+#
+# The hierarchy is defined as follows:
+# Association
+# - SingularAssociation
+# - BelongsToAssociation
+# - HasOneAssociation
+# - CollectionAssociation
+# - HasManyAssociation
+# - HasAndBelongsToManyAssociation
+
module ActiveRecord::Associations::Builder
class Association #:nodoc:
class << self
@@ -58,6 +70,13 @@ module ActiveRecord::Associations::Builder
def validate_options
options.assert_valid_keys(valid_options)
end
+
+ # Defines the setter and getter methods for the association
+ # class Post < ActiveRecord::Base
+ # has_many :comments
+ # end
+ #
+ # Post.first.comments and Post.first.comments= methods are defined by this method...
def define_accessors
define_readers
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index fdead16761..9c6690b721 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -1,3 +1,5 @@
+# This class is inherited by the has_many and has_many_and_belongs_to_many association classes
+
require 'active_record/associations'
module ActiveRecord::Associations::Builder
@@ -66,6 +68,8 @@ module ActiveRecord::Associations::Builder
model.send("#{full_callback_name}=", Array(options[callback_name.to_sym]))
end
+ # Defines the setter and getter methods for the collection_singular_ids.
+
def define_readers
super
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index f06426a09d..96ccbeb8a3 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -1,3 +1,5 @@
+# This class is inherited by the has_one and belongs_to association classes
+
module ActiveRecord::Associations::Builder
class SingularAssociation < Association #:nodoc:
def valid_options
@@ -13,6 +15,8 @@ module ActiveRecord::Associations::Builder
define_constructors if constructable?
end
+ # Defines the (build|create)_association methods for belongs_to or has_one association
+
def define_constructors
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def build_#{name}(*args, &block)
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 2a00ac1386..efd7ecb97c 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -237,11 +237,11 @@ module ActiveRecord
end
end
- # Destroy +records+ and remove them from this association calling
- # +before_remove+ and +after_remove+ callbacks.
+ # Deletes the +records+ and removes them from this association calling
+ # +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
#
- # Note that this method will _always_ remove records from the database
- # ignoring the +:dependent+ option.
+ # Note that this method removes records from the database ignoring the
+ # +:dependent+ option.
def destroy(*records)
records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
delete_or_destroy(records, :destroy)
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 71b64de5ea..e82c195335 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -422,9 +422,9 @@ module ActiveRecord
@association.delete_all
end
- # Deletes the records of the collection directly from the database.
- # This will _always_ remove the records ignoring the +:dependent+
- # option.
+ # Deletes the records of the collection directly from the database
+ # ignoring the +:dependent+ option. It invokes +before_remove+,
+ # +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
#
# class Person < ActiveRecord::Base
# has_many :pets
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index f78ccb01aa..3d85898c41 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -14,7 +14,7 @@ module ActiveRecord
# Executes a custom SQL query against your database and returns all the results. The results will
# be returned as an array with columns requested encapsulated as attributes of the model you call
# this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
- # a Product object with the attributes you specified in the SQL query.
+ # a +Product+ object with the attributes you specified in the SQL query.
#
# If you call a complicated SQL query which spans multiple tables the columns specified by the
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
@@ -29,9 +29,10 @@ module ActiveRecord
# Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
# # => [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
#
- # # You can use the same string replacement techniques as you can with ActiveRecord#find
+ # You can use the same string replacement techniques as you can with <tt>ActiveRecord::QueryMethods#where</tt>:
+ #
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
- # # => [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
+ # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
def find_by_sql(sql, binds = [])
result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
column_types = {}
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 434af3c5f8..f69e9b2217 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -360,7 +360,7 @@ db_namespace = namespace :db do
end
# desc 'Check for pending migrations and load the test schema'
- task :prepare do
+ task :prepare => :load_config do
unless ActiveRecord::Base.configurations.blank?
db_namespace['test:load'].invoke
end
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index eea43aff0e..c114ea0c0d 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -99,8 +99,15 @@ module ActiveRecord
end
def merge_multi_values
- relation.where_values = merged_wheres
- relation.bind_values = merged_binds
+ lhs_wheres = relation.where_values
+ rhs_wheres = values[:where] || []
+ lhs_binds = relation.bind_values
+ rhs_binds = values[:bind] || []
+
+ removed, kept = partition_overwrites(lhs_wheres, rhs_wheres)
+
+ relation.where_values = kept + rhs_wheres
+ relation.bind_values = filter_binds(lhs_binds, removed) + rhs_binds
if values[:reordering]
# override any order specified in the original relation
@@ -123,33 +130,27 @@ module ActiveRecord
end
end
- def merged_binds
- if values[:bind]
- (relation.bind_values + values[:bind]).uniq(&:first)
- else
- relation.bind_values
- end
- end
-
- def merged_wheres
- values[:where] ||= []
+ def filter_binds(lhs_binds, removed_wheres)
+ return lhs_binds if removed_wheres.empty?
- if values[:where].empty? || relation.where_values.empty?
- relation.where_values + values[:where]
- else
- sanitized_wheres + values[:where]
- end
+ set = Set.new removed_wheres.map { |x| x.left.name }
+ lhs_binds.dup.delete_if { |col,_| set.include? col.name }
end
# Remove equalities from the existing relation with a LHS which is
# present in the relation being merged in.
- def sanitized_wheres
- seen = Set.new
- values[:where].each do |w|
- seen << w.left if w.respond_to?(:operator) && w.operator == :==
+ # returns [things_to_remove, things_to_keep]
+ def partition_overwrites(lhs_wheres, rhs_wheres)
+ if lhs_wheres.empty? || rhs_wheres.empty?
+ return [[], lhs_wheres]
+ end
+
+ nodes = rhs_wheres.find_all do |w|
+ w.respond_to?(:operator) && w.operator == :==
end
+ seen = Set.new(nodes) { |node| node.left }
- relation.where_values.reject do |w|
+ lhs_wheres.partition do |w|
w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left)
end
end
diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
index 40e134e626..7e8d68ce69 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -12,6 +12,9 @@ module ActiveRecord
class_option :parent, :type => :string, :desc => "The parent class for the generated model"
class_option :indexes, :type => :boolean, :default => true, :desc => "Add indexes for references and belongs_to columns"
+
+ # creates the migration file for the model.
+
def create_migration_file
return unless options[:migration] && options[:parent].nil?
attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false
@@ -39,6 +42,7 @@ module ActiveRecord
protected
+ # Used by the migration template to determine the parent name of the model
def parent_class_name
options[:parent] || "ActiveRecord::Base"
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 83aedfb61a..1c2d5cdaf2 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -30,7 +30,7 @@ module ActiveRecord
assert @conn.valid_type?(column.type)
end
- # sqlite databses should be able to support any type and not
+ # sqlite databases should be able to support any type and not
# just the ones mentioned in the native_database_types.
# Therefore test_invalid column should always return true
# even if the type is not valid.
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 0f2d22a4a2..c3b728296e 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -217,7 +217,7 @@ class AssociationProxyTest < ActiveRecord::TestCase
assert_equal post.body, "More cool stuff!"
end
- def test_reload_returns_assocition
+ def test_reload_returns_association
david = developers(:david)
assert_nothing_raised do
assert_equal david.projects, david.projects.reload.reload
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index d20ccaa5ca..15d536daf5 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1236,7 +1236,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_inspect_instance
topic = topics(:first)
- assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", important: nil, approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect
+ assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", important: nil, approved: false, replies_count: 1, unique_replies_count: 0, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect
end
def test_inspect_new_instance
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index ba6b0b1362..e09fa95756 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -12,7 +12,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_each_should_execute_one_query_per_batch
- assert_queries(Post.count + 1) do
+ assert_queries(@total + 1) do
Post.find_each(:batch_size => 1) do |post|
assert_kind_of Post, post
end
@@ -51,7 +51,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_find_in_batches_should_return_batches
- assert_queries(Post.count + 1) do
+ assert_queries(@total + 1) do
Post.find_in_batches(:batch_size => 1) do |batch|
assert_kind_of Array, batch
assert_kind_of Post, batch.first
@@ -60,7 +60,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_find_in_batches_should_start_from_the_start_option
- assert_queries(Post.count) do
+ assert_queries(@total) do
Post.find_in_batches(:batch_size => 1, :start => 2) do |batch|
assert_kind_of Array, batch
assert_kind_of Post, batch.first
@@ -69,14 +69,12 @@ class EachTest < ActiveRecord::TestCase
end
def test_find_in_batches_shouldnt_execute_query_unless_needed
- post_count = Post.count
-
assert_queries(2) do
- Post.find_in_batches(:batch_size => post_count) {|batch| assert_kind_of Array, batch }
+ Post.find_in_batches(:batch_size => @total) {|batch| assert_kind_of Array, batch }
end
assert_queries(1) do
- Post.find_in_batches(:batch_size => post_count + 1) {|batch| assert_kind_of Array, batch }
+ Post.find_in_batches(:batch_size => @total + 1) {|batch| assert_kind_of Array, batch }
end
end
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index ac093251a5..61f9d4cdae 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -51,6 +51,18 @@ class CounterCacheTest < ActiveRecord::TestCase
end
end
+ test 'reset multiple association counters' do
+ Topic.increment_counter(:replies_count, @topic.id)
+ assert_difference '@topic.reload.replies_count', -1 do
+ Topic.reset_counters(@topic.id, :replies, :unique_replies)
+ end
+
+ Topic.increment_counter(:unique_replies_count, @topic.id)
+ assert_difference '@topic.reload.unique_replies_count', -1 do
+ Topic.reset_counters(@topic.id, :replies, :unique_replies)
+ end
+ end
+
test "reset counters with string argument" do
Topic.increment_counter('replies_count', @topic.id)
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index a9d46f4fba..b5314bc9be 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -35,18 +35,18 @@ class ReflectionTest < ActiveRecord::TestCase
def test_read_attribute_names
assert_equal(
- %w( id title author_name author_email_address bonus_time written_on last_read content important group approved replies_count parent_id parent_title type created_at updated_at ).sort,
+ %w( id title author_name author_email_address bonus_time written_on last_read content important group approved replies_count unique_replies_count parent_id parent_title type created_at updated_at ).sort,
@first.attribute_names.sort
)
end
def test_columns
- assert_equal 17, Topic.columns.length
+ assert_equal 18, Topic.columns.length
end
def test_columns_are_returned_in_the_order_they_were_declared
column_names = Topic.columns.map { |column| column.name }
- assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important approved replies_count parent_id parent_title type group created_at updated_at), column_names
+ assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important approved replies_count unique_replies_count parent_id parent_title type group created_at updated_at), column_names
end
def test_content_columns
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index cf6af4e8f4..b64ff13d29 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1546,4 +1546,26 @@ class RelationTest < ActiveRecord::TestCase
assert merged.to_sql.include?("wtf")
assert merged.to_sql.include?("bbq")
end
+
+ def test_merging_removes_rhs_bind_parameters
+ left = Post.where(id: Arel::Nodes::BindParam.new('?'))
+ column = Post.columns_hash['id']
+ left.bind_values += [[column, 20]]
+ right = Post.where(id: 10)
+
+ merged = left.merge(right)
+ assert_equal [], merged.bind_values
+ end
+
+ def test_merging_keeps_lhs_bind_parameters
+ column = Post.columns_hash['id']
+ binds = [[column, 20]]
+
+ right = Post.where(id: Arel::Nodes::BindParam.new('?'))
+ right.bind_values += binds
+ left = Post.where(id: 10)
+
+ merged = left.merge(right)
+ assert_equal binds, merged.bind_values
+ end
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 57457359b1..2b33f01783 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -268,7 +268,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
end
def test_validate_case_sensitive_uniqueness_with_attribute_passed_as_integer
- Topic.validates_uniqueness_of(:title, :case_sensitve => true)
+ Topic.validates_uniqueness_of(:title, :case_sensitive => true)
Topic.create!('title' => 101)
t2 = Topic.new('title' => 101)
diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb
index c88262580e..3e82e55d89 100644
--- a/activerecord/test/models/reply.rb
+++ b/activerecord/test/models/reply.rb
@@ -7,6 +7,7 @@ class Reply < Topic
end
class UniqueReply < Reply
+ belongs_to :topic, :foreign_key => 'parent_id', :counter_cache => true
validates_uniqueness_of :content, :scope => 'parent_id'
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 8beb58f3fc..188a3f0164 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -675,6 +675,7 @@ ActiveRecord::Schema.define do
end
t.boolean :approved, :default => true
t.integer :replies_count, :default => 0
+ t.integer :unique_replies_count, :default => 0
t.integer :parent_id
t.string :parent_title
t.string :type
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index bbfa74f98d..85b7669353 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -537,7 +537,7 @@ module ActiveSupport
module ClassMethods
- def normalize_callback_params(name, filters, block) # :nodoc:
+ def normalize_callback_params(filters, block) # :nodoc:
type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before
options = filters.last.is_a?(Hash) ? filters.pop : {}
filters.unshift(block) if block
@@ -589,7 +589,7 @@ module ActiveSupport
# * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
# existing chain rather than appended.
def set_callback(name, *filter_list, &block)
- type, filters, options = normalize_callback_params(name, filter_list, block)
+ type, filters, options = normalize_callback_params(filter_list, block)
self_chain = get_callbacks name
mapped = filters.map do |filter|
Callback.build(self_chain, filter, type, options)
@@ -609,7 +609,7 @@ module ActiveSupport
# skip_callback :validate, :before, :check_membership, if: -> { self.age > 18 }
# end
def skip_callback(name, *filter_list, &block)
- type, filters, options = normalize_callback_params(name, filter_list, block)
+ type, filters, options = normalize_callback_params(filter_list, block)
__update_callbacks(name) do |target, chain|
filters.each do |filter|
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 19b214f114..c4d69908ed 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -1202,6 +1202,7 @@ class User < ActiveRecord::Base
scope :active, -> { where state: 'active' }
scope :inactive, -> { where state: 'inactive' }
end
+```
```ruby
User.active.inactive
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index dfc951f10e..621d2222ff 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -162,8 +162,8 @@ Person.create(name: nil).valid? # => false
```
After Active Record has performed validations, any errors found can be accessed
-through the `errors` instance method, which returns a collection of errors. By
-definition, an object is valid if this collection is empty after running
+through the `errors.messages` instance method, which returns a collection of errors.
+By definition, an object is valid if this collection is empty after running
validations.
Note that an object instantiated with `new` will not report errors even if it's
@@ -176,17 +176,17 @@ end
>> p = Person.new
#=> #<Person id: nil, name: nil>
->> p.errors
+>> p.errors.messages
#=> {}
>> p.valid?
#=> false
->> p.errors
+>> p.errors.messages
#=> {name:["can't be blank"]}
>> p = Person.create
#=> #<Person id: nil, name: nil>
->> p.errors
+>> p.errors.messages
#=> {name:["can't be blank"]}
>> p.save
@@ -993,12 +993,12 @@ end
person = Person.new
person.valid? # => false
-person.errors
+person.errors.messages
# => {:name=>["can't be blank", "is too short (minimum is 3 characters)"]}
person = Person.new(name: "John Doe")
person.valid? # => true
-person.errors # => []
+person.errors.messages # => {}
```
### `errors[]`
diff --git a/guides/source/migrations.md b/guides/source/migrations.md
index fcfc54a3d7..550f8fdc3c 100644
--- a/guides/source/migrations.md
+++ b/guides/source/migrations.md
@@ -852,7 +852,7 @@ end
# app/models/product.rb
class Product < ActiveRecord::Base
- validates :flag, :inclusion => { :in => [true, false] }
+ validates :flag, inclusion: { in: [true, false] }
end
```
@@ -877,7 +877,7 @@ end
# app/models/product.rb
class Product < ActiveRecord::Base
- validates :flag, :inclusion => { :in => [true, false] }
+ validates :flag, inclusion: { in: [true, false] }
validates :fuzz, presence: true
end
```
@@ -1065,8 +1065,8 @@ with foreign key constraints in the database.
Although Active Record does not provide any tools for working directly with
such features, the `execute` method can be used to execute arbitrary SQL. You
-could also use some gem like
-[foreigner](https://github.com/matthuhiggins/foreigner) which add foreign key
+can also use a gem like
+[foreigner](https://github.com/matthuhiggins/foreigner) which adds foreign key
support to Active Record (including support for dumping foreign keys in
`db/schema.rb`).
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 1d14656f79..6c3e763f53 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -22,6 +22,59 @@ Rails generally stays close to the latest released Ruby version when it's releas
TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump straight to 1.9.3 for smooth sailing.
+### HTTP PATCH
+
+Rails 4 now uses `PATCH` as the primary HTTP verb for updates. When a resource
+is declared in `config/routes.rb`:
+
+```ruby
+resources :users
+```
+
+the action in `UsersController` to update a user is still `update`.
+
+`PUT` requests to `/users/:id` in Rails 4 get routed to `update` as they are
+today. So, if you have an API that gets real PUT requests it is going to work.
+The router also routes `PATCH` requests to `/users/:id` to the `update` action.
+
+So, in Rails 4 both `PUT` and `PATCH` are routed to update. We recommend
+switching to `PATCH` as part of your upgrade process if possible, as it's more
+likely what you want.
+
+For more on PATCH and why this change was made, see [this post](http://weblog.rubyonrails.org/2012/2/25/edge-rails-patch-is-the-new-primary-http-method-for-updates/)
+on the Rails blog.
+
+#### A note about media types
+
+The errata for the `PATCH` verb [specifies that a 'diff' media type should be
+used with `PATCH`](http://www.rfc-editor.org/errata_search.php?rfc=5789). One
+such format is [JSON Patch](http://tools.ietf.org/html/rfc6902). While Rails
+does not support JSON Patch natively, it's easy enough to add support:
+
+```
+# in your controller
+def update
+ respond_to do |format|
+ format.json do
+ # perform a partial update
+ @post.update params[:post]
+ end
+
+ format.json_patch do
+ # perform sophisticated change
+ end
+ end
+end
+
+# In config/initializers/json_patch.rb:
+Mime::Type.register 'application/json-patch+json', :json_patch
+```
+
+As JSON Patch was only recently made into an RFC, there aren't a lot of great
+Ruby libraries yet. Aaron Patterson's
+[hana](https://github.com/tenderlove/hana) is one such gem, but doesn't have
+full support for the last few changes in the specification.
+
Upgrading from Rails 3.2 to Rails 4.0
-------------------------------------
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 04cdbec428..d11b0b7e85 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -4,7 +4,7 @@
*Arun Agrawal*
* Rails::Railtie no longer forces the Rails::Configurable module on everything
- that subclassess it. Instead, the methods from Rails::Configurable have been
+ that subclasses it. Instead, the methods from Rails::Configurable have been
moved to class methods in Railtie and the Railtie has been made abstract.
*John Wang*
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index d891ba1215..e712c747b0 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -18,6 +18,8 @@ module Rails
parse_attributes! if respond_to?(:attributes)
end
+ # Defines the template that would be used for the migration file.
+ # The arguments include the source template file, the migration filename etc.
no_tasks do
def template(source, *args, &block)
inside_template do
diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb
index 9807000578..142af2d792 100644
--- a/railties/lib/rails/tasks.rb
+++ b/railties/lib/rails/tasks.rb
@@ -1,6 +1,6 @@
$VERBOSE = nil
-# Load Rails rakefile extensions
+# Load Rails Rakefile extensions
%w(
annotations
documentation