aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJosé Valim <jose.valim@gmail.com>2009-12-01 13:00:34 -0200
committerJosé Valim <jose.valim@gmail.com>2009-12-01 13:00:34 -0200
commitc2e97cb410d759f383d29920165abdbf4b70e019 (patch)
treeb375de84add24ee4c4551deec533b0667512bf34
parentfc3629f6ca2b43693f5447a1fb43881f1814e117 (diff)
parent6ac32a83283f46b55675ddf4ecab6c91f6f8abde (diff)
downloadrails-c2e97cb410d759f383d29920165abdbf4b70e019.tar.gz
rails-c2e97cb410d759f383d29920165abdbf4b70e019.tar.bz2
rails-c2e97cb410d759f383d29920165abdbf4b70e019.zip
Merge branch 'master' of git://github.com/rails/rails
-rw-r--r--.gitmodules3
-rw-r--r--actionmailer/lib/action_mailer/delivery_method/smtp.rb2
-rw-r--r--actionmailer/test/mail_service_test.rb34
-rw-r--r--actionmailer/test/url_test.rb44
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb279
-rw-r--r--actionpack/lib/action_view.rb2
-rw-r--r--actionpack/test/controller/caching_test.rb4
-rw-r--r--actionpack/test/dispatch/routing_test.rb8
-rw-r--r--activesupport/lib/active_support/notifications.rb168
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb101
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb47
-rw-r--r--activesupport/test/notifications_test.rb293
m---------rack-mount0
-rw-r--r--railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt2
-rw-r--r--railties/test/application/notifications_test.rb30
17 files changed, 545 insertions, 476 deletions
diff --git a/.gitmodules b/.gitmodules
index 20ed3ed9c9..fd4fd34d3e 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,3 @@
[submodule "arel"]
path = arel
url = git://github.com/rails/arel.git
-[submodule "rack-mount"]
- path = rack-mount
- url = git://github.com/rails/rack-mount.git
diff --git a/actionmailer/lib/action_mailer/delivery_method/smtp.rb b/actionmailer/lib/action_mailer/delivery_method/smtp.rb
index 86b0ae8329..95c117c9e0 100644
--- a/actionmailer/lib/action_mailer/delivery_method/smtp.rb
+++ b/actionmailer/lib/action_mailer/delivery_method/smtp.rb
@@ -16,7 +16,7 @@ module ActionMailer
def perform_delivery(mail)
destinations = mail.destinations
mail.ready_to_send
- sender = (mail['return-path'] && mail['return-path'].spec) || mail['from']
+ sender = (mail['return-path'] && mail['return-path'].spec) || Array(mail.from).first
smtp = Net::SMTP.new(settings[:address], settings[:port])
smtp.enable_starttls_auto if settings[:enable_starttls_auto] && smtp.respond_to?(:enable_starttls_auto)
diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb
index c98f0a7601..697265b8ec 100644
--- a/actionmailer/test/mail_service_test.rb
+++ b/actionmailer/test/mail_service_test.rb
@@ -31,6 +31,18 @@ class TestMailer < ActionMailer::Base
render :text => "Goodbye, Mr. #{recipient}"
end
+ def from_with_name
+ from "System <system@loudthinking.com>"
+ recipients "root@loudthinking.com"
+ body "Nothing to see here."
+ end
+
+ def from_without_name
+ from "system@loudthinking.com"
+ recipients "root@loudthinking.com"
+ body "Nothing to see here."
+ end
+
def cc_bcc(recipient)
recipients recipient
subject "testing bcc/cc"
@@ -487,6 +499,28 @@ class ActionMailerTest < Test::Unit::TestCase
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
end
+ def test_from_without_name_for_smtp
+ ActionMailer::Base.delivery_method = :smtp
+ TestMailer.deliver_from_without_name
+
+ mail = MockSMTP.deliveries.first
+ assert_not_nil mail
+ mail, from, to = mail
+
+ assert_equal 'system@loudthinking.com', from.to_s
+ end
+
+ def test_from_with_name_for_smtp
+ ActionMailer::Base.delivery_method = :smtp
+ TestMailer.deliver_from_with_name
+
+ mail = MockSMTP.deliveries.first
+ assert_not_nil mail
+ mail, from, to = mail
+
+ assert_equal 'system@loudthinking.com', from.to_s
+ end
+
def test_reply_to
expected = new_mail
diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb
index 2224f6321c..18eb355e7d 100644
--- a/actionmailer/test/url_test.rb
+++ b/actionmailer/test/url_test.rb
@@ -1,7 +1,9 @@
require 'abstract_unit'
-class TestMailer < ActionMailer::Base
+class WelcomeController < ActionController::Base
+end
+class TestMailer < ActionMailer::Base
default_url_options[:host] = 'www.basecamphq.com'
def signed_up_with_url(recipient)
@@ -52,27 +54,25 @@ class ActionMailerUrlTest < Test::Unit::TestCase
end
def test_signed_up_with_url
- ActionController::Routing.use_controllers! ['welcome'] do
- ActionController::Routing::Routes.draw do |map|
- map.connect ':controller/:action/:id'
- map.welcome 'welcome', :controller=>"foo", :action=>"bar"
- end
-
- expected = new_mail
- expected.to = @recipient
- expected.subject = "[Signed up] Welcome #{@recipient}"
- expected.body = "Hello there, \n\nMr. #{@recipient}. Please see our greeting at http://example.com/welcome/greeting http://www.basecamphq.com/welcome\n\n<img alt=\"Somelogo\" src=\"/images/somelogo.png\" />"
- expected.from = "system@loudthinking.com"
- expected.date = Time.local(2004, 12, 12)
-
- created = nil
- assert_nothing_raised { created = TestMailer.create_signed_up_with_url(@recipient) }
- assert_not_nil created
- assert_equal expected.encoded, created.encoded
-
- assert_nothing_raised { TestMailer.deliver_signed_up_with_url(@recipient) }
- assert_not_nil ActionMailer::Base.deliveries.first
- assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
+ ActionController::Routing::Routes.draw do |map|
+ map.connect ':controller/:action/:id'
+ map.welcome 'welcome', :controller=>"foo", :action=>"bar"
end
+
+ expected = new_mail
+ expected.to = @recipient
+ expected.subject = "[Signed up] Welcome #{@recipient}"
+ expected.body = "Hello there, \n\nMr. #{@recipient}. Please see our greeting at http://example.com/welcome/greeting http://www.basecamphq.com/welcome\n\n<img alt=\"Somelogo\" src=\"/images/somelogo.png\" />"
+ expected.from = "system@loudthinking.com"
+ expected.date = Time.local(2004, 12, 12)
+
+ created = nil
+ assert_nothing_raised { created = TestMailer.create_signed_up_with_url(@recipient) }
+ assert_not_nil created
+ assert_equal expected.encoded, created.encoded
+
+ assert_nothing_raised { TestMailer.deliver_signed_up_with_url(@recipient) }
+ assert_not_nil ActionMailer::Base.deliveries.first
+ assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
end
end
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index ccf6bf1e83..936279ad12 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
s.add_dependency('activemodel', '= 3.0.pre')
s.add_dependency('rack', '~> 1.0.1')
s.add_dependency('rack-test', '~> 0.5.0')
- s.add_dependency('rack-mount', '~> 0.2.2')
+ s.add_dependency('rack-mount', '~> 0.2.3')
s.add_dependency('erubis', '~> 2.6.5')
s.require_path = 'lib'
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
index f8f6b424ca..07b4919934 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
@@ -7,7 +7,7 @@
names = traces.collect {|name, trace| name}
%>
-<p><code>RAILS_ROOT: <%= defined?(RAILS_ROOT) ? RAILS_ROOT : "unset" %></code></p>
+<p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
<div id="traces">
<% names.each do |name| %>
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index cfe7425a61..34d75e55b6 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -2,8 +2,73 @@ module ActionDispatch
module Routing
class Mapper
module Resources
+ class Resource #:nodoc:
+ attr_reader :plural, :singular
+ attr_reader :path_prefix, :name_prefix
+
+ def initialize(entities, options = {})
+ entities = entities.to_s
+
+ @plural = entities.pluralize
+ @singular = entities.singularize
+
+ @path_prefix = options[:path_prefix]
+ @name_prefix = options[:name_prefix]
+ end
+
+ def name
+ plural
+ end
+
+ def controller
+ plural
+ end
+
+ def member_name
+ if name_prefix
+ "#{name_prefix}_#{singular}"
+ else
+ singular
+ end
+ end
+
+ def collection_name
+ if name_prefix
+ "#{name_prefix}_#{plural}"
+ else
+ plural
+ end
+ end
+
+ def new_name
+ if name_prefix
+ "new_#{name_prefix}_#{singular}"
+ else
+ "new_#{singular}"
+ end
+ end
+
+ def edit_name
+ if name_prefix
+ "edit_#{name_prefix}_#{singular}"
+ else
+ "edit_#{singular}"
+ end
+ end
+ end
+
+ class SingletonResource < Resource #:nodoc:
+ def initialize(entity, options = {})
+ super(entity)
+ end
+
+ def name
+ singular
+ end
+ end
+
def resource(*resources, &block)
- options = resources.last.is_a?(Hash) ? resources.pop : {}
+ options = resources.extract_options!
if resources.length > 1
raise ArgumentError if block_given?
@@ -11,29 +76,31 @@ module ActionDispatch
return self
end
- resource = resources.pop
+ name_prefix = @scope[:options][:name_prefix] if @scope[:options]
+ resource = SingletonResource.new(resources.pop, :name_prefix => name_prefix)
if @scope[:scope_level] == :resources
- member do
- resource(resource, options, &block)
+ parent_resource = @scope[:scope_level_options][:name]
+ parent_named_prefix = @scope[:scope_level_options][:name_prefix]
+ with_scope_level(:member) do
+ scope(":#{parent_resource}_id", :name_prefix => parent_named_prefix) do
+ resource(resource.name, options, &block)
+ end
end
return self
end
- singular = resource.to_s
- plural = singular.pluralize
-
- controller(plural) do
- namespace(resource) do
- with_scope_level(:resource) do
+ controller(resource.controller) do
+ namespace(resource.name) do
+ with_scope_level(:resource, :name => resource.singular, :name_prefix => resource.member_name) do
yield if block_given?
- get "", :to => :show, :as => "#{singular}"
+ get "", :to => :show, :as => resource.member_name
post "", :to => :create
put "", :to => :update
delete "", :to => :destroy
- get "new", :to => :new, :as => "new_#{singular}"
- get "edit", :to => :edit, :as => "edit_#{singular}"
+ get "new", :to => :new, :as => resource.new_name
+ get "edit", :to => :edit, :as => resource.edit_name
end
end
end
@@ -42,7 +109,7 @@ module ActionDispatch
end
def resources(*resources, &block)
- options = resources.last.is_a?(Hash) ? resources.pop : {}
+ options = resources.extract_options!
if resources.length > 1
raise ArgumentError if block_given?
@@ -50,42 +117,36 @@ module ActionDispatch
return self
end
- resource = resources.pop
-
- plural = resource.to_s
- singular = plural.singularize
+ name_prefix = @scope[:options][:name_prefix] if @scope[:options]
+ resource = Resource.new(resources.pop, :name_prefix => name_prefix)
if @scope[:scope_level] == :resources
parent_resource = @scope[:scope_level_options][:name]
+ parent_named_prefix = @scope[:scope_level_options][:name_prefix]
with_scope_level(:member) do
- scope(":#{parent_resource}_id", :name_prefix => parent_resource) do
- resources(resource, options, &block)
+ scope(":#{parent_resource}_id", :name_prefix => parent_named_prefix) do
+ resources(resource.name, options, &block)
end
end
return self
end
- if @scope[:options] && (prefix = @scope[:options][:name_prefix])
- plural = "#{prefix}_#{plural}"
- singular = "#{prefix}_#{singular}"
- end
-
- controller(resource) do
- namespace(resource) do
- with_scope_level(:resources, :name => singular) do
+ controller(resource.controller) do
+ namespace(resource.name) do
+ with_scope_level(:resources, :name => resource.singular, :name_prefix => resource.member_name) do
yield if block_given?
+ collection do
+ get "", :to => :index, :as => resource.collection_name
+ post "", :to => :create
+ get "new", :to => :new, :as => resource.new_name
+ end
+
member do
- get "", :to => :show, :as => singular
+ get "", :to => :show, :as => resource.member_name
put "", :to => :update
delete "", :to => :destroy
- get "edit", :to => :edit, :as => "edit_#{singular}"
- end
-
- collection do
- get "", :to => :index, :as => plural
- post "", :to => :create
- get "new", :to => :new, :as => "new_#{singular}"
+ get "edit", :to => :edit, :as => resource.edit_name
end
end
end
@@ -117,7 +178,7 @@ module ActionDispatch
end
def match(*args)
- options = args.last.is_a?(Hash) ? args.pop : {}
+ options = args.extract_options!
args.push(options)
case options.delete(:on)
@@ -146,8 +207,14 @@ module ActionDispatch
end
module Scoping
+ def self.extended(object)
+ object.instance_eval do
+ @scope = {}
+ end
+ end
+
def scope(*args)
- options = args.last.is_a?(Hash) ? args.pop : {}
+ options = args.extract_options!
constraints = options.delete(:constraints) || {}
unless constraints.is_a?(Hash)
@@ -193,9 +260,60 @@ module ActionDispatch
def constraints(constraints = {})
scope(:constraints => constraints) { yield }
end
+
+ def match(*args)
+ options = args.extract_options!
+ options = (@scope[:options] || {}).merge(options)
+ args.push(options)
+ super(*args)
+ end
+ end
+
+ module HttpHelpers
+ def get(*args, &block)
+ map_method(:get, *args, &block)
+ end
+
+ def post(*args, &block)
+ map_method(:post, *args, &block)
+ end
+
+ def put(*args, &block)
+ map_method(:put, *args, &block)
+ end
+
+ def delete(*args, &block)
+ map_method(:delete, *args, &block)
+ end
+
+ def redirect(path, options = {})
+ status = options[:status] || 301
+ lambda { |env|
+ req = Rack::Request.new(env)
+ url = req.scheme + '://' + req.host + path
+ [status, {'Location' => url, 'Content-Type' => 'text/html'}, ['Moved Permanently']]
+ }
+ end
+
+ private
+ def map_method(method, *args, &block)
+ options = args.extract_options!
+ options[:via] = method
+ args.push(options)
+ match(*args, &block)
+ self
+ end
end
class Constraints
+ def new(app, constraints = [])
+ if constraints.any?
+ super(app, constraints)
+ else
+ app
+ end
+ end
+
def initialize(app, constraints = [])
@app, @constraints = app, constraints
end
@@ -217,34 +335,18 @@ module ActionDispatch
def initialize(set)
@set = set
- @scope = {}
+ extend HttpHelpers
extend Scoping
extend Resources
end
- def get(*args, &block)
- map_method(:get, *args, &block)
- end
-
- def post(*args, &block)
- map_method(:post, *args, &block)
- end
-
- def put(*args, &block)
- map_method(:put, *args, &block)
- end
-
- def delete(*args, &block)
- map_method(:delete, *args, &block)
- end
-
def root(options = {})
match '/', options.merge(:as => :root)
end
def match(*args)
- options = args.last.is_a?(Hash) ? args.pop : {}
+ options = args.extract_options!
if args.length > 1
args.each { |path| match(path, options) }
@@ -257,7 +359,6 @@ module ActionDispatch
path = args.first
- options = (@scope[:options] || {}).merge(options)
conditions, defaults = {}, {}
path = nil if path == ""
@@ -289,51 +390,45 @@ module ActionDispatch
conditions[:request_method] = Regexp.union(*via)
end
- defaults[:controller] = @scope[:controller].to_s if @scope[:controller]
+ defaults[:controller] ||= @scope[:controller].to_s if @scope[:controller]
- if options[:to].respond_to?(:call)
- app = options[:to]
- defaults.delete(:controller)
- defaults.delete(:action)
- elsif options[:to].is_a?(String)
- defaults[:controller], defaults[:action] = options[:to].split('#')
- elsif options[:to].is_a?(Symbol)
- defaults[:action] = options[:to].to_s
+ app = initialize_app_endpoint(options, defaults)
+ validate_defaults!(app, defaults, segment_keys)
+ app = Constraints.new(app, blocks)
+
+ @set.add_route(app, conditions, requirements, defaults, options[:as])
+
+ self
+ end
+
+ private
+ def initialize_app_endpoint(options, defaults)
+ app = nil
+
+ if options[:to].respond_to?(:call)
+ app = options[:to]
+ defaults.delete(:controller)
+ defaults.delete(:action)
+ elsif options[:to].is_a?(String)
+ defaults[:controller], defaults[:action] = options[:to].split('#')
+ elsif options[:to].is_a?(Symbol)
+ defaults[:action] = options[:to].to_s
+ end
+
+ app || Routing::RouteSet::Dispatcher.new(:defaults => defaults)
end
- app ||= Routing::RouteSet::Dispatcher.new(:defaults => defaults)
- if app.is_a?(Routing::RouteSet::Dispatcher)
+ def validate_defaults!(app, defaults, segment_keys)
+ return unless app.is_a?(Routing::RouteSet::Dispatcher)
+
unless defaults.include?(:controller) || segment_keys.include?("controller")
raise ArgumentError, "missing :controller"
end
+
unless defaults.include?(:action) || segment_keys.include?("action")
raise ArgumentError, "missing :action"
end
end
-
- app = Constraints.new(app, blocks) if blocks.any?
- @set.add_route(app, conditions, requirements, defaults, options[:as])
-
- self
- end
-
- def redirect(path, options = {})
- status = options[:status] || 301
- lambda { |env|
- req = Rack::Request.new(env)
- url = req.scheme + '://' + req.host + path
- [status, {'Location' => url, 'Content-Type' => 'text/html'}, ['Moved Permanently']]
- }
- end
-
- private
- def map_method(method, *args, &block)
- options = args.last.is_a?(Hash) ? args.pop : {}
- options[:via] = method
- args.push(options)
- match(*args, &block)
- self
- end
end
end
end
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index e95e84aeb5..f6f7ec0c8c 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -25,7 +25,7 @@ require File.join(File.dirname(__FILE__), "action_pack")
module ActionView
def self.load_all!
- [Context, Base, InlineTemplate, TemplateError]
+ [Context, Base, TemplateError]
end
autoload :Base, 'action_view/base'
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 3ce90b6ccf..54de920740 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -632,13 +632,15 @@ class FragmentCachingTest < ActionController::TestCase
def test_fragment_for_logging
fragment_computed = false
- ActiveSupport::Notifications.queue.expects(:publish).times(2)
+ events = []
+ ActiveSupport::Notifications.subscribe { |*args| events << args }
buffer = 'generated till now -> '
@controller.fragment_for(buffer, 'expensive') { fragment_computed = true }
assert fragment_computed
assert_equal 'generated till now -> ', buffer
+ assert_equal [:fragment_exist?, :write_fragment], events.map(&:first)
end
end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 496445fc34..b8bcdc2808 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -251,9 +251,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get '/projects/1/companies/1/people'
assert_equal 'people#index', @response.body
- pending do
- assert_equal '/projects/1/companies/1/people', project_company_people_path(:project_id => '1', :company_id => '1')
- end
+ assert_equal '/projects/1/companies/1/people', project_company_people_path(:project_id => '1', :company_id => '1')
get '/projects/1/companies/1/avatar'
assert_equal 'avatars#show', @response.body
@@ -345,9 +343,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get '/projects/1/posts/1/comments'
assert_equal 'comments#index', @response.body
- pending do
- assert_equal '/projects/1/posts/1/comments', project_post_comments_path(:project_id => '1', :post_id => '1')
- end
+ assert_equal '/projects/1/posts/1/comments', project_post_comments_path(:project_id => '1', :post_id => '1')
post '/projects/1/posts/1/comments/preview'
assert_equal 'comments#preview', @response.body
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index e2540cd598..d9bfcbfcab 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -1,7 +1,4 @@
-require 'thread'
require 'active_support/core_ext/module/delegation'
-require 'active_support/core_ext/module/attribute_accessors'
-require 'active_support/secure_random'
module ActiveSupport
# Notifications provides an instrumentation API for Ruby. To instrument an
@@ -41,173 +38,42 @@ module ActiveSupport
# to subscribers in a thread. You can use any queue implementation you want.
#
module Notifications
- mattr_accessor :queue, :listener
+ autoload :Instrumenter, 'active_support/notifications/instrumenter'
+ autoload :Event, 'active_support/notifications/instrumenter'
+ autoload :Fanout, 'active_support/notifications/fanout'
class << self
- delegate :instrument, :transaction_id, :transaction, :to => :instrumenter
+ attr_writer :notifier
+ delegate :publish, :subscribe, :instrument, :to => :notifier
- def instrumenter
- Thread.current[:notifications_instrumeter] ||= Instrumenter.new(publisher)
- end
-
- def publisher
- @publisher ||= Publisher.new(queue)
- end
-
- def subscriber
- @subscriber ||= Subscriber.new(queue)
- end
-
- def subscribe(pattern=nil, options={}, &block)
- with = options[:with] || listener
- subscriber.bind(with, pattern).subscribe(&block)
+ def notifier
+ @notifier ||= Notifier.new
end
end
- class Instrumenter
- def initialize(publisher)
- @publisher = publisher
- @id = random_id
- end
-
- def transaction
- @id, old_id = random_id, @id
- yield
- ensure
- @id = old_id
- end
-
- def transaction_id
- @id
- end
-
- def instrument(name, payload={})
- time = Time.now
- result = yield if block_given?
- ensure
- @publisher.publish(name, time, Time.now, result, @id, payload)
- end
-
- private
- def random_id
- SecureRandom.hex(10)
- end
- end
-
- class Publisher
- def initialize(queue)
+ class Notifier
+ def initialize(queue = Fanout.new)
@queue = queue
end
def publish(*args)
@queue.publish(*args)
end
- end
-
- class Subscriber
- def initialize(queue)
- @queue = queue
- end
-
- def bind(listener, pattern)
- @listener = listener
- @pattern = pattern
- self
- end
- def subscribe
- @queue.subscribe(@listener, @pattern) do |*args|
- yield(*args)
- end
+ def subscribe(pattern = nil, &block)
+ @queue.bind(pattern).subscribe(&block)
end
- end
-
- class Event
- attr_reader :name, :time, :end, :transaction_id, :result, :payload
- def initialize(name, start, ending, result, transaction_id, payload)
- @name = name
- @payload = payload.dup
- @time = start
- @transaction_id = transaction_id
- @end = ending
- @result = result
+ def wait
+ @queue.wait
end
- def duration
- @duration ||= 1000.0 * (@end - @time)
- end
+ delegate :instrument, :to => :current_instrumenter
- def parent_of?(event)
- start = (self.time - event.time) * 1000
- start <= 0 && (start + duration >= event.duration)
- end
- end
-
- class AsyncListener
- def initialize(pattern, &block)
- @pattern = pattern
- @subscriber = block
- @queue = Queue.new
- Thread.new { consume }
- end
-
- def publish(name, *args)
- if !@pattern || @pattern === name.to_s
- @queue << args.unshift(name)
- end
- end
-
- def consume
- while args = @queue.shift
- @subscriber.call(*args)
- end
- end
-
- def drained?
- @queue.size.zero?
- end
- end
-
- class SyncListener
- def initialize(pattern, &block)
- @pattern = pattern
- @subscriber = block
- end
-
- def publish(name, *args)
- if !@pattern || @pattern === name.to_s
- @subscriber.call(*args.unshift(name))
+ private
+ def current_instrumenter
+ Thread.current[:"instrumentation_#{object_id}"] ||= Notifications::Instrumenter.new(self)
end
- end
-
- def drained?
- true
- end
- end
-
- # This is a default queue implementation that ships with Notifications. It
- # consumes events in a thread and publish them to all registered subscribers.
- #
- class LittleFanout
- def initialize
- @listeners = []
- end
-
- def publish(*args)
- @listeners.each { |l| l.publish(*args) }
- end
-
- def subscribe(listener, pattern=nil, &block)
- @listeners << listener.new(pattern, &block)
- end
-
- def drained?
- @listeners.all? &:drained?
- end
end
end
-
- Notifications.queue = Notifications::LittleFanout.new
- Notifications.listener = Notifications::AsyncListener
end
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
new file mode 100644
index 0000000000..bb07e4765c
--- /dev/null
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -0,0 +1,101 @@
+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.
+ #
+ class Fanout
+ def initialize(sync = false)
+ @subscriber_klass = sync ? Subscriber : AsyncSubscriber
+ @subscribers = []
+ end
+
+ def bind(pattern)
+ Binding.new(self, pattern)
+ end
+
+ def subscribe(pattern = nil, &block)
+ @subscribers << @subscriber_klass.new(pattern, &block)
+ end
+
+ def publish(*args)
+ @subscribers.each { |s| s.publish(*args) }
+ end
+
+ def wait
+ sleep(0.05) until @subscribers.all?(&:drained?)
+ end
+
+ # Used for internal implementation only.
+ class Binding #:nodoc:
+ def initialize(queue, pattern)
+ @queue = queue
+ @pattern =
+ case pattern
+ when Regexp, NilClass
+ pattern
+ else
+ /^#{Regexp.escape(pattern.to_s)}/
+ end
+ end
+
+ def subscribe(&block)
+ @queue.subscribe(@pattern, &block)
+ end
+ end
+
+ class Subscriber #:nodoc:
+ def initialize(pattern, &block)
+ @pattern = pattern
+ @block = block
+ end
+
+ def publish(*args)
+ push(*args) if matches?(args.first)
+ end
+
+ def drained?
+ true
+ end
+
+ private
+ def matches?(name)
+ !@pattern || @pattern =~ name.to_s
+ end
+
+ def push(*args)
+ @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
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
new file mode 100644
index 0000000000..fb95422af2
--- /dev/null
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -0,0 +1,47 @@
+require 'active_support/secure_random'
+require 'active_support/core_ext/module/delegation'
+
+module ActiveSupport
+ module Notifications
+ class Instrumenter
+ def initialize(notifier)
+ @id = unique_id
+ @notifier = notifier
+ end
+
+ def instrument(name, payload={})
+ time = Time.now
+ result = yield if block_given?
+ ensure
+ @notifier.publish(name, time, Time.now, result, @id, payload)
+ end
+
+ private
+ def unique_id
+ SecureRandom.hex(10)
+ end
+ end
+
+ class Event
+ attr_reader :name, :time, :end, :transaction_id, :result, :payload
+
+ def initialize(name, start, ending, result, transaction_id, payload)
+ @name = name
+ @payload = payload.dup
+ @time = start
+ @transaction_id = transaction_id
+ @end = ending
+ @result = result
+ end
+
+ def duration
+ @duration ||= 1000.0 * (@end - @time)
+ end
+
+ def parent_of?(event)
+ start = (self.time - event.time) * 1000
+ start <= 0 && (start + duration >= event.duration)
+ end
+ end
+ end
+end
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index 35d44367cf..4f880d0db7 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -1,221 +1,170 @@
require 'abstract_unit'
-# Allow LittleFanout to be cleaned.
-class ActiveSupport::Notifications::LittleFanout
- def clear
- @listeners.clear
- end
-end
-
-class NotificationsEventTest < Test::Unit::TestCase
- def test_events_are_initialized_with_details
- event = event(:foo, Time.now, Time.now + 1, 1, random_id, :payload => :bar)
- assert_equal :foo, event.name
- assert_equal Hash[:payload => :bar], event.payload
- end
-
- def test_events_consumes_information_given_as_payload
- time = Time.now
- event = event(:foo, time, time + 0.01, 1, random_id, {})
-
- assert_equal Hash.new, event.payload
- assert_equal time, event.time
- assert_equal 1, event.result
- assert_equal 10.0, event.duration
- end
-
- def test_event_is_parent_based_on_time_frame
- time = Time.utc(2009, 01, 01, 0, 0, 1)
-
- parent = event(:foo, Time.utc(2009), Time.utc(2009) + 100, nil, random_id, {})
- child = event(:foo, time, time + 10, nil, random_id, {})
- not_child = event(:foo, time, time + 100, nil, random_id, {})
-
- assert parent.parent_of?(child)
- assert !child.parent_of?(parent)
- assert !parent.parent_of?(not_child)
- assert !not_child.parent_of?(parent)
- end
+module Notifications
+ class TestCase < ActiveSupport::TestCase
+ def setup
+ Thread.abort_on_exception = true
+
+ @notifier = ActiveSupport::Notifications::Notifier.new
+ @events = []
+ @notifier.subscribe { |*args| @events << event(*args) }
+ end
-protected
+ def teardown
+ Thread.abort_on_exception = false
+ end
- def random_id
- @random_id ||= ActiveSupport::SecureRandom.hex(10)
- end
+ private
+ def event(*args)
+ ActiveSupport::Notifications::Event.new(*args)
+ end
- def event(*args)
- ActiveSupport::Notifications::Event.new(*args)
+ def drain
+ @notifier.wait
+ end
end
-end
-class NotificationsMainTest < Test::Unit::TestCase
- def setup
- @events = []
- Thread.abort_on_exception = true
- ActiveSupport::Notifications.subscribe do |*args|
- @events << ActiveSupport::Notifications::Event.new(*args)
+ class PubSubTest < TestCase
+ def test_events_are_published_to_a_listener
+ @notifier.publish :foo
+ @notifier.wait
+ assert_equal [[:foo]], @events
end
- end
- def teardown
- Thread.abort_on_exception = false
- ActiveSupport::Notifications.queue.clear
- end
-
- def test_notifications_returns_action_result
- result = ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") do
- 1 + 1
- end
+ def test_subscriber_with_pattern
+ events = []
+ @notifier.subscribe('1') { |*args| events << args }
- assert_equal 2, result
- end
+ @notifier.publish '1'
+ @notifier.publish '1.a'
+ @notifier.publish 'a.1'
+ @notifier.wait
- def test_events_are_published_to_a_listener
- ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") do
- 1 + 1
+ assert_equal [['1'], ['1.a']], events
end
- drain
-
- assert_equal 1, @events.size
- assert_equal :awesome, @events.last.name
- assert_equal Hash[:payload => "notifications"], @events.last.payload
- end
+ def test_subscriber_with_pattern_as_regexp
+ events = []
+ @notifier.subscribe(/\d/) { |*args| events << args }
- def test_nested_events_can_be_instrumented
- ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") do
- ActiveSupport::Notifications.instrument(:wot, :payload => "child") do
- 1 + 1
- end
+ @notifier.publish '1'
+ @notifier.publish 'a.1'
+ @notifier.publish '1.a'
+ @notifier.wait
- drain
-
- assert_equal 1, @events.size
- assert_equal :wot, @events.first.name
- assert_equal Hash[:payload => "child"], @events.first.payload
+ assert_equal [['1'], ['a.1'], ['1.a']], events
end
- drain
+ def test_multiple_subscribers
+ @another = []
+ @notifier.subscribe { |*args| @another << args }
+ @notifier.publish :foo
+ @notifier.wait
- assert_equal 2, @events.size
- assert_equal :awesome, @events.last.name
- assert_equal Hash[:payload => "notifications"], @events.last.payload
- end
-
- def test_event_is_pushed_even_if_block_fails
- ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") do
- raise "OMG"
- end rescue RuntimeError
-
- drain
+ assert_equal [[:foo]], @events
+ assert_equal [[:foo]], @another
+ end
- assert_equal 1, @events.size
- assert_equal :awesome, @events.last.name
- assert_equal Hash[:payload => "notifications"], @events.last.payload
+ private
+ def event(*args)
+ args
+ end
end
- def test_event_is_pushed_even_without_block
- ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications")
- drain
+ class SyncPubSubTest < PubSubTest
+ def setup
+ Thread.abort_on_exception = true
- assert_equal 1, @events.size
- assert_equal :awesome, @events.last.name
- assert_equal Hash[:payload => "notifications"], @events.last.payload
+ @notifier = ActiveSupport::Notifications::Notifier.new(ActiveSupport::Notifications::Fanout.new(true))
+ @events = []
+ @notifier.subscribe { |*args| @events << event(*args) }
+ end
end
- def test_subscribed_in_a_transaction
- @another = []
-
- ActiveSupport::Notifications.subscribe("cache") do |*args|
- @another << ActiveSupport::Notifications::Event.new(*args)
+ class InstrumentationTest < TestCase
+ def test_instrument_returns_block_result
+ assert_equal 2, @notifier.instrument(:awesome) { 1 + 1 }
end
- ActiveSupport::Notifications.instrument(:cache){ 1 }
- ActiveSupport::Notifications.transaction do
- ActiveSupport::Notifications.instrument(:cache){ 1 }
- end
- ActiveSupport::Notifications.instrument(:cache){ 1 }
+ def test_nested_events_can_be_instrumented
+ @notifier.instrument(:awesome, :payload => "notifications") do
+ @notifier.instrument(:wot, :payload => "child") do
+ 1 + 1
+ end
- drain
+ drain
- assert_equal 3, @another.size
- before, during, after = @another.map {|e| e.transaction_id }
- assert_equal before, after
- assert_not_equal before, during
- end
+ assert_equal 1, @events.size
+ assert_equal :wot, @events.first.name
+ assert_equal Hash[:payload => "child"], @events.first.payload
+ end
- def test_subscriber_with_pattern
- @another = []
+ drain
- ActiveSupport::Notifications.subscribe("cache") do |*args|
- @another << ActiveSupport::Notifications::Event.new(*args)
+ assert_equal 2, @events.size
+ assert_equal :awesome, @events.last.name
+ assert_equal Hash[:payload => "notifications"], @events.last.payload
end
- ActiveSupport::Notifications.instrument(:cache){ 1 }
-
- drain
+ def test_instrument_publishes_when_exception_is_raised
+ begin
+ @notifier.instrument(:awesome, :payload => "notifications") do
+ raise "OMG"
+ end
+ flunk
+ rescue
+ end
- assert_equal 1, @another.size
- assert_equal :cache, @another.first.name
- assert_equal 1, @another.first.result
- end
+ drain
- def test_subscriber_with_pattern_as_regexp
- @another = []
- ActiveSupport::Notifications.subscribe(/cache/) do |*args|
- @another << ActiveSupport::Notifications::Event.new(*args)
+ assert_equal 1, @events.size
+ assert_equal :awesome, @events.last.name
+ assert_equal Hash[:payload => "notifications"], @events.last.payload
end
- ActiveSupport::Notifications.instrument(:something){ 0 }
- ActiveSupport::Notifications.instrument(:cache){ 1 }
-
- drain
+ def test_event_is_pushed_even_without_block
+ @notifier.instrument(:awesome, :payload => "notifications")
+ drain
- assert_equal 1, @another.size
- assert_equal :cache, @another.first.name
- assert_equal 1, @another.first.result
+ assert_equal 1, @events.size
+ assert_equal :awesome, @events.last.name
+ assert_equal Hash[:payload => "notifications"], @events.last.payload
+ end
end
- def test_subscriber_allows_sync_listeners
- @another = []
- ActiveSupport::Notifications.subscribe(/cache/, :with => ActiveSupport::Notifications::SyncListener) do |*args|
- @another << ActiveSupport::Notifications::Event.new(*args)
+ class EventTest < TestCase
+ def test_events_are_initialized_with_details
+ event = event(:foo, Time.now, Time.now + 1, 1, random_id, :payload => :bar)
+ assert_equal :foo, event.name
+ assert_equal Hash[:payload => :bar], event.payload
end
- Thread.expects(:new).never
- ActiveSupport::Notifications.instrument(:something){ 0 }
- ActiveSupport::Notifications.instrument(:cache){ 1 }
+ def test_events_consumes_information_given_as_payload
+ time = Time.now
+ event = event(:foo, time, time + 0.01, 1, random_id, {})
- assert_equal 1, @another.size
- assert_equal :cache, @another.first.name
- assert_equal 1, @another.first.result
- end
-
- def test_with_several_consumers_and_several_events
- @another = []
- ActiveSupport::Notifications.subscribe do |*args|
- @another << ActiveSupport::Notifications::Event.new(*args)
+ assert_equal Hash.new, event.payload
+ assert_equal time, event.time
+ assert_equal 1, event.result
+ assert_equal 10.0, event.duration
end
- 1.upto(100) do |i|
- ActiveSupport::Notifications.instrument(:value){ i }
- end
+ def test_event_is_parent_based_on_time_frame
+ time = Time.utc(2009, 01, 01, 0, 0, 1)
- drain
+ parent = event(:foo, Time.utc(2009), Time.utc(2009) + 100, nil, random_id, {})
+ child = event(:foo, time, time + 10, nil, random_id, {})
+ not_child = event(:foo, time, time + 100, nil, random_id, {})
- assert_equal 100, @events.size
- assert_equal :value, @events.first.name
- assert_equal 1, @events.first.result
- assert_equal 100, @events.last.result
+ assert parent.parent_of?(child)
+ assert !child.parent_of?(parent)
+ assert !parent.parent_of?(not_child)
+ assert !not_child.parent_of?(parent)
+ end
- assert_equal 100, @another.size
- assert_equal :value, @another.first.name
- assert_equal 1, @another.first.result
- assert_equal 100, @another.last.result
+ protected
+ def random_id
+ @random_id ||= ActiveSupport::SecureRandom.hex(10)
+ end
end
-
- private
- def drain
- sleep(0.05) until ActiveSupport::Notifications.queue.drained?
- end
end
diff --git a/rack-mount b/rack-mount
deleted file mode 160000
-Subproject 3784e633b42f43a4131e02519be60080d179da2
diff --git a/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt b/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt
index 675f00043f..d8757460e4 100644
--- a/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt
+++ b/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt
@@ -1,5 +1,5 @@
class <%= class_name %>Generator < Rails::Generators::NamedBase
def self.source_root
- @source_root ||= File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
+ @source_root ||= File.expand_path('../templates', __FILE__)
end
end
diff --git a/railties/test/application/notifications_test.rb b/railties/test/application/notifications_test.rb
index 28dfdfcd83..71e406f2c1 100644
--- a/railties/test/application/notifications_test.rb
+++ b/railties/test/application/notifications_test.rb
@@ -5,21 +5,8 @@ module ApplicationTests
include ActiveSupport::Testing::Isolation
class MyQueue
- attr_reader :events, :subscribers
-
- def initialize
- @events = []
- @subscribers = []
- @listeners = []
- end
-
def publish(name, *args)
- @events << name
- end
-
- def subscribe(listener, pattern=nil, &block)
- @listeners << listener
- @subscribers << pattern
+ raise name
end
end
@@ -28,21 +15,16 @@ module ApplicationTests
boot_rails
require "rails"
require "active_support/notifications"
+ @events = []
Rails::Initializer.run do |c|
- c.notifications.queue = MyQueue.new
- c.notifications.subscribe(/listening/) do
- puts "Cool"
- end
+ c.notifications.notifier = ActiveSupport::Notifications::Notifier.new(MyQueue.new)
end
end
test "new queue is set" do
- ActiveSupport::Notifications.instrument(:foo)
- assert_equal :foo, ActiveSupport::Notifications.queue.events.first
- end
-
- test "configuration subscribers are loaded" do
- assert_equal 1, ActiveSupport::Notifications.queue.subscribers.count { |s| s == /listening/ }
+ assert_raise RuntimeError do
+ ActiveSupport::Notifications.publish('foo')
+ end
end
end
end