aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--actioncable/lib/action_cable/channel/base.rb2
-rw-r--r--actioncable/lib/action_cable/connection/client_socket.rb7
-rw-r--r--actioncable/lib/action_cable/connection/subscriptions.rb2
-rw-r--r--actioncable/test/client/echo_channel.rb4
-rw-r--r--actioncable/test/client_test.rb36
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb26
-rw-r--r--actionpack/test/controller/parameters/accessors_test.rb45
-rw-r--r--actionpack/test/controller/render_test.rb2
-rw-r--r--actionview/CHANGELOG.md4
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb40
-rw-r--r--actionview/test/template/url_helper_test.rb46
-rw-r--r--activemodel/lib/active_model/validator.rb7
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb2
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb3
-rw-r--r--activerecord/lib/active_record/validations/absence.rb2
-rw-r--r--activerecord/lib/active_record/validations/length.rb2
-rw-r--r--activerecord/lib/active_record/validations/presence.rb2
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb13
-rw-r--r--activerecord/test/cases/validations/absence_validation_test.rb14
-rw-r--r--activerecord/test/cases/validations/length_validation_test.rb16
-rw-r--r--activerecord/test/cases/validations/presence_validation_test.rb15
-rw-r--r--activerecord/test/schema/schema.rb2
-rw-r--r--activesupport/CHANGELOG.md4
-rw-r--r--activesupport/lib/active_support/benchmarkable.rb2
-rw-r--r--activesupport/lib/active_support/dependencies.rb6
-rw-r--r--activesupport/lib/active_support/test_case.rb7
-rw-r--r--activesupport/test/benchmarkable_test.rb14
-rw-r--r--activesupport/test/dependencies_test.rb13
-rw-r--r--guides/source/api_app.md37
32 files changed, 303 insertions, 78 deletions
diff --git a/README.md b/README.md
index 0c58685573..fb9e683bc3 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ or to generate the body of an email. In Rails, View generation is handled by Act
You can read more about Action View in its [README](actionview/README.rdoc).
Active Record, Active Model, Action Pack, and Action View can each be used independently outside Rails.
-In addition to them, Rails also comes with Action Mailer ([README](actionmailer/README.rdoc)), a library
+In addition to that, Rails also comes with Action Mailer ([README](actionmailer/README.rdoc)), a library
to generate and send emails; Active Job ([README](activejob/README.md)), a
framework for declaring jobs and making them run on a variety of queueing
backends; Action Cable ([README](actioncable/README.md)), a framework to
diff --git a/actioncable/lib/action_cable/channel/base.rb b/actioncable/lib/action_cable/channel/base.rb
index 874ebe2e71..86c8f12f6e 100644
--- a/actioncable/lib/action_cable/channel/base.rb
+++ b/actioncable/lib/action_cable/channel/base.rb
@@ -191,7 +191,7 @@ module ActionCable
# Transmit a hash of data to the subscriber. The hash will automatically be wrapped in a JSON envelope with
# the proper channel identifier marked as the recipient.
def transmit(data, via: nil)
- logger.info "#{self.class.name} transmitting #{data.inspect}".tap { |m| m << " (via #{via})" if via }
+ logger.info "#{self.class.name} transmitting #{data.inspect.truncate(300)}".tap { |m| m << " (via #{via})" if via }
connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, message: data)
end
diff --git a/actioncable/lib/action_cable/connection/client_socket.rb b/actioncable/lib/action_cable/connection/client_socket.rb
index ef937d7c16..95e1ac4c16 100644
--- a/actioncable/lib/action_cable/connection/client_socket.rb
+++ b/actioncable/lib/action_cable/connection/client_socket.rb
@@ -132,11 +132,8 @@ module ActionCable
@ready_state = CLOSING
@close_params = [reason, code]
- if @stream
- @stream.shutdown
- else
- finalize_close
- end
+ @stream.shutdown if @stream
+ finalize_close
end
def finalize_close
diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb
index d7f95e6a62..24934e12f2 100644
--- a/actioncable/lib/action_cable/connection/subscriptions.rb
+++ b/actioncable/lib/action_cable/connection/subscriptions.rb
@@ -54,7 +54,7 @@ module ActionCable
end
def unsubscribe_from_all
- subscriptions.each { |id, channel| channel.unsubscribe_from_channel }
+ subscriptions.each { |id, channel| remove_subscription(channel) }
end
protected
diff --git a/actioncable/test/client/echo_channel.rb b/actioncable/test/client/echo_channel.rb
index 63e35f194a..5a7bac25c5 100644
--- a/actioncable/test/client/echo_channel.rb
+++ b/actioncable/test/client/echo_channel.rb
@@ -3,6 +3,10 @@ class EchoChannel < ActionCable::Channel::Base
stream_from "global"
end
+ def unsubscribed
+ 'Goodbye from EchoChannel!'
+ end
+
def ding(data)
transmit(dong: data['message'])
end
diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb
index d30c381131..1b07689127 100644
--- a/actioncable/test/client_test.rb
+++ b/actioncable/test/client_test.rb
@@ -54,7 +54,7 @@ class ClientTest < ActionCable::TestCase
@ws = Faye::WebSocket::Client.new("ws://127.0.0.1:#{port}/")
@messages = Queue.new
@closed = Concurrent::Event.new
- @has_messages = Concurrent::Event.new
+ @has_messages = Concurrent::Semaphore.new(0)
@pings = 0
open = Concurrent::Event.new
@@ -79,7 +79,7 @@ class ClientTest < ActionCable::TestCase
@pings += 1
else
@messages << hash
- @has_messages.set
+ @has_messages.release
end
end
@@ -92,8 +92,7 @@ class ClientTest < ActionCable::TestCase
end
def read_message
- @has_messages.wait(WAIT_WHEN_EXPECTING_EVENT) if @messages.empty?
- @has_messages.reset if @messages.size < 2
+ @has_messages.try_acquire(1, WAIT_WHEN_EXPECTING_EVENT)
msg = @messages.pop(true)
raise msg if msg.is_a?(Exception)
@@ -104,9 +103,11 @@ class ClientTest < ActionCable::TestCase
def read_messages(expected_size = 0)
list = []
loop do
- @has_messages.wait(list.size < expected_size ? WAIT_WHEN_EXPECTING_EVENT : WAIT_WHEN_NOT_EXPECTING_EVENT)
- if @has_messages.set?
- list << read_message
+ if @has_messages.try_acquire(1, list.size < expected_size ? WAIT_WHEN_EXPECTING_EVENT : WAIT_WHEN_NOT_EXPECTING_EVENT)
+ msg = @messages.pop(true)
+ raise msg if msg.is_a?(Exception)
+
+ list << msg
else
break
end
@@ -198,4 +199,25 @@ class ClientTest < ActionCable::TestCase
c.close # disappear before read
end
end
+
+ def test_unsubscribe_client
+ with_puma_server do |port|
+ app = ActionCable.server
+ identifier = JSON.dump(channel: 'EchoChannel')
+
+ c = faye_client(port)
+ c.send_message command: 'subscribe', identifier: identifier
+ assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message)
+ assert_equal(1, app.connections.count)
+ assert(app.remote_connections.where(identifier: identifier))
+
+ channel = app.connections.first.subscriptions.send(:subscriptions).first[1]
+ channel.expects(:unsubscribed)
+ c.close
+ sleep 0.1 # Data takes a moment to process
+
+ # All data is removed: No more connection or subscription information!
+ assert_equal(0, app.connections.count)
+ end
+ end
end
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index e17189f9f9..25ec3cf5b6 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -144,17 +144,21 @@ module ActionController
end
# Returns true if another +Parameters+ object contains the same content and
- # permitted flag, or other Hash-like object contains the same content. This
- # override is in place so you can perform a comparison with `Hash`.
- def ==(other_hash)
- if other_hash.respond_to?(:permitted?)
- super
+ # permitted flag.
+ def ==(other)
+ if other.respond_to?(:permitted?)
+ self.permitted? == other.permitted? && self.parameters == other.parameters
+ elsif other.is_a?(Hash)
+ ActiveSupport::Deprecation.warn <<-WARNING.squish
+ Comparing equality between `ActionController::Parameters` and a
+ `Hash` is deprecated and will be removed in Rails 5.1. Please only do
+ comparisons between instances of `ActionController::Parameters`. If
+ you need to compare to a hash, first convert it using
+ `ActionController::Parameters#new`.
+ WARNING
+ @parameters == other.with_indifferent_access
else
- if other_hash.is_a?(Hash)
- @parameters == other_hash.with_indifferent_access
- else
- @parameters == other_hash
- end
+ @parameters == other
end
end
@@ -597,6 +601,8 @@ module ActionController
end
protected
+ attr_reader :parameters
+
def permitted=(new_permitted)
@permitted = new_permitted
end
diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb
index 08b3d81bf0..4ef5bed30d 100644
--- a/actionpack/test/controller/parameters/accessors_test.rb
+++ b/actionpack/test/controller/parameters/accessors_test.rb
@@ -129,10 +129,51 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
assert_not @params[:person].values_at(:name).first.permitted?
end
- test "equality with another hash works" do
+ test "equality with a hash is deprecated" do
hash1 = { foo: :bar }
params1 = ActionController::Parameters.new(hash1)
- assert(params1 == hash1)
+ assert_deprecated("will be removed in Rails 5.1") do
+ assert(params1 == hash1)
+ end
+ end
+
+ test "is equal to Parameters instance with same params" do
+ params1 = ActionController::Parameters.new(a: 1, b: 2)
+ params2 = ActionController::Parameters.new(a: 1, b: 2)
+ assert(params1 == params2)
+ end
+
+ test "is equal to Parameters instance with same permitted params" do
+ params1 = ActionController::Parameters.new(a: 1, b: 2).permit(:a)
+ params2 = ActionController::Parameters.new(a: 1, b: 2).permit(:a)
+ assert(params1 == params2)
+ end
+
+ test "is equal to Parameters instance with same different source params, but same permitted params" do
+ params1 = ActionController::Parameters.new(a: 1, b: 2).permit(:a)
+ params2 = ActionController::Parameters.new(a: 1, c: 3).permit(:a)
+ assert(params1 == params2)
+ assert(params2 == params1)
+ end
+
+ test 'is not equal to an unpermitted Parameters instance with same params' do
+ params1 = ActionController::Parameters.new(a: 1).permit(:a)
+ params2 = ActionController::Parameters.new(a: 1)
+ assert(params1 != params2)
+ assert(params2 != params1)
+ end
+
+ test "is not equal to Parameters instance with different permitted params" do
+ params1 = ActionController::Parameters.new(a: 1, b: 2).permit(:a, :b)
+ params2 = ActionController::Parameters.new(a: 1, b: 2).permit(:a)
+ assert(params1 != params2)
+ assert(params2 != params1)
+ end
+
+ test "equality with simple types works" do
+ assert(@params != 'Hello')
+ assert(@params != 42)
+ assert(@params != false)
end
test "inspect shows both class name and parameters" do
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index c814d4ea54..60c6518c62 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -509,7 +509,7 @@ class EtagRenderTest < ActionController::TestCase
begin
File.write path, 'foo'
- ActionView::Digestor.cache.clear
+ ActionView::LookupContext::DetailsKey.clear
request.if_none_match = etag
get :with_template
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index bebe78c360..461a26b10b 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,7 @@
+* Fix `button_to`'s `:params` option support to correctly generated input names for nested hashes/arrays.
+
+ *James Coleman*
+
## Rails 5.0.0.beta2 (February 01, 2016) ##
* Fix stripping the digest from the automatically generated img tag alt
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 3a4561a083..87218821ed 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -329,8 +329,8 @@ module ActionView
inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
if params
- params.each do |param_name, value|
- inner_tags.safe_concat tag(:input, type: "hidden", name: param_name, value: value.to_param)
+ to_form_params(params).each do |param|
+ inner_tags.safe_concat tag(:input, type: "hidden", name: param[:name], value: param[:value])
end
end
content_tag('form', inner_tags, form_options)
@@ -595,6 +595,42 @@ module ActionView
def method_tag(method)
tag('input', type: 'hidden', name: '_method', value: method.to_s)
end
+
+ # Returns an array of hashes each containing :name and :value keys
+ # suitable for use as the names and values of form input fields:
+ #
+ # to_form_params(name: 'David', nationality: 'Danish')
+ # # => [{name: :name, value: 'David'}, {name: 'nationality', value: 'Danish'}]
+ #
+ # to_form_params(country: {name: 'Denmark'})
+ # # => [{name: 'country[name]', value: 'Denmark'}]
+ #
+ # to_form_params(countries: ['Denmark', 'Sweden']})
+ # # => [{name: 'countries[]', value: 'Denmark'}, {name: 'countries[]', value: 'Sweden'}]
+ #
+ # An optional namespace can be passed to enclose key names:
+ #
+ # to_form_params({ name: 'Denmark' }, 'country')
+ # # => [{name: 'country[name]', value: 'Denmark'}]
+ def to_form_params(attribute, namespace = nil) # :nodoc:
+ params = []
+ case attribute
+ when Hash
+ attribute.each do |key, value|
+ prefix = namespace ? "#{namespace}[#{key}]" : key
+ params.push(*to_form_params(value, prefix))
+ end
+ when Array
+ array_prefix = "#{namespace}[]"
+ attribute.each do |value|
+ params.push(*to_form_params(value, array_prefix))
+ end
+ else
+ params << { name: namespace, value: attribute.to_param }
+ end
+
+ params.sort_by { |pair| pair[:name] }
+ end
end
end
end
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 3010656166..d6a19a829f 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -71,6 +71,34 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_equal 'javascript:history.back()', url_for(:back)
end
+ def test_to_form_params_with_hash
+ assert_equal(
+ [{ name: :name, value: 'David' }, { name: :nationality, value: 'Danish' }],
+ to_form_params(name: 'David', nationality: 'Danish')
+ )
+ end
+
+ def test_to_form_params_with_nested_hash
+ assert_equal(
+ [{ name: 'country[name]', value: 'Denmark' }],
+ to_form_params(country: { name: 'Denmark' })
+ )
+ end
+
+ def test_to_form_params_with_array_nested_in_hash
+ assert_equal(
+ [{ name: 'countries[]', value: 'Denmark' }, { name: 'countries[]', value: 'Sweden' }],
+ to_form_params(countries: ['Denmark', 'Sweden'])
+ )
+ end
+
+ def test_to_form_params_with_namespace
+ assert_equal(
+ [{ name: 'country[name]', value: 'Denmark' }],
+ to_form_params({name: 'Denmark'}, 'country')
+ )
+ end
+
def test_button_to_with_straight_url
assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com")
end
@@ -189,8 +217,22 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_button_to_with_params
assert_dom_equal(
- %{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="foo" value="bar" /><input type="hidden" name="baz" value="quux" /></form>},
- button_to("Hello", "http://www.example.com", params: {foo: :bar, baz: "quux"})
+ %{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="baz" value="quux" /><input type="hidden" name="foo" value="bar" /></form>},
+ button_to("Hello", "http://www.example.com", params: { foo: :bar, baz: "quux" })
+ )
+ end
+
+ def test_button_to_with_nested_hash_params
+ assert_dom_equal(
+ %{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="foo[bar]" value="baz" /></form>},
+ button_to("Hello", "http://www.example.com", params: { foo: { bar: 'baz' } })
+ )
+ end
+
+ def test_button_to_with_nested_array_params
+ assert_dom_equal(
+ %{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="foo[]" value="bar" /></form>},
+ button_to("Hello", "http://www.example.com", params: { foo: ['bar'] })
)
end
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 1d2888a818..21339b628e 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -167,6 +167,13 @@ module ActiveModel
def should_validate?(record) # :nodoc:
!record.persisted? || record.changed? || record.marked_for_destruction?
end
+
+ # Always validate the record if the attribute is a virtual attribute.
+ # We have no way of knowing that the record was changed if the attribute
+ # does not exist in the database.
+ def unknown_attribute?(record, attribute) # :nodoc:
+ !record.attributes.include?(attribute.to_s)
+ end
end
# +BlockValidator+ is a special +EachValidator+ which receives a block on initialization
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index b888148841..5fbd79d118 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -76,6 +76,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
left_model.retrieve_connection
end
+ def self.primary_key
+ false
+ end
}
join_model.name = "HABTM_#{association_name.to_s.camelize}"
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 91ec921434..5ef434734a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -1,5 +1,4 @@
require 'active_record/type'
-require 'active_support/core_ext/benchmark'
require 'active_record/connection_adapters/determine_if_preparable_visitor'
require 'active_record/connection_adapters/schema_cache'
require 'active_record/connection_adapters/sql_type_metadata'
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index 6aa264d766..6f2e03b370 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -118,7 +118,7 @@ module ActiveRecord
alias :exec_update :exec_delete
def sql_for_insert(sql, pk, id_value, sequence_name, binds) # :nodoc:
- unless pk
+ if pk.nil?
# Extract the table from the insert sql. Yuck.
table_ref = extract_table_ref_from_insert_sql(sql)
pk = primary_key(table_ref) if table_ref
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index e4e5d63006..2b1ac42395 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -37,7 +37,8 @@ module ActiveRecord
# for each different klass, and the delegations are compiled into that subclass only.
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
- :[], :&, :|, :+, :-, :sample, :shuffle, :reverse, :compact, to: :to_a
+ :[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
+ :shuffle, :split, to: :to_a
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
:connection, :columns_hash, :to => :klass
diff --git a/activerecord/lib/active_record/validations/absence.rb b/activerecord/lib/active_record/validations/absence.rb
index 2e19e6dc5c..376d743c92 100644
--- a/activerecord/lib/active_record/validations/absence.rb
+++ b/activerecord/lib/active_record/validations/absence.rb
@@ -2,7 +2,7 @@ module ActiveRecord
module Validations
class AbsenceValidator < ActiveModel::Validations::AbsenceValidator # :nodoc:
def validate_each(record, attribute, association_or_value)
- return unless should_validate?(record)
+ return unless should_validate?(record) || unknown_attribute?(record, attribute)
if record.class._reflect_on_association(attribute)
association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?)
end
diff --git a/activerecord/lib/active_record/validations/length.rb b/activerecord/lib/active_record/validations/length.rb
index 69e048eef1..fe34e4875c 100644
--- a/activerecord/lib/active_record/validations/length.rb
+++ b/activerecord/lib/active_record/validations/length.rb
@@ -2,7 +2,7 @@ module ActiveRecord
module Validations
class LengthValidator < ActiveModel::Validations::LengthValidator # :nodoc:
def validate_each(record, attribute, association_or_value)
- return unless should_validate?(record) || associations_are_dirty?(record)
+ return unless should_validate?(record) || unknown_attribute?(record, attribute) || associations_are_dirty?(record)
if association_or_value.respond_to?(:loaded?) && association_or_value.loaded?
association_or_value = association_or_value.target.reject(&:marked_for_destruction?)
end
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index 7e85ed43ac..e34d2d70ab 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -2,7 +2,7 @@ module ActiveRecord
module Validations
class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc:
def validate_each(record, attribute, association_or_value)
- return unless should_validate?(record)
+ return unless should_validate?(record) || unknown_attribute?(record, attribute)
if record.class._reflect_on_association(attribute)
association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?)
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 5c4586da19..9096cbc0ab 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -146,6 +146,19 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, country.treaties.count
end
+ def test_join_table_composite_primary_key_should_not_warn
+ country = Country.new(:name => 'India')
+ country.country_id = 'c1'
+ country.save!
+
+ treaty = Treaty.new(:name => 'peace')
+ treaty.treaty_id = 't1'
+ warning = capture(:stderr) do
+ country.treaties << treaty
+ end
+ assert_no_match(/WARNING: Rails does not support composite primary key\./, warning)
+ end
+
def test_has_and_belongs_to_many
david = Developer.find(1)
diff --git a/activerecord/test/cases/validations/absence_validation_test.rb b/activerecord/test/cases/validations/absence_validation_test.rb
index dd43ee358c..180acbcb6a 100644
--- a/activerecord/test/cases/validations/absence_validation_test.rb
+++ b/activerecord/test/cases/validations/absence_validation_test.rb
@@ -72,4 +72,18 @@ class AbsenceValidationTest < ActiveRecord::TestCase
assert man.valid?
end
end
+
+ def test_validates_absence_of_virtual_attribute_on_model
+ repair_validations(Interest) do
+ Interest.send(:attr_accessor, :token)
+ Interest.validates_absence_of(:token)
+
+ interest = Interest.create!(topic: 'Thought Leadering')
+ assert interest.valid?
+
+ interest.token = 'tl'
+
+ assert interest.invalid?
+ end
+ end
end
diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb
index c5d8f8895c..624aeaaddb 100644
--- a/activerecord/test/cases/validations/length_validation_test.rb
+++ b/activerecord/test/cases/validations/length_validation_test.rb
@@ -74,4 +74,20 @@ class LengthValidationTest < ActiveRecord::TestCase
assert owner.valid?
assert pet.valid?
end
+
+ def test_validates_presence_of_virtual_attribute_on_model
+ repair_validations(Pet) do
+ Pet.send(:attr_accessor, :nickname)
+ Pet.validates_length_of(:name, minimum: 1)
+ Pet.validates_length_of(:nickname, minimum: 1)
+
+ pet = Pet.create!(name: 'Fancy Pants', nickname: 'Fancy')
+
+ assert pet.valid?
+
+ pet.nickname = ''
+
+ assert pet.invalid?
+ end
+ end
end
diff --git a/activerecord/test/cases/validations/presence_validation_test.rb b/activerecord/test/cases/validations/presence_validation_test.rb
index 6f8ad06ab6..691f10a635 100644
--- a/activerecord/test/cases/validations/presence_validation_test.rb
+++ b/activerecord/test/cases/validations/presence_validation_test.rb
@@ -80,4 +80,19 @@ class PresenceValidationTest < ActiveRecord::TestCase
assert man.valid?
end
end
+
+ def test_validates_presence_of_virtual_attribute_on_model
+ repair_validations(Interest) do
+ Interest.send(:attr_accessor, :abbreviation)
+ Interest.validates_presence_of(:topic)
+ Interest.validates_presence_of(:abbreviation)
+
+ interest = Interest.create!(topic: 'Thought Leadering', abbreviation: 'tl')
+ assert interest.valid?
+
+ interest.abbreviation = ''
+
+ assert interest.invalid?
+ end
+ end
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index b9e0706d60..2a8996f35c 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -929,7 +929,7 @@ ActiveRecord::Schema.define do
t.string :treaty_id
t.string :name
end
- create_table :countries_treaties, force: true, id: false do |t|
+ create_table :countries_treaties, force: true, primary_key: [:country_id, :treaty_id] do |t|
t.string :country_id, null: false
t.string :treaty_id, null: false
end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index f4c324803c..db8d279cff 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,7 @@
+* Make `benchmark('something', silence: true)` actually work
+
+ *DHH*
+
* Add `#on_weekday?` method to `Date`, `Time`, and `DateTime`.
`#on_weekday?` returns `true` if the receiving date/time does not fall on a Saturday
diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb
index 805b7a714f..3988b147ac 100644
--- a/activesupport/lib/active_support/benchmarkable.rb
+++ b/activesupport/lib/active_support/benchmarkable.rb
@@ -38,7 +38,7 @@ module ActiveSupport
options[:level] ||= :info
result = nil
- ms = Benchmark.ms { result = options[:silence] ? silence { yield } : yield }
+ ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield }
logger.send(options[:level], '%s (%.1fms)' % [ message, ms ])
result
else
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index af18ff746f..fd9fbff96a 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -145,9 +145,9 @@ module ActiveSupport #:nodoc:
# Get a list of the constants that were added
new_constants = mod.local_constants - original_constants
- # self[namespace] returns an Array of the constants that are being evaluated
+ # @stack[namespace] returns an Array of the constants that are being evaluated
# for that namespace. For instance, if parent.rb requires child.rb, the first
- # element of self[Object] will be an Array of the constants that were present
+ # element of @stack[Object] will be an Array of the constants that were present
# before parent.rb was required. The second element will be an Array of the
# constants that were present before child.rb was required.
@stack[namespace].each do |namespace_constants|
@@ -262,7 +262,7 @@ module ActiveSupport #:nodoc:
end
def load_dependency(file)
- if Dependencies.load? && ActiveSupport::Dependencies.constant_watch_stack.watching?
+ if Dependencies.load? && Dependencies.constant_watch_stack.watching?
Dependencies.new_constants_in(Object) { yield }
else
yield
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index d9a668c0ea..4dc84e4a59 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -66,10 +66,13 @@ module ActiveSupport
alias :assert_not_respond_to :refute_respond_to
alias :assert_not_same :refute_same
- # Reveals the intention that the block should not raise any exception.
+
+ # Assertion that the block should not raise an exception.
+ #
+ # Passes if evaluated code in the yielded block raises no exception.
#
# assert_nothing_raised do
- # ...
+ # perform_service(param: 'no_exception')
# end
def assert_nothing_raised(*args)
yield
diff --git a/activesupport/test/benchmarkable_test.rb b/activesupport/test/benchmarkable_test.rb
index 04d4f5e503..5af041f458 100644
--- a/activesupport/test/benchmarkable_test.rb
+++ b/activesupport/test/benchmarkable_test.rb
@@ -41,6 +41,20 @@ class BenchmarkableTest < ActiveSupport::TestCase
assert_last_logged 'test_run'
end
+ def test_with_silence
+ assert_difference 'buffer.count', +2 do
+ benchmark('test_run') do
+ logger.info "SOMETHING"
+ end
+ end
+
+ assert_difference 'buffer.count', +1 do
+ benchmark('test_run', silence: true) do
+ logger.info "NOTHING"
+ end
+ end
+ end
+
def test_within_level
logger.level = ActiveSupport::Logger::DEBUG
benchmark('included_debug_run', :level => :debug) { }
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 757e600646..b7a5747f1b 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -76,6 +76,7 @@ class DependenciesTest < ActiveSupport::TestCase
def test_dependency_which_raises_exception_isnt_added_to_loaded_set
with_loading do
filename = 'dependencies/raises_exception'
+ expanded = File.expand_path(filename)
$raises_exception_load_count = 0
5.times do |count|
@@ -86,8 +87,8 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal 'Loading me failed, so do not add to loaded or history.', e.message
assert_equal count + 1, $raises_exception_load_count
- assert_not ActiveSupport::Dependencies.loaded.include?(filename)
- assert_not ActiveSupport::Dependencies.history.include?(filename)
+ assert_not ActiveSupport::Dependencies.loaded.include?(expanded)
+ assert_not ActiveSupport::Dependencies.history.include?(expanded)
end
end
end
@@ -1047,12 +1048,4 @@ class DependenciesTest < ActiveSupport::TestCase
ensure
ActiveSupport::Dependencies.hook!
end
-
- def test_unhook
- ActiveSupport::Dependencies.unhook!
- assert !Module.new.respond_to?(:const_missing_without_dependencies)
- assert !Module.new.respond_to?(:load_without_new_constant_marking)
- ensure
- ActiveSupport::Dependencies.hook!
- end
end
diff --git a/guides/source/api_app.md b/guides/source/api_app.md
index 563214896a..0598b9c7fa 100644
--- a/guides/source/api_app.md
+++ b/guides/source/api_app.md
@@ -13,8 +13,8 @@ In this guide you will learn:
--------------------------------------------------------------------------------
-What is an API app?
--------------------
+What is an API Application?
+---------------------------
Traditionally, when people said that they used Rails as an "API", they meant
providing a programmatically accessible API alongside their web application.
@@ -28,15 +28,14 @@ applications.
For example, Twitter uses its [public API](https://dev.twitter.com) in its web
application, which is built as a static site that consumes JSON resources.
-Instead of using Rails to generate dynamic HTML that will communicate with the
-server through forms and links, many developers are treating their web application
-as just another client, delivered as static HTML, CSS and JavaScript consuming
-a simple JSON API.
+Instead of using Rails to generate HTML that communicates with the server
+through forms and links, many developers are treating their web application as
+just an API client delivered as HTML with JavaScript that consumes a JSON API.
This guide covers building a Rails application that serves JSON resources to an
-API client **or** a client-side framework.
+API client, including client-side frameworks.
-Why use Rails for JSON APIs?
+Why Use Rails for JSON APIs?
----------------------------
The first question a lot of people have when thinking about building a JSON API
@@ -75,7 +74,7 @@ Handled at the middleware layer:
URL-encoded String? No problem. Rails will decode the JSON for you and make
it available in `params`. Want to use nested URL-encoded parameters? That
works too.
-- Conditional GETs: Rails handles conditional `GET`, (`ETag` and `Last-Modified`),
+- Conditional GETs: Rails handles conditional `GET` (`ETag` and `Last-Modified`)
processing request headers and returning the correct response headers and status
code. All you need to do is use the
[`stale?`](http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-stale-3F)
@@ -104,21 +103,21 @@ Handled at the Action Pack layer:
add the response headers, but why?
- Caching: Rails provides page, action and fragment caching. Fragment caching
is especially helpful when building up a nested JSON object.
-- Basic, Digest and Token Authentication: Rails comes with out-of-the-box support
+- Basic, Digest, and Token Authentication: Rails comes with out-of-the-box support
for three kinds of HTTP authentication.
-- Instrumentation: Rails has an instrumentation API that will trigger registered
+- Instrumentation: Rails has an instrumentation API that triggers registered
handlers for a variety of events, such as action processing, sending a file or
data, redirection, and database queries. The payload of each event comes with
relevant information (for the action processing event, the payload includes
the controller, action, parameters, request format, request method and the
request's full path).
-- Generators: This may be passé for advanced Rails users, but it can be nice to
- generate a resource and get your model, controller, test stubs, and routes
- created for you in a single command.
+- Generators: It is often handy to generate a resource and get your model,
+ controller, test stubs, and routes created for you in a single command for
+ further tweaking. Same for migrations and others.
- Plugins: Many third-party libraries come with support for Rails that reduce
or eliminate the cost of setting up and gluing together the library and the
web framework. This includes things like overriding default generators, adding
- rake tasks, and honoring Rails choices (like the logger and cache back-end).
+ Rake tasks, and honoring Rails choices (like the logger and cache back-end).
Of course, the Rails boot process also glues together all registered components.
For example, the Rails boot process is what uses your `config/database.yml` file
@@ -167,14 +166,6 @@ class definition:
config.api_only = true
```
-Optionally, in `config/environments/development.rb` add the following line
-to render error responses using the API format (JSON by default) when it
-is a local request:
-
-```ruby
-config.debug_exception_response_format = :api
-```
-
Finally, inside `app/controllers/application_controller.rb`, instead of:
```ruby