mirror of
https://github.com/github/rails.git
synced 2026-04-26 03:00:59 -04:00
Merge branch 'master' of git://github.com/rails/rails
This commit is contained in:
@@ -2,7 +2,8 @@ require "abstract_unit"
|
||||
require "rails/subscriber/test_helper"
|
||||
require "action_mailer/railties/subscriber"
|
||||
|
||||
module SubscriberTest
|
||||
class AMSubscriberTest < ActionMailer::TestCase
|
||||
include Rails::Subscriber::TestHelper
|
||||
Rails::Subscriber.add(:action_mailer, ActionMailer::Railties::Subscriber.new)
|
||||
|
||||
class TestMailer < ActionMailer::Base
|
||||
@@ -40,14 +41,4 @@ module SubscriberTest
|
||||
assert_equal 1, @logger.logged(:debug).size
|
||||
assert_match /Jamis/, @logger.logged(:debug).first
|
||||
end
|
||||
|
||||
class SyncSubscriberTest < ActionMailer::TestCase
|
||||
include Rails::Subscriber::SyncTestHelper
|
||||
include SubscriberTest
|
||||
end
|
||||
|
||||
class AsyncSubscriberTest < ActionMailer::TestCase
|
||||
include Rails::Subscriber::AsyncTestHelper
|
||||
include SubscriberTest
|
||||
end
|
||||
end
|
||||
@@ -16,7 +16,6 @@ module ActionController
|
||||
autoload :ConditionalGet
|
||||
autoload :Configuration
|
||||
autoload :Cookies
|
||||
autoload :FilterParameterLogging
|
||||
autoload :Flash
|
||||
autoload :Head
|
||||
autoload :Helpers
|
||||
|
||||
@@ -27,7 +27,6 @@ module ActionController
|
||||
include ActionController::Compatibility
|
||||
|
||||
include ActionController::Cookies
|
||||
include ActionController::FilterParameterLogging
|
||||
include ActionController::Flash
|
||||
include ActionController::Verification
|
||||
include ActionController::RequestForgeryProtection
|
||||
@@ -74,6 +73,15 @@ module ActionController
|
||||
@subclasses ||= []
|
||||
end
|
||||
|
||||
# This method has been moved to ActionDispatch::Request.filter_parameters
|
||||
def self.filter_parameter_logging(*args, &block)
|
||||
ActiveSupport::Deprecation.warn("Setting filter_parameter_logging in ActionController is deprecated and has no longer effect, please set 'config.filter_parameters' in config/application.rb instead", caller)
|
||||
filter = Rails.application.config.filter_parameters
|
||||
filter.concat(args)
|
||||
filter << block if block
|
||||
filter
|
||||
end
|
||||
|
||||
def _normalize_options(action=nil, options={}, &blk)
|
||||
case action
|
||||
when NilClass
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
module ActionController
|
||||
module FilterParameterLogging
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
INTERNAL_PARAMS = %w(controller action format _method only_path)
|
||||
|
||||
module ClassMethods
|
||||
# Replace sensitive parameter data from the request log.
|
||||
# Filters parameters that have any of the arguments as a substring.
|
||||
# Looks in all subhashes of the param hash for keys to filter.
|
||||
# If a block is given, each key and value of the parameter hash and all
|
||||
# subhashes is passed to it, the value or key
|
||||
# can be replaced using String#replace or similar method.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# filter_parameter_logging :password
|
||||
# => replaces the value to all keys matching /password/i with "[FILTERED]"
|
||||
#
|
||||
# filter_parameter_logging :foo, "bar"
|
||||
# => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
|
||||
#
|
||||
# filter_parameter_logging { |k,v| v.reverse! if k =~ /secret/i }
|
||||
# => reverses the value to all keys matching /secret/i
|
||||
#
|
||||
# filter_parameter_logging(:foo, "bar") { |k,v| v.reverse! if k =~ /secret/i }
|
||||
# => reverses the value to all keys matching /secret/i, and
|
||||
# replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
|
||||
def filter_parameter_logging(*filter_words, &block)
|
||||
raise "You must filter at least one word from logging" if filter_words.empty?
|
||||
|
||||
parameter_filter = Regexp.new(filter_words.join('|'), true)
|
||||
|
||||
define_method(:filter_parameters) do |original_params|
|
||||
filtered_params = {}
|
||||
|
||||
original_params.each do |key, value|
|
||||
if key =~ parameter_filter
|
||||
value = '[FILTERED]'
|
||||
elsif value.is_a?(Hash)
|
||||
value = filter_parameters(value)
|
||||
elsif value.is_a?(Array)
|
||||
value = value.map { |item| filter_parameters(item) }
|
||||
elsif block_given?
|
||||
key = key.dup
|
||||
value = value.dup if value.duplicable?
|
||||
yield key, value
|
||||
end
|
||||
|
||||
filtered_params[key] = value
|
||||
end
|
||||
|
||||
filtered_params.except!(*INTERNAL_PARAMS)
|
||||
end
|
||||
protected :filter_parameters
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def filter_parameters(params)
|
||||
params.dup.except!(*INTERNAL_PARAMS)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -10,7 +10,6 @@ module ActionController
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include AbstractController::Logger
|
||||
include ActionController::FilterParameterLogging
|
||||
|
||||
attr_internal :view_runtime
|
||||
|
||||
@@ -18,7 +17,7 @@ module ActionController
|
||||
raw_payload = {
|
||||
:controller => self.class.name,
|
||||
:action => self.action_name,
|
||||
:params => filter_parameters(params),
|
||||
:params => request.filtered_parameters,
|
||||
:formats => request.formats.map(&:to_sym)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
module ActionController
|
||||
module Railties
|
||||
class Subscriber < Rails::Subscriber
|
||||
INTERNAL_PARAMS = %w(controller action format _method only_path)
|
||||
|
||||
def start_processing(event)
|
||||
payload = event.payload
|
||||
params = payload[:params].except(*INTERNAL_PARAMS)
|
||||
|
||||
info " Processing by #{payload[:controller]}##{payload[:action]} as #{payload[:formats].first.to_s.upcase}"
|
||||
info " Parameters: #{payload[:params].inspect}" unless payload[:params].blank?
|
||||
info " Parameters: #{params.inspect}" unless params.empty?
|
||||
end
|
||||
|
||||
def process_action(event)
|
||||
|
||||
@@ -46,7 +46,6 @@ module ActionDispatch
|
||||
autoload :Cookies
|
||||
autoload :Flash
|
||||
autoload :Head
|
||||
autoload :Notifications
|
||||
autoload :ParamsParser
|
||||
autoload :Rescue
|
||||
autoload :ShowExceptions
|
||||
@@ -63,6 +62,7 @@ module ActionDispatch
|
||||
autoload :Headers
|
||||
autoload :MimeNegotiation
|
||||
autoload :Parameters
|
||||
autoload :FilterParameters
|
||||
autoload :Upload
|
||||
autoload :UploadedFile, 'action_dispatch/http/upload'
|
||||
autoload :URL
|
||||
|
||||
98
actionpack/lib/action_dispatch/http/filter_parameters.rb
Normal file
98
actionpack/lib/action_dispatch/http/filter_parameters.rb
Normal file
@@ -0,0 +1,98 @@
|
||||
require 'active_support/core_ext/object/blank'
|
||||
require 'active_support/core_ext/hash/keys'
|
||||
|
||||
module ActionDispatch
|
||||
module Http
|
||||
# Allows you to specify sensitive parameters which will be replaced from
|
||||
# the request log by looking in all subhashes of the param hash for keys
|
||||
# to filter. If a block is given, each key and value of the parameter
|
||||
# hash and all subhashes is passed to it, the value or key can be replaced
|
||||
# using String#replace or similar method.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# env["action_dispatch.parameter_filter"] = [:password]
|
||||
# => replaces the value to all keys matching /password/i with "[FILTERED]"
|
||||
#
|
||||
# env["action_dispatch.parameter_filter"] = [:foo, "bar"]
|
||||
# => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
|
||||
#
|
||||
# env["action_dispatch.parameter_filter"] = lambda do |k,v|
|
||||
# v.reverse! if k =~ /secret/i
|
||||
# end
|
||||
# => reverses the value to all keys matching /secret/i
|
||||
#
|
||||
module FilterParameters
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# Return a hash of parameters with all sensitive data replaced.
|
||||
def filtered_parameters
|
||||
@filtered_parameters ||= process_parameter_filter(parameters)
|
||||
end
|
||||
alias :fitered_params :filtered_parameters
|
||||
|
||||
# Return a hash of request.env with all sensitive data replaced.
|
||||
def filtered_env
|
||||
filtered_env = @env.dup
|
||||
filtered_env.each do |key, value|
|
||||
if (key =~ /RAW_POST_DATA/i)
|
||||
filtered_env[key] = '[FILTERED]'
|
||||
elsif value.is_a?(Hash)
|
||||
filtered_env[key] = process_parameter_filter(value)
|
||||
end
|
||||
end
|
||||
filtered_env
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def compile_parameter_filter #:nodoc:
|
||||
strings, regexps, blocks = [], [], []
|
||||
|
||||
Array(@env["action_dispatch.parameter_filter"]).each do |item|
|
||||
case item
|
||||
when NilClass
|
||||
when Proc
|
||||
blocks << item
|
||||
when Regexp
|
||||
regexps << item
|
||||
else
|
||||
strings << item.to_s
|
||||
end
|
||||
end
|
||||
|
||||
regexps << Regexp.new(strings.join('|'), true) unless strings.empty?
|
||||
[regexps, blocks]
|
||||
end
|
||||
|
||||
def filtering_parameters? #:nodoc:
|
||||
@env["action_dispatch.parameter_filter"].present?
|
||||
end
|
||||
|
||||
def process_parameter_filter(original_params) #:nodoc:
|
||||
return original_params.dup unless filtering_parameters?
|
||||
|
||||
filtered_params = {}
|
||||
regexps, blocks = compile_parameter_filter
|
||||
|
||||
original_params.each do |key, value|
|
||||
if regexps.find { |r| key =~ r }
|
||||
value = '[FILTERED]'
|
||||
elsif value.is_a?(Hash)
|
||||
value = process_parameter_filter(value)
|
||||
elsif value.is_a?(Array)
|
||||
value = value.map { |i| process_parameter_filter(i) }
|
||||
elsif blocks.present?
|
||||
key = key.dup
|
||||
value = value.dup if value.duplicable?
|
||||
blocks.each { |b| b.call(key, value) }
|
||||
end
|
||||
|
||||
filtered_params[key] = value
|
||||
end
|
||||
|
||||
filtered_params
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -30,29 +30,6 @@ module ActionDispatch
|
||||
@env["action_dispatch.request.path_parameters"] ||= {}
|
||||
end
|
||||
|
||||
def filter_parameters
|
||||
# TODO: Remove dependency on controller
|
||||
if controller = @env['action_controller.instance']
|
||||
controller.send(:filter_parameters, params)
|
||||
else
|
||||
params
|
||||
end
|
||||
end
|
||||
|
||||
def filter_env
|
||||
if controller = @env['action_controller.instance']
|
||||
@env.map do |key, value|
|
||||
if (key =~ /RAW_POST_DATA/i)
|
||||
'[FILTERED]'
|
||||
else
|
||||
controller.send(:filter_parameters, {key => value}).values[0]
|
||||
end
|
||||
end
|
||||
else
|
||||
env
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Convert nested Hashs to HashWithIndifferentAccess
|
||||
def normalize_parameters(value)
|
||||
|
||||
@@ -11,6 +11,7 @@ module ActionDispatch
|
||||
include ActionDispatch::Http::Cache::Request
|
||||
include ActionDispatch::Http::MimeNegotiation
|
||||
include ActionDispatch::Http::Parameters
|
||||
include ActionDispatch::Http::FilterParameters
|
||||
include ActionDispatch::Http::Upload
|
||||
include ActionDispatch::Http::URL
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
module ActionDispatch
|
||||
# Provide notifications in the middleware stack. Notice that for the before_dispatch
|
||||
# and after_dispatch notifications, we just send the original env, so we don't pile
|
||||
# up large env hashes in the queue. However, in exception cases, the whole env hash
|
||||
# is actually useful, so we send it all.
|
||||
class Notifications
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
request = Request.new(env)
|
||||
payload = retrieve_payload_from_env(request.filter_env)
|
||||
|
||||
ActiveSupport::Notifications.instrument("action_dispatch.before_dispatch", payload)
|
||||
|
||||
ActiveSupport::Notifications.instrument!("action_dispatch.after_dispatch", payload) do
|
||||
@app.call(env)
|
||||
end
|
||||
rescue Exception => exception
|
||||
ActiveSupport::Notifications.instrument('action_dispatch.exception',
|
||||
:env => env, :exception => exception)
|
||||
raise exception
|
||||
end
|
||||
|
||||
protected
|
||||
# Remove any rack related constants from the env, like rack.input.
|
||||
def retrieve_payload_from_env(env)
|
||||
Hash[:env => env.except(*env.keys.select { |k| k.to_s.index("rack.") == 0 })]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,9 +5,6 @@ module ActionDispatch
|
||||
class Railtie < Rails::Railtie
|
||||
plugin_name :action_dispatch
|
||||
|
||||
require "action_dispatch/railties/subscriber"
|
||||
subscriber ActionDispatch::Railties::Subscriber.new
|
||||
|
||||
# Prepare dispatcher callbacks and run 'prepare' callbacks
|
||||
initializer "action_dispatch.prepare_dispatcher" do |app|
|
||||
# TODO: This used to say unless defined?(Dispatcher). Find out why and fix.
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
module ActionDispatch
|
||||
module Railties
|
||||
class Subscriber < Rails::Subscriber
|
||||
def before_dispatch(event)
|
||||
request = Request.new(event.payload[:env])
|
||||
path = request.request_uri.inspect rescue "unknown"
|
||||
|
||||
info "\n\nStarted #{request.method.to_s.upcase} #{path} " <<
|
||||
"for #{request.remote_ip} at #{event.time.to_s(:db)}"
|
||||
end
|
||||
|
||||
def logger
|
||||
ActionController::Base.logger
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -33,6 +33,10 @@ module AbstractController
|
||||
render
|
||||
end
|
||||
|
||||
def shortcut
|
||||
render "template"
|
||||
end
|
||||
|
||||
def template_name
|
||||
render :_template_name => :template_name
|
||||
end
|
||||
@@ -73,6 +77,11 @@ module AbstractController
|
||||
assert_equal "With Default", @controller.response_body
|
||||
end
|
||||
|
||||
def test_render_template_through_shortcut
|
||||
@controller.process(:shortcut)
|
||||
assert_equal "With Template", @controller.response_body
|
||||
end
|
||||
|
||||
def test_render_template_name
|
||||
@controller.process(:template_name)
|
||||
assert_equal "With Template Name", @controller.response_body
|
||||
|
||||
@@ -6,16 +6,15 @@ require 'action_controller/railties/subscriber'
|
||||
|
||||
ActionController::Base.send :include, ActiveRecord::Railties::ControllerRuntime
|
||||
|
||||
module ControllerRuntimeSubscriberTest
|
||||
class ControllerRuntimeSubscriberTest < ActionController::TestCase
|
||||
class SubscriberController < ActionController::Base
|
||||
def show
|
||||
render :inline => "<%= Project.all %>"
|
||||
end
|
||||
end
|
||||
|
||||
def self.included(base)
|
||||
base.tests SubscriberController
|
||||
end
|
||||
|
||||
include Rails::Subscriber::TestHelper
|
||||
tests SubscriberController
|
||||
|
||||
def setup
|
||||
@old_logger = ActionController::Base.logger
|
||||
@@ -40,14 +39,4 @@ module ControllerRuntimeSubscriberTest
|
||||
assert_equal 2, @logger.logged(:info).size
|
||||
assert_match /\(Views: [\d\.]+ms | ActiveRecord: [\d\.]+ms\)/, @logger.logged(:info)[1]
|
||||
end
|
||||
|
||||
class SyncSubscriberTest < ActionController::TestCase
|
||||
include Rails::Subscriber::SyncTestHelper
|
||||
include ControllerRuntimeSubscriberTest
|
||||
end
|
||||
|
||||
class AsyncSubscriberTest < ActionController::TestCase
|
||||
include Rails::Subscriber::AsyncTestHelper
|
||||
include ControllerRuntimeSubscriberTest
|
||||
end
|
||||
end
|
||||
@@ -2,6 +2,9 @@ require 'abstract_unit'
|
||||
require 'logger'
|
||||
require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late
|
||||
|
||||
module Rails
|
||||
end
|
||||
|
||||
# Provide some controller to run the tests on.
|
||||
module Submodule
|
||||
class ContainedEmptyController < ActionController::Base
|
||||
@@ -63,7 +66,7 @@ class DefaultUrlOptionsController < ActionController::Base
|
||||
end
|
||||
end
|
||||
|
||||
class ControllerClassTests < Test::Unit::TestCase
|
||||
class ControllerClassTests < ActiveSupport::TestCase
|
||||
def test_controller_path
|
||||
assert_equal 'empty', EmptyController.controller_path
|
||||
assert_equal EmptyController.controller_path, EmptyController.new.controller_path
|
||||
@@ -74,7 +77,21 @@ class ControllerClassTests < Test::Unit::TestCase
|
||||
def test_controller_name
|
||||
assert_equal 'empty', EmptyController.controller_name
|
||||
assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name
|
||||
end
|
||||
end
|
||||
|
||||
def test_filter_parameter_logging
|
||||
parameters = []
|
||||
config = mock(:config => mock(:filter_parameters => parameters))
|
||||
Rails.expects(:application).returns(config)
|
||||
|
||||
assert_deprecated do
|
||||
Class.new(ActionController::Base) do
|
||||
filter_parameter_logging :password
|
||||
end
|
||||
end
|
||||
|
||||
assert_equal [:password], parameters
|
||||
end
|
||||
end
|
||||
|
||||
class ControllerInstanceTests < Test::Unit::TestCase
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class FilterParamController < ActionController::Base
|
||||
def payment
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
|
||||
class FilterParamTest < ActionController::TestCase
|
||||
tests FilterParamController
|
||||
|
||||
def test_filter_parameters_must_have_one_word
|
||||
assert_raises RuntimeError do
|
||||
FilterParamController.filter_parameter_logging
|
||||
end
|
||||
end
|
||||
|
||||
def test_filter_parameters
|
||||
assert FilterParamController.respond_to?(:filter_parameter_logging)
|
||||
|
||||
test_hashes = [
|
||||
[{'foo'=>'bar'},{'foo'=>'bar'},%w'food'],
|
||||
[{'foo'=>'bar'},{'foo'=>'[FILTERED]'},%w'foo'],
|
||||
[{'foo'=>'bar', 'bar'=>'foo'},{'foo'=>'[FILTERED]', 'bar'=>'foo'},%w'foo baz'],
|
||||
[{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'],
|
||||
[{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'],
|
||||
[{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana'],
|
||||
[{'baz'=>[{'foo'=>'baz'}]}, {'baz'=>[{'foo'=>'[FILTERED]'}]}, %w(foo)]]
|
||||
|
||||
test_hashes.each do |before_filter, after_filter, filter_words|
|
||||
FilterParamController.filter_parameter_logging(*filter_words)
|
||||
assert_equal after_filter, @controller.__send__(:filter_parameters, before_filter)
|
||||
|
||||
filter_words.push('blah')
|
||||
FilterParamController.filter_parameter_logging(*filter_words) do |key, value|
|
||||
value.reverse! if key =~ /bargain/
|
||||
end
|
||||
|
||||
before_filter['barg'] = {'bargain'=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}}
|
||||
after_filter['barg'] = {'bargain'=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}}
|
||||
|
||||
assert_equal after_filter, @controller.__send__(:filter_parameters, before_filter)
|
||||
end
|
||||
end
|
||||
|
||||
def test_filter_parameters_is_protected
|
||||
FilterParamController.filter_parameter_logging(:foo)
|
||||
assert !FilterParamController.action_methods.include?('filter_parameters')
|
||||
assert_raise(NoMethodError) { @controller.filter_parameters([{'password' => '[FILTERED]'}]) }
|
||||
end
|
||||
end
|
||||
@@ -35,11 +35,9 @@ module Another
|
||||
end
|
||||
end
|
||||
|
||||
module ActionControllerSubscriberTest
|
||||
|
||||
def self.included(base)
|
||||
base.tests Another::SubscribersController
|
||||
end
|
||||
class ACSubscriberTest < ActionController::TestCase
|
||||
tests Another::SubscribersController
|
||||
include Rails::Subscriber::TestHelper
|
||||
|
||||
def setup
|
||||
@old_logger = ActionController::Base.logger
|
||||
@@ -99,7 +97,7 @@ module ActionControllerSubscriberTest
|
||||
end
|
||||
|
||||
def test_process_action_with_filter_parameters
|
||||
Another::SubscribersController.filter_parameter_logging(:lifo, :amount)
|
||||
@request.env["action_dispatch.parameter_filter"] = [:lifo, :amount]
|
||||
|
||||
get :show, :lifo => 'Pratik', :amount => '420', :step => '1'
|
||||
wait
|
||||
@@ -171,14 +169,4 @@ module ActionControllerSubscriberTest
|
||||
def logs
|
||||
@logs ||= @logger.logged(:info)
|
||||
end
|
||||
|
||||
class SyncSubscriberTest < ActionController::TestCase
|
||||
include Rails::Subscriber::SyncTestHelper
|
||||
include ActionControllerSubscriberTest
|
||||
end
|
||||
|
||||
class AsyncSubscriberTest < ActionController::TestCase
|
||||
include Rails::Subscriber::AsyncTestHelper
|
||||
include ActionControllerSubscriberTest
|
||||
end
|
||||
end
|
||||
|
||||
@@ -454,6 +454,56 @@ class RequestTest < ActiveSupport::TestCase
|
||||
assert_equal Mime::XML, request.negotiate_mime([Mime::XML, Mime::CSV])
|
||||
end
|
||||
|
||||
test "process parameter filter" do
|
||||
test_hashes = [
|
||||
[{'foo'=>'bar'},{'foo'=>'bar'},%w'food'],
|
||||
[{'foo'=>'bar'},{'foo'=>'[FILTERED]'},%w'foo'],
|
||||
[{'foo'=>'bar', 'bar'=>'foo'},{'foo'=>'[FILTERED]', 'bar'=>'foo'},%w'foo baz'],
|
||||
[{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'],
|
||||
[{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'],
|
||||
[{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana'],
|
||||
[{'baz'=>[{'foo'=>'baz'}]}, {'baz'=>[{'foo'=>'[FILTERED]'}]}, [/foo/]]]
|
||||
|
||||
test_hashes.each do |before_filter, after_filter, filter_words|
|
||||
request = stub_request('action_dispatch.parameter_filter' => filter_words)
|
||||
assert_equal after_filter, request.send(:process_parameter_filter, before_filter)
|
||||
|
||||
filter_words << 'blah'
|
||||
filter_words << lambda { |key, value|
|
||||
value.reverse! if key =~ /bargain/
|
||||
}
|
||||
|
||||
request = stub_request('action_dispatch.parameter_filter' => filter_words)
|
||||
before_filter['barg'] = {'bargain'=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}}
|
||||
after_filter['barg'] = {'bargain'=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}}
|
||||
|
||||
assert_equal after_filter, request.send(:process_parameter_filter, before_filter)
|
||||
end
|
||||
end
|
||||
|
||||
test "filtered_parameters returns params filtered" do
|
||||
request = stub_request('action_dispatch.request.parameters' =>
|
||||
{ 'lifo' => 'Pratik', 'amount' => '420', 'step' => '1' },
|
||||
'action_dispatch.parameter_filter' => [:lifo, :amount])
|
||||
|
||||
params = request.filtered_parameters
|
||||
assert_equal "[FILTERED]", params["lifo"]
|
||||
assert_equal "[FILTERED]", params["amount"]
|
||||
assert_equal "1", params["step"]
|
||||
end
|
||||
|
||||
test "filtered_env filters env as a whole" do
|
||||
request = stub_request('action_dispatch.request.parameters' =>
|
||||
{ 'amount' => '420', 'step' => '1' }, "RAW_POST_DATA" => "yada yada",
|
||||
'action_dispatch.parameter_filter' => [:lifo, :amount])
|
||||
|
||||
request = stub_request(request.filtered_env)
|
||||
|
||||
assert_equal "[FILTERED]", request.raw_post
|
||||
assert_equal "[FILTERED]", request.params["amount"]
|
||||
assert_equal "1", request.params["step"]
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def stub_request(env={})
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
require "abstract_unit"
|
||||
require "rails/subscriber/test_helper"
|
||||
require "action_dispatch/railties/subscriber"
|
||||
|
||||
module DispatcherSubscriberTest
|
||||
Boomer = lambda do |env|
|
||||
req = ActionDispatch::Request.new(env)
|
||||
case req.path
|
||||
when "/"
|
||||
[200, {}, []]
|
||||
else
|
||||
raise "puke!"
|
||||
end
|
||||
end
|
||||
|
||||
App = ActionDispatch::Notifications.new(Boomer)
|
||||
|
||||
def setup
|
||||
Rails::Subscriber.add(:action_dispatch, ActionDispatch::Railties::Subscriber.new)
|
||||
@app = App
|
||||
super
|
||||
|
||||
@events = []
|
||||
ActiveSupport::Notifications.subscribe do |*args|
|
||||
@events << args
|
||||
end
|
||||
end
|
||||
|
||||
def set_logger(logger)
|
||||
ActionController::Base.logger = logger
|
||||
end
|
||||
|
||||
def test_publishes_notifications
|
||||
get "/"
|
||||
wait
|
||||
|
||||
assert_equal 2, @events.size
|
||||
before, after = @events
|
||||
|
||||
assert_equal 'action_dispatch.before_dispatch', before[0]
|
||||
assert_kind_of Hash, before[4][:env]
|
||||
assert_equal 'GET', before[4][:env]["REQUEST_METHOD"]
|
||||
|
||||
assert_equal 'action_dispatch.after_dispatch', after[0]
|
||||
assert_kind_of Hash, after[4][:env]
|
||||
assert_equal 'GET', after[4][:env]["REQUEST_METHOD"]
|
||||
end
|
||||
|
||||
def test_publishes_notifications_even_on_failures
|
||||
begin
|
||||
get "/puke"
|
||||
rescue
|
||||
end
|
||||
|
||||
wait
|
||||
|
||||
assert_equal 3, @events.size
|
||||
before, after, exception = @events
|
||||
|
||||
assert_equal 'action_dispatch.before_dispatch', before[0]
|
||||
assert_kind_of Hash, before[4][:env]
|
||||
assert_equal 'GET', before[4][:env]["REQUEST_METHOD"]
|
||||
|
||||
assert_equal 'action_dispatch.after_dispatch', after[0]
|
||||
assert_kind_of Hash, after[4][:env]
|
||||
assert_equal 'GET', after[4][:env]["REQUEST_METHOD"]
|
||||
|
||||
assert_equal 'action_dispatch.exception', exception[0]
|
||||
assert_kind_of Hash, exception[4][:env]
|
||||
assert_equal 'GET', exception[4][:env]["REQUEST_METHOD"]
|
||||
assert_kind_of RuntimeError, exception[4][:exception]
|
||||
end
|
||||
|
||||
def test_subscriber_logs_notifications
|
||||
get "/"
|
||||
wait
|
||||
|
||||
log = @logger.logged(:info).first
|
||||
assert_equal 1, @logger.logged(:info).size
|
||||
|
||||
assert_match %r{^Started GET "/"}, log
|
||||
assert_match %r{for 127\.0\.0\.1}, log
|
||||
end
|
||||
|
||||
def test_subscriber_has_its_logged_flushed_after_request
|
||||
assert_equal 0, @logger.flush_count
|
||||
get "/"
|
||||
wait
|
||||
assert_equal 1, @logger.flush_count
|
||||
end
|
||||
|
||||
def test_subscriber_has_its_logged_flushed_even_after_busted_requests
|
||||
assert_equal 0, @logger.flush_count
|
||||
begin
|
||||
get "/puke"
|
||||
rescue
|
||||
end
|
||||
wait
|
||||
assert_equal 1, @logger.flush_count
|
||||
end
|
||||
|
||||
class SyncSubscriberTest < ActionController::IntegrationTest
|
||||
include Rails::Subscriber::SyncTestHelper
|
||||
include DispatcherSubscriberTest
|
||||
end
|
||||
|
||||
class AsyncSubscriberTest < ActionController::IntegrationTest
|
||||
include Rails::Subscriber::AsyncTestHelper
|
||||
include DispatcherSubscriberTest
|
||||
end
|
||||
end
|
||||
@@ -3,7 +3,8 @@ require "rails/subscriber/test_helper"
|
||||
require "action_view/railties/subscriber"
|
||||
require "controller/fake_models"
|
||||
|
||||
module ActionViewSubscriberTest
|
||||
class AVSubscriberTest < ActiveSupport::TestCase
|
||||
include Rails::Subscriber::TestHelper
|
||||
|
||||
def setup
|
||||
@old_logger = ActionController::Base.logger
|
||||
@@ -89,14 +90,4 @@ module ActionViewSubscriberTest
|
||||
assert_equal 1, @logger.logged(:info).size
|
||||
assert_match /Rendered collection/, @logger.logged(:info).last
|
||||
end
|
||||
|
||||
class SyncSubscriberTest < ActiveSupport::TestCase
|
||||
include Rails::Subscriber::SyncTestHelper
|
||||
include ActionViewSubscriberTest
|
||||
end
|
||||
|
||||
class AsyncSubscriberTest < ActiveSupport::TestCase
|
||||
include Rails::Subscriber::AsyncTestHelper
|
||||
include ActionViewSubscriberTest
|
||||
end
|
||||
end
|
||||
@@ -45,7 +45,6 @@ module ActiveRecord
|
||||
autoload :AssociationPreload
|
||||
autoload :Associations
|
||||
autoload :AttributeMethods
|
||||
autoload :Attributes
|
||||
autoload :AutosaveAssociation
|
||||
|
||||
autoload :Relation
|
||||
@@ -77,7 +76,6 @@ module ActiveRecord
|
||||
autoload :StateMachine
|
||||
autoload :Timestamp
|
||||
autoload :Transactions
|
||||
autoload :Types
|
||||
autoload :Validations
|
||||
end
|
||||
|
||||
@@ -95,28 +93,6 @@ module ActiveRecord
|
||||
end
|
||||
end
|
||||
|
||||
module Attributes
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
eager_autoload do
|
||||
autoload :Aliasing
|
||||
autoload :Store
|
||||
autoload :Typecasting
|
||||
end
|
||||
end
|
||||
|
||||
module Type
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
eager_autoload do
|
||||
autoload :Number, 'active_record/types/number'
|
||||
autoload :Object, 'active_record/types/object'
|
||||
autoload :Serialize, 'active_record/types/serialize'
|
||||
autoload :TimeWithZone, 'active_record/types/time_with_zone'
|
||||
autoload :Unknown, 'active_record/types/unknown'
|
||||
end
|
||||
end
|
||||
|
||||
module Locking
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
|
||||
@@ -8,18 +8,25 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def read_attribute_before_type_cast(attr_name)
|
||||
_attributes.without_typecast[attr_name]
|
||||
@attributes[attr_name]
|
||||
end
|
||||
|
||||
# Returns a hash of attributes before typecasting and deserialization.
|
||||
def attributes_before_type_cast
|
||||
_attributes.without_typecast
|
||||
self.attribute_names.inject({}) do |attrs, name|
|
||||
attrs[name] = read_attribute_before_type_cast(name)
|
||||
attrs
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Handle *_before_type_cast for method_missing.
|
||||
def attribute_before_type_cast(attribute_name)
|
||||
read_attribute_before_type_cast(attribute_name)
|
||||
if attribute_name == 'id'
|
||||
read_attribute_before_type_cast(self.class.primary_key)
|
||||
else
|
||||
read_attribute_before_type_cast(attribute_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,23 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def query_attribute(attr_name)
|
||||
_attributes.has?(attr_name)
|
||||
unless value = read_attribute(attr_name)
|
||||
false
|
||||
else
|
||||
column = self.class.columns_hash[attr_name]
|
||||
if column.nil?
|
||||
if Numeric === value || value !~ /[^0-9]/
|
||||
!value.to_i.zero?
|
||||
else
|
||||
return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
|
||||
!value.blank?
|
||||
end
|
||||
elsif column.number?
|
||||
!value.zero?
|
||||
else
|
||||
!value.blank?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
@@ -19,5 +35,3 @@ module ActiveRecord
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -37,7 +37,11 @@ module ActiveRecord
|
||||
|
||||
protected
|
||||
def define_method_attribute(attr_name)
|
||||
define_read_method(attr_name.to_sym, attr_name, columns_hash[attr_name])
|
||||
if self.serialized_attributes[attr_name]
|
||||
define_read_method_for_serialized_attribute(attr_name)
|
||||
else
|
||||
define_read_method(attr_name.to_sym, attr_name, columns_hash[attr_name])
|
||||
end
|
||||
|
||||
if attr_name == primary_key && attr_name != "id"
|
||||
define_read_method(:id, attr_name, columns_hash[attr_name])
|
||||
@@ -45,12 +49,18 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
private
|
||||
# Define read method for serialized attribute.
|
||||
def define_read_method_for_serialized_attribute(attr_name)
|
||||
generated_attribute_methods.module_eval("def #{attr_name}; unserialize_attribute('#{attr_name}'); end", __FILE__, __LINE__)
|
||||
end
|
||||
|
||||
# Define an attribute reader method. Cope with nil column.
|
||||
def define_read_method(symbol, attr_name, column)
|
||||
access_code = "_attributes['#{attr_name}']"
|
||||
cast_code = column.type_cast_code('v') if column
|
||||
access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
|
||||
|
||||
unless attr_name.to_s == self.primary_key.to_s
|
||||
access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless _attributes.key?('#{attr_name}'); ")
|
||||
access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
|
||||
end
|
||||
|
||||
if cache_attribute?(attr_name)
|
||||
@@ -63,7 +73,38 @@ module ActiveRecord
|
||||
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
|
||||
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
|
||||
def read_attribute(attr_name)
|
||||
_attributes[attr_name]
|
||||
attr_name = attr_name.to_s
|
||||
attr_name = self.class.primary_key if attr_name == 'id'
|
||||
if !(value = @attributes[attr_name]).nil?
|
||||
if column = column_for_attribute(attr_name)
|
||||
if unserializable_attribute?(attr_name, column)
|
||||
unserialize_attribute(attr_name)
|
||||
else
|
||||
column.type_cast(value)
|
||||
end
|
||||
else
|
||||
value
|
||||
end
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Returns true if the attribute is of a text column and marked for serialization.
|
||||
def unserializable_attribute?(attr_name, column)
|
||||
column.text? && self.class.serialized_attributes[attr_name]
|
||||
end
|
||||
|
||||
# Returns the unserialized object of the attribute.
|
||||
def unserialize_attribute(attr_name)
|
||||
unserialized_object = object_from_yaml(@attributes[attr_name])
|
||||
|
||||
if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil?
|
||||
@attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
|
||||
else
|
||||
raise SerializationTypeMismatch,
|
||||
"#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -12,20 +12,48 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
||||
def cache_attribute?(attr_name)
|
||||
time_zone_aware?(attr_name) || super
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def time_zone_aware?(attr_name)
|
||||
column = columns_hash[attr_name]
|
||||
time_zone_aware_attributes &&
|
||||
!skip_time_zone_conversion_for_attributes.include?(attr_name.to_sym) &&
|
||||
[:datetime, :timestamp].include?(column.type)
|
||||
# Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
|
||||
# This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone.
|
||||
def define_method_attribute(attr_name)
|
||||
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
|
||||
method_body = <<-EOV
|
||||
def #{attr_name}(reload = false)
|
||||
cached = @attributes_cache['#{attr_name}']
|
||||
return cached if cached && !reload
|
||||
time = read_attribute('#{attr_name}')
|
||||
@attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
|
||||
end
|
||||
EOV
|
||||
generated_attribute_methods.module_eval(method_body, __FILE__, __LINE__)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
|
||||
# This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
|
||||
def define_method_attribute=(attr_name)
|
||||
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
|
||||
method_body = <<-EOV
|
||||
def #{attr_name}=(time)
|
||||
unless time.acts_like?(:time)
|
||||
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
|
||||
end
|
||||
time = time.in_time_zone rescue nil if time
|
||||
write_attribute(:#{attr_name}, time)
|
||||
end
|
||||
EOV
|
||||
generated_attribute_methods.module_eval(method_body, __FILE__, __LINE__)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def create_time_zone_conversion_attribute?(name, column)
|
||||
time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,9 +17,14 @@ module ActiveRecord
|
||||
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
|
||||
# columns are turned into +nil+.
|
||||
def write_attribute(attr_name, value)
|
||||
attr_name = _attributes.unalias(attr_name)
|
||||
attr_name = attr_name.to_s
|
||||
attr_name = self.class.primary_key if attr_name == 'id'
|
||||
@attributes_cache.delete(attr_name)
|
||||
_attributes[attr_name] = value
|
||||
if (column = column_for_attribute(attr_name)) && column.number?
|
||||
@attributes[attr_name] = convert_number_column_value(value)
|
||||
else
|
||||
@attributes[attr_name] = value
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
module ActiveRecord
|
||||
module Attributes
|
||||
|
||||
# Returns true if the given attribute is in the attributes hash
|
||||
def has_attribute?(attr_name)
|
||||
_attributes.key?(attr_name)
|
||||
end
|
||||
|
||||
# Returns an array of names for the attributes available on this object sorted alphabetically.
|
||||
def attribute_names
|
||||
_attributes.keys.sort!
|
||||
end
|
||||
|
||||
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
|
||||
def attributes
|
||||
attributes = _attributes.dup
|
||||
attributes.typecast! unless _attributes.frozen?
|
||||
attributes.to_h
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Not to be confused with the public #attributes method, which returns a typecasted Hash.
|
||||
def _attributes
|
||||
@attributes
|
||||
end
|
||||
|
||||
def initialize_attribute_store(merge_attributes = nil)
|
||||
@attributes = ActiveRecord::Attributes::Store.new
|
||||
@attributes.merge!(merge_attributes) if merge_attributes
|
||||
@attributes.types.merge!(self.class.attribute_types)
|
||||
@attributes.aliases.merge!('id' => self.class.primary_key) unless 'id' == self.class.primary_key
|
||||
@attributes
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -1,42 +0,0 @@
|
||||
module ActiveRecord
|
||||
module Attributes
|
||||
module Aliasing
|
||||
# Allows access to keys using aliased names.
|
||||
#
|
||||
# Example:
|
||||
# class Attributes < Hash
|
||||
# include Aliasing
|
||||
# end
|
||||
#
|
||||
# attributes = Attributes.new
|
||||
# attributes.aliases['id'] = 'fancy_primary_key'
|
||||
# attributes['fancy_primary_key'] = 2020
|
||||
#
|
||||
# attributes['id']
|
||||
# => 2020
|
||||
#
|
||||
# Additionally, symbols are always aliases of strings:
|
||||
# attributes[:fancy_primary_key]
|
||||
# => 2020
|
||||
#
|
||||
def [](key)
|
||||
super(unalias(key))
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
super(unalias(key), value)
|
||||
end
|
||||
|
||||
def aliases
|
||||
@aliases ||= {}
|
||||
end
|
||||
|
||||
def unalias(key)
|
||||
key = key.to_s
|
||||
aliases[key] || key
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
module ActiveRecord
|
||||
module Attributes
|
||||
class Store < Hash
|
||||
include ActiveRecord::Attributes::Typecasting
|
||||
include ActiveRecord::Attributes::Aliasing
|
||||
|
||||
# Attributes not mapped to a column are handled using Type::Unknown,
|
||||
# which enables boolean typecasting for unmapped keys.
|
||||
def types
|
||||
@types ||= Hash.new(Type::Unknown.new)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,117 +0,0 @@
|
||||
module ActiveRecord
|
||||
module Attributes
|
||||
module Typecasting
|
||||
# Typecasts values during access based on their key mapping to a Type.
|
||||
#
|
||||
# Example:
|
||||
# class Attributes < Hash
|
||||
# include Typecasting
|
||||
# end
|
||||
#
|
||||
# attributes = Attributes.new
|
||||
# attributes.types['comments_count'] = Type::Integer
|
||||
# attributes['comments_count'] = '5'
|
||||
#
|
||||
# attributes['comments_count']
|
||||
# => 5
|
||||
#
|
||||
# To support keys not mapped to a typecaster, add a default to types.
|
||||
# attributes.types.default = Type::Unknown
|
||||
# attributes['age'] = '25'
|
||||
# attributes['age']
|
||||
# => '25'
|
||||
#
|
||||
# A valid type supports #cast, #precast, #boolean, and #appendable? methods.
|
||||
#
|
||||
def [](key)
|
||||
value = super(key)
|
||||
typecast_read(key, value)
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
super(key, typecast_write(key, value))
|
||||
end
|
||||
|
||||
def to_h
|
||||
hash = {}
|
||||
hash.merge!(self)
|
||||
hash
|
||||
end
|
||||
|
||||
def dup # :nodoc:
|
||||
copy = super
|
||||
copy.types = types.dup
|
||||
copy
|
||||
end
|
||||
|
||||
# Provides a duplicate with typecasting disabled.
|
||||
#
|
||||
# Example:
|
||||
# attributes = Attributes.new
|
||||
# attributes.types['comments_count'] = Type::Integer
|
||||
# attributes['comments_count'] = '5'
|
||||
#
|
||||
# attributes.without_typecast['comments_count']
|
||||
# => '5'
|
||||
#
|
||||
def without_typecast
|
||||
dup.without_typecast!
|
||||
end
|
||||
|
||||
def without_typecast!
|
||||
types.clear
|
||||
self
|
||||
end
|
||||
|
||||
def typecast!
|
||||
keys.each { |key| self[key] = self[key] }
|
||||
self
|
||||
end
|
||||
|
||||
# Check if key has a value that typecasts to true.
|
||||
#
|
||||
# attributes = Attributes.new
|
||||
# attributes.types['comments_count'] = Type::Integer
|
||||
#
|
||||
# attributes['comments_count'] = 0
|
||||
# attributes.has?('comments_count')
|
||||
# => false
|
||||
#
|
||||
# attributes['comments_count'] = 1
|
||||
# attributes.has?('comments_count')
|
||||
# => true
|
||||
#
|
||||
def has?(key)
|
||||
value = self[key]
|
||||
boolean_typecast(key, value)
|
||||
end
|
||||
|
||||
def types
|
||||
@types ||= {}
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def types=(other_types)
|
||||
@types = other_types
|
||||
end
|
||||
|
||||
def boolean_typecast(key, value)
|
||||
value ? types[key].boolean(value) : false
|
||||
end
|
||||
|
||||
def typecast_read(key, value)
|
||||
type = types[key]
|
||||
value = type.cast(value)
|
||||
self[key] = value if type.appendable? && !frozen?
|
||||
|
||||
value
|
||||
end
|
||||
|
||||
def typecast_write(key, value)
|
||||
types[key].precast(value)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1200,7 +1200,7 @@ module ActiveRecord #:nodoc:
|
||||
def instantiate(record)
|
||||
object = find_sti_class(record[inheritance_column]).allocate
|
||||
|
||||
object.send(:initialize_attribute_store, record)
|
||||
object.instance_variable_set(:'@attributes', record)
|
||||
object.instance_variable_set(:'@attributes_cache', {})
|
||||
|
||||
object.send(:_run_find_callbacks)
|
||||
@@ -1663,7 +1663,7 @@ module ActiveRecord #:nodoc:
|
||||
# In both instances, valid attribute keys are determined by the column names of the associated table --
|
||||
# hence you can't have attributes that aren't part of the table columns.
|
||||
def initialize(attributes = nil)
|
||||
initialize_attribute_store(attributes_from_column_definition)
|
||||
@attributes = attributes_from_column_definition
|
||||
@attributes_cache = {}
|
||||
@new_record = true
|
||||
ensure_proper_type
|
||||
@@ -1694,7 +1694,7 @@ module ActiveRecord #:nodoc:
|
||||
callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
|
||||
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
|
||||
cloned_attributes.delete(self.class.primary_key)
|
||||
initialize_attribute_store(cloned_attributes)
|
||||
@attributes = cloned_attributes
|
||||
clear_aggregation_cache
|
||||
@attributes_cache = {}
|
||||
@new_record = true
|
||||
@@ -1924,11 +1924,21 @@ module ActiveRecord #:nodoc:
|
||||
def reload(options = nil)
|
||||
clear_aggregation_cache
|
||||
clear_association_cache
|
||||
_attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes'))
|
||||
@attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes'))
|
||||
@attributes_cache = {}
|
||||
self
|
||||
end
|
||||
|
||||
# Returns true if the given attribute is in the attributes hash
|
||||
def has_attribute?(attr_name)
|
||||
@attributes.has_key?(attr_name.to_s)
|
||||
end
|
||||
|
||||
# Returns an array of names for the attributes available on this object sorted alphabetically.
|
||||
def attribute_names
|
||||
@attributes.keys.sort
|
||||
end
|
||||
|
||||
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
|
||||
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
|
||||
# (Alias for the protected read_attribute method).
|
||||
@@ -2262,7 +2272,7 @@ module ActiveRecord #:nodoc:
|
||||
end
|
||||
|
||||
def instantiate_time_object(name, values)
|
||||
if self.class.send(:time_zone_aware?, name)
|
||||
if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
|
||||
Time.zone.local(*values)
|
||||
else
|
||||
Time.time_with_datetime_fallback(@@default_timezone, *values)
|
||||
@@ -2345,6 +2355,22 @@ module ActiveRecord #:nodoc:
|
||||
comma_pair_list(quote_columns(quoter, hash))
|
||||
end
|
||||
|
||||
def convert_number_column_value(value)
|
||||
if value == false
|
||||
0
|
||||
elsif value == true
|
||||
1
|
||||
elsif value.is_a?(String) && value.blank?
|
||||
nil
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
def object_from_yaml(string)
|
||||
return string unless string.is_a?(String) && string =~ /^---/
|
||||
YAML::load(string) rescue string
|
||||
end
|
||||
end
|
||||
|
||||
Base.class_eval do
|
||||
@@ -2359,7 +2385,6 @@ module ActiveRecord #:nodoc:
|
||||
include AttributeMethods::PrimaryKey
|
||||
include AttributeMethods::TimeZoneConversion
|
||||
include AttributeMethods::Dirty
|
||||
include Attributes, Types
|
||||
include Callbacks, ActiveModel::Observing, Timestamp
|
||||
include Associations, AssociationPreload, NamedScope
|
||||
include ActiveModel::Conversion
|
||||
|
||||
@@ -282,8 +282,11 @@ module ActiveRecord
|
||||
|
||||
def scope_for_create
|
||||
@scope_for_create ||= begin
|
||||
@create_with_value || wheres.inject({}) do |hash, where|
|
||||
hash[where.operand1.name] = where.operand2.value if where.is_a?(Arel::Predicates::Equality)
|
||||
@create_with_value || @where_values.inject({}) do |hash, where|
|
||||
if where.is_a?(Arel::Predicates::Equality)
|
||||
hash[where.operand1.name] = where.operand2.respond_to?(:value) ? where.operand2.value : where.operand2
|
||||
end
|
||||
|
||||
hash
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,11 +8,10 @@ module ActiveRecord
|
||||
|
||||
class_eval <<-CEVAL
|
||||
def #{query_method}(*args)
|
||||
spawn.tap do |new_relation|
|
||||
new_relation.#{query_method}_values ||= []
|
||||
value = Array.wrap(args.flatten).reject {|x| x.blank? }
|
||||
new_relation.#{query_method}_values += value if value.present?
|
||||
end
|
||||
new_relation = spawn
|
||||
value = Array.wrap(args.flatten).reject {|x| x.blank? }
|
||||
new_relation.#{query_method}_values += value if value.present?
|
||||
new_relation
|
||||
end
|
||||
CEVAL
|
||||
end
|
||||
@@ -20,11 +19,10 @@ module ActiveRecord
|
||||
[:where, :having].each do |query_method|
|
||||
class_eval <<-CEVAL
|
||||
def #{query_method}(*args)
|
||||
spawn.tap do |new_relation|
|
||||
new_relation.#{query_method}_values ||= []
|
||||
value = build_where(*args)
|
||||
new_relation.#{query_method}_values += [*value] if value.present?
|
||||
end
|
||||
new_relation = spawn
|
||||
value = build_where(*args)
|
||||
new_relation.#{query_method}_values += [*value] if value.present?
|
||||
new_relation
|
||||
end
|
||||
CEVAL
|
||||
end
|
||||
@@ -34,9 +32,9 @@ module ActiveRecord
|
||||
|
||||
class_eval <<-CEVAL
|
||||
def #{query_method}(value = true)
|
||||
spawn.tap do |new_relation|
|
||||
new_relation.#{query_method}_value = value
|
||||
end
|
||||
new_relation = spawn
|
||||
new_relation.#{query_method}_value = value
|
||||
new_relation
|
||||
end
|
||||
CEVAL
|
||||
end
|
||||
@@ -119,8 +117,16 @@ module ActiveRecord
|
||||
end
|
||||
end
|
||||
|
||||
@where_values.uniq.each do |w|
|
||||
arel = w.is_a?(String) ? arel.where(w) : arel.where(*w)
|
||||
@where_values.uniq.each do |where|
|
||||
next if where.blank?
|
||||
|
||||
case where
|
||||
when Arel::SqlLiteral
|
||||
arel = arel.where(where)
|
||||
else
|
||||
sql = where.is_a?(String) ? where : where.to_sql
|
||||
arel = arel.where(Arel::SqlLiteral.new("(#{sql})"))
|
||||
end
|
||||
end
|
||||
|
||||
@having_values.uniq.each do |h|
|
||||
@@ -135,7 +141,7 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
@order_values.uniq.each do |o|
|
||||
arel = arel.order(o) if o.present?
|
||||
arel = arel.order(Arel::SqlLiteral.new(o.to_s)) if o.present?
|
||||
end
|
||||
|
||||
selects = @select_values.uniq
|
||||
@@ -169,8 +175,7 @@ module ActiveRecord
|
||||
builder = PredicateBuilder.new(table.engine)
|
||||
|
||||
conditions = if [String, Array].include?(args.first.class)
|
||||
sql = @klass.send(:sanitize_sql, args.size > 1 ? args : args.first)
|
||||
Arel::SqlLiteral.new("(#{sql})") if sql.present?
|
||||
@klass.send(:sanitize_sql, args.size > 1 ? args : args.first)
|
||||
elsif args.first.is_a?(Hash)
|
||||
attributes = @klass.send(:expand_hash_conditions_for_aggregates, args.first)
|
||||
builder.build_from_hash(attributes, table)
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
module ActiveRecord
|
||||
module Types
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
module ClassMethods
|
||||
|
||||
def attribute_types
|
||||
attribute_types = {}
|
||||
columns.each do |column|
|
||||
options = {}
|
||||
options[:time_zone_aware] = time_zone_aware?(column.name)
|
||||
options[:serialize] = serialized_attributes[column.name]
|
||||
|
||||
attribute_types[column.name] = to_type(column, options)
|
||||
end
|
||||
attribute_types
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def to_type(column, options = {})
|
||||
type_class = if options[:time_zone_aware]
|
||||
Type::TimeWithZone
|
||||
elsif options[:serialize]
|
||||
Type::Serialize
|
||||
elsif [ :integer, :float, :decimal ].include?(column.type)
|
||||
Type::Number
|
||||
else
|
||||
Type::Object
|
||||
end
|
||||
|
||||
type_class.new(column, options)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -1,30 +0,0 @@
|
||||
module ActiveRecord
|
||||
module Type
|
||||
class Number < Object
|
||||
|
||||
def boolean(value)
|
||||
value = cast(value)
|
||||
!(value.nil? || value.zero?)
|
||||
end
|
||||
|
||||
def precast(value)
|
||||
convert_number_column_value(value)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def convert_number_column_value(value)
|
||||
if value == false
|
||||
0
|
||||
elsif value == true
|
||||
1
|
||||
elsif value.is_a?(String) && value.blank?
|
||||
nil
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,37 +0,0 @@
|
||||
module ActiveRecord
|
||||
module Type
|
||||
module Casting
|
||||
|
||||
def cast(value)
|
||||
typecaster.type_cast(value)
|
||||
end
|
||||
|
||||
def precast(value)
|
||||
value
|
||||
end
|
||||
|
||||
def boolean(value)
|
||||
cast(value).present?
|
||||
end
|
||||
|
||||
# Attributes::Typecasting stores appendable? types (e.g. serialized Arrays) when typecasting reads.
|
||||
def appendable?
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Object
|
||||
include Casting
|
||||
|
||||
attr_reader :name, :options
|
||||
attr_reader :typecaster
|
||||
|
||||
def initialize(typecaster = nil, options = {})
|
||||
@typecaster, @options = typecaster, options
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -1,33 +0,0 @@
|
||||
module ActiveRecord
|
||||
module Type
|
||||
class Serialize < Object
|
||||
|
||||
def cast(value)
|
||||
unserialize(value)
|
||||
end
|
||||
|
||||
def appendable?
|
||||
true
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def unserialize(value)
|
||||
unserialized_object = object_from_yaml(value)
|
||||
|
||||
if unserialized_object.is_a?(@options[:serialize]) || unserialized_object.nil?
|
||||
unserialized_object
|
||||
else
|
||||
raise SerializationTypeMismatch,
|
||||
"#{name} was supposed to be a #{@options[:serialize]}, but was a #{unserialized_object.class.to_s}"
|
||||
end
|
||||
end
|
||||
|
||||
def object_from_yaml(string)
|
||||
return string unless string.is_a?(String) && string =~ /^---/
|
||||
YAML::load(string) rescue string
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,20 +0,0 @@
|
||||
module ActiveRecord
|
||||
module Type
|
||||
class TimeWithZone < Object
|
||||
|
||||
def cast(time)
|
||||
time = super(time)
|
||||
time.acts_like?(:time) ? time.in_time_zone : time
|
||||
end
|
||||
|
||||
def precast(time)
|
||||
unless time.acts_like?(:time)
|
||||
time = time.is_a?(String) ? ::Time.zone.parse(time) : time.to_time rescue time
|
||||
end
|
||||
time = time.in_time_zone rescue nil if time
|
||||
super(time)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,37 +0,0 @@
|
||||
module ActiveRecord
|
||||
module Type
|
||||
# Useful for handling attributes not mapped to types. Performs some boolean typecasting,
|
||||
# but otherwise leaves the value untouched.
|
||||
class Unknown
|
||||
|
||||
def cast(value)
|
||||
value
|
||||
end
|
||||
|
||||
def precast(value)
|
||||
value
|
||||
end
|
||||
|
||||
# Attempts typecasting to handle numeric, false and blank values.
|
||||
def boolean(value)
|
||||
empty = (numeric?(value) && value.to_i.zero?) || false?(value) || value.blank?
|
||||
!empty
|
||||
end
|
||||
|
||||
def appendable?
|
||||
false
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def false?(value)
|
||||
ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
|
||||
end
|
||||
|
||||
def numeric?(value)
|
||||
Numeric === value || value !~ /[^0-9]/
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,20 +0,0 @@
|
||||
require "cases/helper"
|
||||
|
||||
class AliasingTest < ActiveRecord::TestCase
|
||||
|
||||
class AliasingAttributes < Hash
|
||||
include ActiveRecord::Attributes::Aliasing
|
||||
end
|
||||
|
||||
test "attribute access with aliasing" do
|
||||
attributes = AliasingAttributes.new
|
||||
attributes[:name] = 'Batman'
|
||||
attributes.aliases['nickname'] = 'name'
|
||||
|
||||
assert_equal 'Batman', attributes[:name], "Symbols should point to Strings"
|
||||
assert_equal 'Batman', attributes['name']
|
||||
assert_equal 'Batman', attributes['nickname']
|
||||
assert_equal 'Batman', attributes[:nickname]
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,120 +0,0 @@
|
||||
require "cases/helper"
|
||||
|
||||
class TypecastingTest < ActiveRecord::TestCase
|
||||
|
||||
class TypecastingAttributes < Hash
|
||||
include ActiveRecord::Attributes::Typecasting
|
||||
end
|
||||
|
||||
module MockType
|
||||
class Object
|
||||
|
||||
def cast(value)
|
||||
value
|
||||
end
|
||||
|
||||
def precast(value)
|
||||
value
|
||||
end
|
||||
|
||||
def boolean(value)
|
||||
!value.blank?
|
||||
end
|
||||
|
||||
def appendable?
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Integer < Object
|
||||
|
||||
def cast(value)
|
||||
value.to_i
|
||||
end
|
||||
|
||||
def precast(value)
|
||||
value ? value : 0
|
||||
end
|
||||
|
||||
def boolean(value)
|
||||
!Float(value).zero?
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Serialize < Object
|
||||
|
||||
def cast(value)
|
||||
YAML::load(value) rescue value
|
||||
end
|
||||
|
||||
def precast(value)
|
||||
value
|
||||
end
|
||||
|
||||
def appendable?
|
||||
true
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
@attributes = TypecastingAttributes.new
|
||||
@attributes.types.default = MockType::Object.new
|
||||
@attributes.types['comments_count'] = MockType::Integer.new
|
||||
end
|
||||
|
||||
test "typecast on read" do
|
||||
attributes = @attributes.merge('comments_count' => '5')
|
||||
assert_equal 5, attributes['comments_count']
|
||||
end
|
||||
|
||||
test "typecast on write" do
|
||||
@attributes['comments_count'] = false
|
||||
|
||||
assert_equal 0, @attributes.to_h['comments_count']
|
||||
end
|
||||
|
||||
test "serialized objects" do
|
||||
attributes = @attributes.merge('tags' => [ 'peanut butter' ].to_yaml)
|
||||
attributes.types['tags'] = MockType::Serialize.new
|
||||
attributes['tags'] << 'jelly'
|
||||
|
||||
assert_equal [ 'peanut butter', 'jelly' ], attributes['tags']
|
||||
end
|
||||
|
||||
test "without typecasting" do
|
||||
@attributes.merge!('comments_count' => '5')
|
||||
attributes = @attributes.without_typecast
|
||||
|
||||
assert_equal '5', attributes['comments_count']
|
||||
assert_equal 5, @attributes['comments_count'], "Original attributes should typecast"
|
||||
end
|
||||
|
||||
|
||||
test "typecast all attributes" do
|
||||
attributes = @attributes.merge('title' => 'I love sandwiches', 'comments_count' => '5')
|
||||
attributes.typecast!
|
||||
|
||||
assert_equal({ 'title' => 'I love sandwiches', 'comments_count' => 5 }, attributes)
|
||||
end
|
||||
|
||||
test "query for has? value" do
|
||||
attributes = @attributes.merge('comments_count' => '1')
|
||||
|
||||
assert_equal true, attributes.has?('comments_count')
|
||||
attributes['comments_count'] = '0'
|
||||
assert_equal false, attributes.has?('comments_count')
|
||||
end
|
||||
|
||||
test "attributes to Hash" do
|
||||
attributes_hash = { 'title' => 'I love sandwiches', 'comments_count' => '5' }
|
||||
attributes = @attributes.merge(attributes_hash)
|
||||
|
||||
assert_equal Hash, attributes.to_h.class
|
||||
assert_equal attributes_hash, attributes.to_h
|
||||
end
|
||||
|
||||
end
|
||||
@@ -3,7 +3,8 @@ require "models/developer"
|
||||
require "rails/subscriber/test_helper"
|
||||
require "active_record/railties/subscriber"
|
||||
|
||||
module SubscriberTest
|
||||
class SubscriberTest < ActiveSupport::TestCase
|
||||
include Rails::Subscriber::TestHelper
|
||||
Rails::Subscriber.add(:active_record, ActiveRecord::Railties::Subscriber.new)
|
||||
|
||||
def setup
|
||||
@@ -38,14 +39,4 @@ module SubscriberTest
|
||||
assert_match /CACHE/, @logger.logged(:debug).last
|
||||
assert_match /SELECT .*?FROM .?developers.?/, @logger.logged(:debug).last
|
||||
end
|
||||
|
||||
class SyncSubscriberTest < ActiveSupport::TestCase
|
||||
include Rails::Subscriber::SyncTestHelper
|
||||
include SubscriberTest
|
||||
end
|
||||
|
||||
class AsyncSubscriberTest < ActiveSupport::TestCase
|
||||
include Rails::Subscriber::AsyncTestHelper
|
||||
include SubscriberTest
|
||||
end
|
||||
end
|
||||
@@ -1,30 +0,0 @@
|
||||
require "cases/helper"
|
||||
|
||||
class NumberTest < ActiveRecord::TestCase
|
||||
|
||||
def setup
|
||||
@column = ActiveRecord::ConnectionAdapters::Column.new('comments_count', 0, 'integer')
|
||||
@number = ActiveRecord::Type::Number.new(@column)
|
||||
end
|
||||
|
||||
test "typecast" do
|
||||
assert_equal 1, @number.cast(1)
|
||||
assert_equal 1, @number.cast('1')
|
||||
assert_equal 0, @number.cast('')
|
||||
|
||||
assert_equal 0, @number.precast(false)
|
||||
assert_equal 1, @number.precast(true)
|
||||
assert_equal nil, @number.precast('')
|
||||
assert_equal 0, @number.precast(0)
|
||||
end
|
||||
|
||||
test "cast as boolean" do
|
||||
assert_equal true, @number.boolean('1')
|
||||
assert_equal true, @number.boolean(1)
|
||||
|
||||
assert_equal false, @number.boolean(0)
|
||||
assert_equal false, @number.boolean('0')
|
||||
assert_equal false, @number.boolean(nil)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,24 +0,0 @@
|
||||
require "cases/helper"
|
||||
|
||||
class ObjectTest < ActiveRecord::TestCase
|
||||
|
||||
def setup
|
||||
@column = ActiveRecord::ConnectionAdapters::Column.new('name', '', 'date')
|
||||
@object = ActiveRecord::Type::Object.new(@column)
|
||||
end
|
||||
|
||||
test "typecast with column" do
|
||||
date = Date.new(2009, 7, 10)
|
||||
assert_equal date, @object.cast('10-07-2009')
|
||||
assert_equal nil, @object.cast('')
|
||||
|
||||
assert_equal date, @object.precast(date)
|
||||
end
|
||||
|
||||
test "cast as boolean" do
|
||||
assert_equal false, @object.boolean(nil)
|
||||
assert_equal false, @object.boolean('false')
|
||||
assert_equal true, @object.boolean('10-07-2009')
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,20 +0,0 @@
|
||||
require "cases/helper"
|
||||
|
||||
class SerializeTest < ActiveRecord::TestCase
|
||||
|
||||
test "typecast" do
|
||||
serializer = ActiveRecord::Type::Serialize.new(column = nil, :serialize => Array)
|
||||
|
||||
assert_equal [], serializer.cast([].to_yaml)
|
||||
assert_equal ['1'], serializer.cast(['1'].to_yaml)
|
||||
assert_equal nil, serializer.cast(nil.to_yaml)
|
||||
end
|
||||
|
||||
test "cast as boolean" do
|
||||
serializer = ActiveRecord::Type::Serialize.new(column = nil, :serialize => Array)
|
||||
|
||||
assert_equal true, serializer.boolean(['1'].to_yaml)
|
||||
assert_equal false, serializer.boolean([].to_yaml)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,42 +0,0 @@
|
||||
require "cases/helper"
|
||||
|
||||
class TimeWithZoneTest < ActiveRecord::TestCase
|
||||
|
||||
def setup
|
||||
@column = ActiveRecord::ConnectionAdapters::Column.new('created_at', 0, 'datetime')
|
||||
@time_with_zone = ActiveRecord::Type::TimeWithZone.new(@column)
|
||||
end
|
||||
|
||||
test "typecast" do
|
||||
Time.use_zone("Pacific Time (US & Canada)") do
|
||||
time_string = "2009-10-07 21:29:10"
|
||||
time = Time.zone.parse(time_string)
|
||||
|
||||
# assert_equal time, @time_with_zone.cast(time_string)
|
||||
assert_equal nil, @time_with_zone.cast('')
|
||||
assert_equal nil, @time_with_zone.cast(nil)
|
||||
|
||||
assert_equal time, @time_with_zone.precast(time)
|
||||
assert_equal time, @time_with_zone.precast(time_string)
|
||||
assert_equal time, @time_with_zone.precast(time.to_time)
|
||||
# assert_equal "#{time.to_date.to_s} 00:00:00 -0700", @time_with_zone.precast(time.to_date).to_s
|
||||
end
|
||||
end
|
||||
|
||||
test "cast as boolean" do
|
||||
Time.use_zone('Central Time (US & Canada)') do
|
||||
time = Time.zone.now
|
||||
|
||||
assert_equal true, @time_with_zone.boolean(time)
|
||||
assert_equal true, @time_with_zone.boolean(time.to_date)
|
||||
assert_equal true, @time_with_zone.boolean(time.to_time)
|
||||
|
||||
assert_equal true, @time_with_zone.boolean(time.to_s)
|
||||
assert_equal true, @time_with_zone.boolean(time.to_date.to_s)
|
||||
assert_equal true, @time_with_zone.boolean(time.to_time.to_s)
|
||||
|
||||
assert_equal false, @time_with_zone.boolean('')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,29 +0,0 @@
|
||||
require "cases/helper"
|
||||
|
||||
class UnknownTest < ActiveRecord::TestCase
|
||||
|
||||
test "typecast attributes does't modify values" do
|
||||
unkown = ActiveRecord::Type::Unknown.new
|
||||
person = { 'name' => '0' }
|
||||
|
||||
assert_equal person['name'], unkown.cast(person['name'])
|
||||
assert_equal person['name'], unkown.precast(person['name'])
|
||||
end
|
||||
|
||||
test "cast as boolean" do
|
||||
person = { 'id' => 0, 'name' => ' ', 'admin' => 'false', 'votes' => '0' }
|
||||
unkown = ActiveRecord::Type::Unknown.new
|
||||
|
||||
assert_equal false, unkown.boolean(person['votes'])
|
||||
assert_equal false, unkown.boolean(person['admin'])
|
||||
assert_equal false, unkown.boolean(person['name'])
|
||||
assert_equal false, unkown.boolean(person['id'])
|
||||
|
||||
person = { 'id' => 5, 'name' => 'Eric', 'admin' => 'true', 'votes' => '25' }
|
||||
assert_equal true, unkown.boolean(person['votes'])
|
||||
assert_equal true, unkown.boolean(person['admin'])
|
||||
assert_equal true, unkown.boolean(person['name'])
|
||||
assert_equal true, unkown.boolean(person['id'])
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,32 +0,0 @@
|
||||
require "cases/helper"
|
||||
require 'models/topic'
|
||||
|
||||
class TypesTest < ActiveRecord::TestCase
|
||||
|
||||
test "attribute types from columns" do
|
||||
begin
|
||||
ActiveRecord::Base.time_zone_aware_attributes = true
|
||||
attribute_type_classes = {}
|
||||
Topic.attribute_types.each { |key, type| attribute_type_classes[key] = type.class }
|
||||
|
||||
expected = { "id" => ActiveRecord::Type::Number,
|
||||
"replies_count" => ActiveRecord::Type::Number,
|
||||
"parent_id" => ActiveRecord::Type::Number,
|
||||
"content" => ActiveRecord::Type::Serialize,
|
||||
"written_on" => ActiveRecord::Type::TimeWithZone,
|
||||
"title" => ActiveRecord::Type::Object,
|
||||
"author_name" => ActiveRecord::Type::Object,
|
||||
"approved" => ActiveRecord::Type::Object,
|
||||
"parent_title" => ActiveRecord::Type::Object,
|
||||
"bonus_time" => ActiveRecord::Type::Object,
|
||||
"type" => ActiveRecord::Type::Object,
|
||||
"last_read" => ActiveRecord::Type::Object,
|
||||
"author_email_address" => ActiveRecord::Type::Object }
|
||||
|
||||
assert_equal expected, attribute_type_classes
|
||||
ensure
|
||||
ActiveRecord::Base.time_zone_aware_attributes = false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -3,7 +3,8 @@ require "fixtures/person"
|
||||
require "rails/subscriber/test_helper"
|
||||
require "active_resource/railties/subscriber"
|
||||
|
||||
module SubscriberTest
|
||||
class SubscriberTest < ActiveSupport::TestCase
|
||||
include Rails::Subscriber::TestHelper
|
||||
Rails::Subscriber.add(:active_resource, ActiveResource::Railties::Subscriber.new)
|
||||
|
||||
def setup
|
||||
@@ -26,14 +27,4 @@ module SubscriberTest
|
||||
assert_equal "GET http://somewhere.else:80/people/1.xml", @logger.logged(:info)[0]
|
||||
assert_match /\-\-\> 200 200 106/, @logger.logged(:info)[1]
|
||||
end
|
||||
|
||||
class SyncSubscriberTest < ActiveSupport::TestCase
|
||||
include Rails::Subscriber::SyncTestHelper
|
||||
include SubscriberTest
|
||||
end
|
||||
|
||||
class AsyncSubscriberTest < ActiveSupport::TestCase
|
||||
include Rails::Subscriber::AsyncTestHelper
|
||||
include SubscriberTest
|
||||
end
|
||||
end
|
||||
@@ -3,11 +3,9 @@ require 'thread'
|
||||
module ActiveSupport
|
||||
module Notifications
|
||||
# This is a default queue implementation that ships with Notifications. It
|
||||
# consumes events in a thread and publish them to all registered subscribers.
|
||||
#
|
||||
# just pushes events to all registered subscribers.
|
||||
class Fanout
|
||||
def initialize(sync = false)
|
||||
@subscriber_klass = sync ? Subscriber : AsyncSubscriber
|
||||
def initialize
|
||||
@subscribers = []
|
||||
end
|
||||
|
||||
@@ -16,7 +14,7 @@ module ActiveSupport
|
||||
end
|
||||
|
||||
def subscribe(pattern = nil, &block)
|
||||
@subscribers << @subscriber_klass.new(pattern, &block)
|
||||
@subscribers << Subscriber.new(pattern, &block)
|
||||
end
|
||||
|
||||
def publish(*args)
|
||||
@@ -68,34 +66,6 @@ module ActiveSupport
|
||||
@block.call(*args)
|
||||
end
|
||||
end
|
||||
|
||||
# Used for internal implementation only.
|
||||
class AsyncSubscriber < Subscriber #:nodoc:
|
||||
def initialize(pattern, &block)
|
||||
super
|
||||
@events = Queue.new
|
||||
start_consumer
|
||||
end
|
||||
|
||||
def drained?
|
||||
@events.empty?
|
||||
end
|
||||
|
||||
private
|
||||
def start_consumer
|
||||
Thread.new { consume }
|
||||
end
|
||||
|
||||
def consume
|
||||
while args = @events.shift
|
||||
@block.call(*args)
|
||||
end
|
||||
end
|
||||
|
||||
def push(*args)
|
||||
@events << args
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,18 +3,12 @@ require 'abstract_unit'
|
||||
module Notifications
|
||||
class TestCase < ActiveSupport::TestCase
|
||||
def setup
|
||||
Thread.abort_on_exception = true
|
||||
|
||||
ActiveSupport::Notifications.notifier = nil
|
||||
@notifier = ActiveSupport::Notifications.notifier
|
||||
@events = []
|
||||
@notifier.subscribe { |*args| @events << event(*args) }
|
||||
end
|
||||
|
||||
def teardown
|
||||
Thread.abort_on_exception = false
|
||||
end
|
||||
|
||||
private
|
||||
def event(*args)
|
||||
ActiveSupport::Notifications::Event.new(*args)
|
||||
@@ -25,7 +19,7 @@ module Notifications
|
||||
end
|
||||
end
|
||||
|
||||
class PubSubTest < TestCase
|
||||
class SyncPubSubTest < TestCase
|
||||
def test_events_are_published_to_a_listener
|
||||
@notifier.publish :foo
|
||||
@notifier.wait
|
||||
@@ -72,16 +66,6 @@ module Notifications
|
||||
end
|
||||
end
|
||||
|
||||
class SyncPubSubTest < PubSubTest
|
||||
def setup
|
||||
Thread.abort_on_exception = true
|
||||
|
||||
@notifier = ActiveSupport::Notifications::Notifier.new(ActiveSupport::Notifications::Fanout.new(true))
|
||||
@events = []
|
||||
@notifier.subscribe { |*args| @events << event(*args) }
|
||||
end
|
||||
end
|
||||
|
||||
class InstrumentationTest < TestCase
|
||||
delegate :instrument, :instrument!, :to => ActiveSupport::Notifications
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
protect_from_forgery
|
||||
filter_parameter_logging :password
|
||||
end
|
||||
|
||||
@@ -30,5 +30,8 @@ module <%= app_const_base %>
|
||||
# g.template_engine :erb
|
||||
# g.test_framework :test_unit, :fixture => true
|
||||
# end
|
||||
|
||||
# Configure sensitive parameters which will be filtered from the log file.
|
||||
config.filter_parameters << :password
|
||||
end
|
||||
end
|
||||
|
||||
@@ -110,6 +110,7 @@ module Rails
|
||||
end
|
||||
|
||||
def call(env)
|
||||
env["action_dispatch.parameter_filter"] = config.filter_parameters
|
||||
app.call(env)
|
||||
end
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ module Rails
|
||||
trap(:INT) { exit }
|
||||
puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
|
||||
|
||||
initialize_log_tailer! unless options[:daemonize]
|
||||
super
|
||||
ensure
|
||||
puts 'Exiting' unless options[:daemonize]
|
||||
@@ -54,6 +53,7 @@ module Rails
|
||||
|
||||
def middleware
|
||||
middlewares = []
|
||||
middlewares << [Rails::Rack::LogTailer, log_path] unless options[:daemonize]
|
||||
middlewares << [Rails::Rack::Debugger] if options[:debugger]
|
||||
Hash.new(middlewares)
|
||||
end
|
||||
@@ -71,14 +71,5 @@ module Rails
|
||||
:pid => "tmp/pids/server.pid"
|
||||
})
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# LogTailer should not be used as a middleware since the logging happens
|
||||
# async in a request and the middleware calls are sync. So we send it
|
||||
# to subscriber which will be responsible for calling tail! in the log tailer.
|
||||
def initialize_log_tailer!
|
||||
Rails::Subscriber.log_tailer = Rails::Rack::LogTailer.new(nil, log_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,15 +10,15 @@ module Rails
|
||||
|
||||
def self.default_middleware_stack
|
||||
ActionDispatch::MiddlewareStack.new.tap do |middleware|
|
||||
middleware.use('ActionDispatch::Static', lambda { Rails.public_path }, :if => lambda { Rails.application.config.serve_static_assets })
|
||||
middleware.use('::ActionDispatch::Static', lambda { Rails.public_path }, :if => lambda { Rails.application.config.serve_static_assets })
|
||||
middleware.use('::Rack::Lock', :if => lambda { !ActionController::Base.allow_concurrency })
|
||||
middleware.use('::Rack::Runtime')
|
||||
middleware.use('ActionDispatch::ShowExceptions', lambda { ActionController::Base.consider_all_requests_local })
|
||||
middleware.use('ActionDispatch::Notifications')
|
||||
middleware.use('ActionDispatch::Callbacks', lambda { !Rails.application.config.cache_classes })
|
||||
middleware.use('ActionDispatch::Cookies')
|
||||
middleware.use('::Rails::Rack::Logger')
|
||||
middleware.use('::ActionDispatch::ShowExceptions', lambda { ActionController::Base.consider_all_requests_local })
|
||||
middleware.use('::ActionDispatch::Callbacks', lambda { !Rails.application.config.cache_classes })
|
||||
middleware.use('::ActionDispatch::Cookies')
|
||||
middleware.use(lambda { ActionController::Base.session_store }, lambda { ActionController::Base.session_options })
|
||||
middleware.use('ActionDispatch::Flash', :if => lambda { ActionController::Base.session_store })
|
||||
middleware.use('::ActionDispatch::Flash', :if => lambda { ActionController::Base.session_store })
|
||||
middleware.use(lambda { Rails::Rack::Metal.new(Rails.application.config.paths.app.metals.to_a, Rails.application.config.metals) })
|
||||
middleware.use('ActionDispatch::ParamsParser')
|
||||
middleware.use('::Rack::MethodOverride')
|
||||
@@ -69,7 +69,7 @@ module Rails
|
||||
|
||||
class Configuration < Railtie::Configuration
|
||||
attr_accessor :after_initialize_blocks, :cache_classes, :colorize_logging,
|
||||
:consider_all_requests_local, :dependency_loading,
|
||||
:consider_all_requests_local, :dependency_loading, :filter_parameters,
|
||||
:load_once_paths, :logger, :metals, :plugins,
|
||||
:preload_frameworks, :reload_plugins, :serve_static_assets,
|
||||
:time_zone, :whiny_nils
|
||||
@@ -83,6 +83,7 @@ module Rails
|
||||
super
|
||||
@load_once_paths = []
|
||||
@after_initialize_blocks = []
|
||||
@filter_parameters = []
|
||||
@dependency_loading = true
|
||||
@serve_static_assets = true
|
||||
end
|
||||
|
||||
@@ -134,9 +134,14 @@ module Rails
|
||||
lookups = []
|
||||
lookups << "#{base}:#{name}" if base
|
||||
lookups << "#{name}:#{context}" if context
|
||||
lookups << "#{name}:#{name}" unless name.to_s.include?(?:)
|
||||
lookups << "#{name}"
|
||||
lookups << "rails:#{name}" unless base || context || name.to_s.include?(?:)
|
||||
|
||||
unless base || context
|
||||
unless name.to_s.include?(?:)
|
||||
lookups << "#{name}:#{name}"
|
||||
lookups << "rails:#{name}"
|
||||
end
|
||||
lookups << "#{name}"
|
||||
end
|
||||
|
||||
lookup(lookups)
|
||||
|
||||
@@ -232,9 +237,9 @@ module Rails
|
||||
load_generators_from_railties!
|
||||
paths = namespaces_to_paths(namespaces)
|
||||
|
||||
paths.each do |path|
|
||||
["generators", "rails_generators"].each do |base|
|
||||
path = "#{base}/#{path}_generator"
|
||||
paths.each do |raw_path|
|
||||
["rails_generators", "generators"].each do |base|
|
||||
path = "#{base}/#{raw_path}_generator"
|
||||
|
||||
begin
|
||||
require path
|
||||
@@ -243,7 +248,9 @@ module Rails
|
||||
raise unless e.message =~ /#{Regexp.escape(path)}$/
|
||||
rescue NameError => e
|
||||
raise unless e.message =~ /Rails::Generator([\s(::)]|$)/
|
||||
warn "[WARNING] Could not load generator #{path.inspect} because it's a Rails 2.x generator, which is not supported anymore. Error: #{e.message}"
|
||||
warn "[WARNING] Could not load generator #{path.inspect} because it's a Rails 2.x generator, which is not supported anymore. Error: #{e.message}.\n#{e.backtrace.join("\n")}"
|
||||
rescue Exception => e
|
||||
warn "[WARNING] Could not load generator #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -278,7 +285,7 @@ module Rails
|
||||
paths = []
|
||||
namespaces.each do |namespace|
|
||||
pieces = namespace.split(":")
|
||||
paths << pieces.dup.push(pieces.last).join("/")
|
||||
paths << pieces.dup.push(pieces.last).join("/") unless pieces.uniq.size == 1
|
||||
paths << pieces.join("/")
|
||||
end
|
||||
paths.uniq!
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
module Rails
|
||||
module Rack
|
||||
autoload :Debugger, "rails/rack/debugger"
|
||||
autoload :Logger, "rails/rack/logger"
|
||||
autoload :LogTailer, "rails/rack/log_tailer"
|
||||
autoload :Metal, "rails/rack/metal"
|
||||
autoload :Static, "rails/rack/static"
|
||||
|
||||
38
railties/lib/rails/rack/logger.rb
Normal file
38
railties/lib/rails/rack/logger.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
require 'rails/subscriber'
|
||||
|
||||
module Rails
|
||||
module Rack
|
||||
# Log the request started and flush all loggers after it.
|
||||
class Logger < Rails::Subscriber
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@env = env
|
||||
before_dispatch
|
||||
result = @app.call(@env)
|
||||
after_dispatch
|
||||
result
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def request
|
||||
@request ||= ActionDispatch::Request.new(@env)
|
||||
end
|
||||
|
||||
def before_dispatch
|
||||
path = request.request_uri.inspect rescue "unknown"
|
||||
|
||||
info "\n\nStarted #{request.method.to_s.upcase} #{path} " <<
|
||||
"for #{request.remote_ip} at #{Time.now.to_s(:db)}"
|
||||
end
|
||||
|
||||
def after_dispatch
|
||||
Rails::Subscriber.flush_all!
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -33,7 +33,7 @@ module Rails
|
||||
# Subscriber also has some helpers to deal with logging and automatically flushes
|
||||
# all logs when the request finishes (via action_dispatch.callback notification).
|
||||
class Subscriber
|
||||
mattr_accessor :colorize_logging, :log_tailer
|
||||
mattr_accessor :colorize_logging
|
||||
self.colorize_logging = true
|
||||
|
||||
# Embed in a String to clear all previous ANSI sequences.
|
||||
@@ -69,11 +69,6 @@ module Rails
|
||||
Rails.logger.error "Could not log #{args[0].inspect} event. #{e.class}: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
if args[0] == "action_dispatch.after_dispatch" && !subscribers.empty?
|
||||
flush_all!
|
||||
log_tailer.tail! if log_tailer
|
||||
end
|
||||
end
|
||||
|
||||
# Flush all subscribers' logger.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
require 'rails/subscriber'
|
||||
require 'active_support/notifications'
|
||||
|
||||
module Rails
|
||||
class Subscriber
|
||||
# Provides some helpers to deal with testing subscribers by setting up
|
||||
# notifications. Take for instance ActiveRecord subscriber tests:
|
||||
#
|
||||
# module SubscriberTest
|
||||
# class SyncSubscriberTest < ActiveSupport::TestCase
|
||||
# include Rails::Subscriber::TestHelper
|
||||
# Rails::Subscriber.add(:active_record, ActiveRecord::Railties::Subscriber.new)
|
||||
#
|
||||
# def test_basic_query_logging
|
||||
@@ -39,8 +39,6 @@ module Rails
|
||||
#
|
||||
module TestHelper
|
||||
def setup
|
||||
Thread.abort_on_exception = true
|
||||
|
||||
@logger = MockLogger.new
|
||||
@notifier = ActiveSupport::Notifications::Notifier.new(queue)
|
||||
|
||||
@@ -54,7 +52,6 @@ module Rails
|
||||
def teardown
|
||||
set_logger(nil)
|
||||
ActiveSupport::Notifications.notifier = nil
|
||||
Thread.abort_on_exception = false
|
||||
end
|
||||
|
||||
class MockLogger
|
||||
@@ -92,26 +89,9 @@ module Rails
|
||||
def set_logger(logger)
|
||||
Rails.logger = logger
|
||||
end
|
||||
end
|
||||
|
||||
module SyncTestHelper
|
||||
include TestHelper
|
||||
|
||||
def queue
|
||||
ActiveSupport::Notifications::Fanout.new(true)
|
||||
end
|
||||
end
|
||||
|
||||
module AsyncTestHelper
|
||||
include TestHelper
|
||||
|
||||
def queue
|
||||
ActiveSupport::Notifications::Fanout.new(false)
|
||||
end
|
||||
|
||||
def wait
|
||||
sleep(0.01)
|
||||
super
|
||||
ActiveSupport::Notifications::Fanout.new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -122,5 +122,17 @@ module ApplicationTests
|
||||
require "#{app_path}/config/environment"
|
||||
end
|
||||
end
|
||||
|
||||
test "filter_parameters should be able to set via config.filter_parameters" do
|
||||
add_to_config <<-RUBY
|
||||
config.filter_parameters += [ :foo, 'bar', lambda { |key, value|
|
||||
value = value.reverse if key =~ /baz/
|
||||
}]
|
||||
RUBY
|
||||
|
||||
assert_nothing_raised do
|
||||
require "#{app_path}/config/application"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,8 +17,8 @@ module ApplicationTests
|
||||
"ActionDispatch::Static",
|
||||
"Rack::Lock",
|
||||
"Rack::Runtime",
|
||||
"Rails::Rack::Logger",
|
||||
"ActionDispatch::ShowExceptions",
|
||||
"ActionDispatch::Notifications",
|
||||
"ActionDispatch::Callbacks",
|
||||
"ActionDispatch::Cookies",
|
||||
"ActionDispatch::Session::CookieStore",
|
||||
|
||||
1
railties/test/fixtures/lib/generators/model_generator.rb
vendored
Normal file
1
railties/test/fixtures/lib/generators/model_generator.rb
vendored
Normal file
@@ -0,0 +1 @@
|
||||
raise "I should never be loaded"
|
||||
@@ -16,6 +16,7 @@ class GeneratorsTest < Rails::Generators::TestCase
|
||||
end
|
||||
|
||||
def test_simple_invoke
|
||||
assert File.exists?(File.join(@path, 'generators', 'model_generator.rb'))
|
||||
TestUnit::Generators::ModelGenerator.expects(:start).with(["Account"], {})
|
||||
Rails::Generators.invoke("test_unit:model", ["Account"])
|
||||
end
|
||||
@@ -30,6 +31,13 @@ class GeneratorsTest < Rails::Generators::TestCase
|
||||
assert_match /Description:/, output
|
||||
end
|
||||
|
||||
def test_should_give_higher_preference_to_rails_generators
|
||||
assert File.exists?(File.join(@path, 'generators', 'model_generator.rb'))
|
||||
Rails::Generators::ModelGenerator.expects(:start).with(["Account"], {})
|
||||
warnings = capture(:stderr){ Rails::Generators.invoke :model, ["Account"] }
|
||||
assert warnings.empty?
|
||||
end
|
||||
|
||||
def test_invoke_with_default_values
|
||||
Rails::Generators::ModelGenerator.expects(:start).with(["Account"], {})
|
||||
Rails::Generators.invoke :model, ["Account"]
|
||||
|
||||
@@ -24,7 +24,9 @@ class MySubscriber < Rails::Subscriber
|
||||
end
|
||||
end
|
||||
|
||||
module SubscriberTest
|
||||
class SyncSubscriberTest < ActiveSupport::TestCase
|
||||
include Rails::Subscriber::TestHelper
|
||||
|
||||
def setup
|
||||
super
|
||||
@subscriber = MySubscriber.new
|
||||
@@ -94,51 +96,24 @@ module SubscriberTest
|
||||
assert_equal 1, @logger.flush_count
|
||||
end
|
||||
|
||||
def test_flushes_loggers_when_action_dispatch_callback_is_received
|
||||
Rails::Subscriber.add :my_subscriber, @subscriber
|
||||
instrument "action_dispatch.after_dispatch"
|
||||
wait
|
||||
assert_equal 1, @logger.flush_count
|
||||
end
|
||||
|
||||
def test_flushes_the_same_logger_just_once
|
||||
Rails::Subscriber.add :my_subscriber, @subscriber
|
||||
Rails::Subscriber.add :another, @subscriber
|
||||
instrument "action_dispatch.after_dispatch"
|
||||
Rails::Subscriber.flush_all!
|
||||
wait
|
||||
assert_equal 1, @logger.flush_count
|
||||
end
|
||||
|
||||
def test_logging_thread_does_not_die_on_failures
|
||||
def test_logging_does_not_die_on_failures
|
||||
Rails::Subscriber.add :my_subscriber, @subscriber
|
||||
instrument "my_subscriber.puke"
|
||||
instrument "action_dispatch.after_dispatch"
|
||||
instrument "my_subscriber.some_event"
|
||||
wait
|
||||
assert_equal 1, @logger.flush_count
|
||||
|
||||
assert_equal 1, @logger.logged(:info).size
|
||||
assert_equal 'my_subscriber.some_event', @logger.logged(:info).last
|
||||
|
||||
assert_equal 1, @logger.logged(:error).size
|
||||
assert_equal 'Could not log "my_subscriber.puke" event. RuntimeError: puke', @logger.logged(:error).last
|
||||
end
|
||||
|
||||
def test_tails_logs_when_action_dispatch_callback_is_received
|
||||
log_tailer = mock()
|
||||
log_tailer.expects(:tail!)
|
||||
Rails::Subscriber.log_tailer = log_tailer
|
||||
|
||||
Rails::Subscriber.add :my_subscriber, @subscriber
|
||||
instrument "action_dispatch.after_dispatch"
|
||||
wait
|
||||
ensure
|
||||
Rails::Subscriber.log_tailer = nil
|
||||
end
|
||||
|
||||
class SyncSubscriberTest < ActiveSupport::TestCase
|
||||
include Rails::Subscriber::SyncTestHelper
|
||||
include SubscriberTest
|
||||
end
|
||||
|
||||
class AsyncSubscriberTest < ActiveSupport::TestCase
|
||||
include Rails::Subscriber::AsyncTestHelper
|
||||
include SubscriberTest
|
||||
end
|
||||
|
||||
end
|
||||
Reference in New Issue
Block a user